dsh-plugin-prompt-tool 3.1.3 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
3
- import { Document, Pair, Scalar, YAMLMap, YAMLSeq, isMap, isScalar, isSeq, parse, parseDocument, stringify, visit } from "yaml";
3
+ import { Document, Pair, Scalar, YAMLMap, YAMLSeq, isAlias, isMap, isScalar, isSeq, parse, parseDocument, stringify, visit } from "yaml";
4
4
  import { accessSync, appendFileSync, chmodSync, constants, cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { homedir } from "node:os";
@@ -15,9 +15,9 @@ import { Transform } from "node:stream";
15
15
  import { pipeline } from "node:stream/promises";
16
16
  import { Uint8ArrayReader, Uint8ArrayWriter, ZipReader, ZipWriter } from "@zip.js/zip.js";
17
17
  import { inflateSync } from "node:zlib";
18
+ import "@deepseek-ai/cordis";
18
19
  import SystemPrompt, { PERSONA_PREFIX_SECTION, PERSONA_SUFFIX_SECTION } from "@deepseek-ai/dsh-system-prompt";
19
20
  import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
20
- import "@deepseek-ai/cordis";
21
21
  //#region src/shared/persona-section.ts
22
22
  const FIELDS$1 = [
23
23
  "prefix",
@@ -429,6 +429,7 @@ const MODULE_TOOL_NAMES = {
429
429
  manifest: "module_manifest",
430
430
  variables: "module_variables",
431
431
  soulLedger: "soul_ledger",
432
+ soulState: "soul_state",
432
433
  search: "module_search",
433
434
  open: "module_open"
434
435
  };
@@ -441,6 +442,7 @@ const MODULE_TOOL_CAPABILITIES = {
441
442
  [MODULE_TOOL_NAMES.manifest]: "module-manifest-tools",
442
443
  [MODULE_TOOL_NAMES.variables]: "module-variable-tools",
443
444
  [MODULE_TOOL_NAMES.soulLedger]: "soul-ledger",
445
+ [MODULE_TOOL_NAMES.soulState]: "soul-ledger",
444
446
  [MODULE_TOOL_NAMES.search]: "retrieval",
445
447
  [MODULE_TOOL_NAMES.open]: "retrieval"
446
448
  };
@@ -2086,7 +2088,7 @@ const PAIR = Symbol.for("yaml.pair");
2086
2088
  const SCALAR$1 = Symbol.for("yaml.scalar");
2087
2089
  const SEQ = Symbol.for("yaml.seq");
2088
2090
  const NODE_TYPE = Symbol.for("yaml.node.type");
2089
- const isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
2091
+ const isAlias$1 = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
2090
2092
  const isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
2091
2093
  const isMap$1 = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
2092
2094
  const isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
@@ -2281,7 +2283,7 @@ function callVisitor(key, node, visitor, path) {
2281
2283
  if (isSeq$1(node)) return visitor.Seq?.(key, node, path);
2282
2284
  if (isPair(node)) return visitor.Pair?.(key, node, path);
2283
2285
  if (isScalar$1(node)) return visitor.Scalar?.(key, node, path);
2284
- if (isAlias(node)) return visitor.Alias?.(key, node, path);
2286
+ if (isAlias$1(node)) return visitor.Alias?.(key, node, path);
2285
2287
  }
2286
2288
  function replaceNode$1(key, path, node) {
2287
2289
  const parent = path[path.length - 1];
@@ -2291,7 +2293,7 @@ function replaceNode$1(key, path, node) {
2291
2293
  else parent.value = node;
2292
2294
  } else if (isDocument(parent)) parent.contents = node;
2293
2295
  else {
2294
- const pt = isAlias(parent) ? "alias" : "scalar";
2296
+ const pt = isAlias$1(parent) ? "alias" : "scalar";
2295
2297
  throw new Error(`Cannot replace node with ${pt} parent`);
2296
2298
  }
2297
2299
  }
@@ -2634,7 +2636,7 @@ var Alias = class extends NodeBase {
2634
2636
  else {
2635
2637
  nodes = [];
2636
2638
  visit$2(doc, { Node: (_key, node) => {
2637
- if (isAlias(node) || hasAnchor(node)) nodes.push(node);
2639
+ if (isAlias$1(node) || hasAnchor(node)) nodes.push(node);
2638
2640
  } });
2639
2641
  if (ctx) ctx.aliasResolveCache = nodes;
2640
2642
  }
@@ -2683,7 +2685,7 @@ var Alias = class extends NodeBase {
2683
2685
  }
2684
2686
  };
2685
2687
  function getAliasCount(doc, node, anchors) {
2686
- if (isAlias(node)) {
2688
+ if (isAlias$1(node)) {
2687
2689
  const source = node.resolve(doc);
2688
2690
  const anchor = anchors && source && anchors.get(source);
2689
2691
  return anchor ? anchor.count * anchor.aliasCount : 0;
@@ -3321,7 +3323,7 @@ function stringifyProps(node, tagObj, { anchors, doc }) {
3321
3323
  }
3322
3324
  function stringify$1(item, ctx, onComment, onChompKeep) {
3323
3325
  if (isPair(item)) return item.toString(ctx, onComment, onChompKeep);
3324
- if (isAlias(item)) {
3326
+ if (isAlias$1(item)) {
3325
3327
  if (ctx.doc.directives) return item.toString(ctx);
3326
3328
  if (ctx.resolvedAliases?.has(item)) throw new TypeError(`Cannot stringify circular structure without alias nodes`);
3327
3329
  else {
@@ -3448,11 +3450,11 @@ const merge = {
3448
3450
  const isMergeKey = (ctx, key) => (merge.identify(key) || isScalar$1(key) && (!key.type || key.type === Scalar$1.PLAIN) && merge.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge.tag && tag.default);
3449
3451
  function addMergeToJSMap(ctx, map, value) {
3450
3452
  const source = resolveAliasValue(ctx, value);
3451
- if (isSeq$1(source)) for (const it of source.items) mergeValue(ctx, map, it);
3452
- else if (Array.isArray(source)) for (const it of source) mergeValue(ctx, map, it);
3453
- else mergeValue(ctx, map, source);
3453
+ if (isSeq$1(source)) for (const it of source.items) mergeValue$1(ctx, map, it);
3454
+ else if (Array.isArray(source)) for (const it of source) mergeValue$1(ctx, map, it);
3455
+ else mergeValue$1(ctx, map, source);
3454
3456
  }
3455
- function mergeValue(ctx, map, value) {
3457
+ function mergeValue$1(ctx, map, value) {
3456
3458
  const source = resolveAliasValue(ctx, value);
3457
3459
  if (!isMap$1(source)) throw new Error("Merge sources must be maps or map aliases");
3458
3460
  const srcMap = source.toJSON(null, ctx, Map);
@@ -3468,7 +3470,7 @@ function mergeValue(ctx, map, value) {
3468
3470
  return map;
3469
3471
  }
3470
3472
  function resolveAliasValue(ctx, value) {
3471
- return ctx && isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
3473
+ return ctx && isAlias$1(value) ? value.resolve(ctx.doc, ctx) : value;
3472
3474
  }
3473
3475
  //#endregion
3474
3476
  //#region engine/vendor/yaml/dist/nodes/addPairToJSMap.js
@@ -8861,7 +8863,8 @@ function createSoulSelfResolver(config) {
8861
8863
  return ({ ctx, agent, signal }) => {
8862
8864
  const service = getService(ctx, SOUL_SELF_SERVICE);
8863
8865
  if (typeof service?.render !== "function") throw new Error("SELF dependency missing");
8864
- return { text: service.render(config.sourceModuleId, agent, signal) };
8866
+ const text = service.render(config.sourceModuleId, agent, signal);
8867
+ return typeof text?.then === "function" ? text.then((value) => ({ text: value })) : { text };
8865
8868
  };
8866
8869
  }
8867
8870
  /** 动态填充器:strategy=placeholder 时按 fill 键在注入点填充内容。 */
@@ -10346,6 +10349,7 @@ function ruleFrame(channel, args, warnOnce = () => {}, ctx, onOutcome) {
10346
10349
  subject: subjectOf(channel, args, warnOnce, ctx),
10347
10350
  decisions: /* @__PURE__ */ new Map(),
10348
10351
  actionDecisions: /* @__PURE__ */ new Set(),
10352
+ reminders: /* @__PURE__ */ new Map(),
10349
10353
  warnOnce,
10350
10354
  onOutcome
10351
10355
  };
@@ -10381,7 +10385,8 @@ function decideRule(rule, frame) {
10381
10385
  }
10382
10386
  const decided = {
10383
10387
  hit,
10384
- outcome
10388
+ outcome,
10389
+ reminders: hit ? rule.when?.soulReminders ?? [] : []
10385
10390
  };
10386
10391
  frame.decisions.set(rule, decided);
10387
10392
  return decided;
@@ -10407,7 +10412,11 @@ function ruleMatches(rule, frame) {
10407
10412
  * `hit` 再记 `miss`。没有分支的动作最终判定就是规则级判定,沿用规则级上报。
10408
10413
  */
10409
10414
  function actionMatches(entry, frame) {
10410
- if (!(entry.actionWhen !== void 0 || entry.bypassRuleWhen === true)) return ruleMatches(entry.rule, frame);
10415
+ if (!(entry.actionWhen !== void 0 || entry.bypassRuleWhen === true)) {
10416
+ const hit = ruleMatches(entry.rule, frame);
10417
+ frame.reminders.set(entry, hit ? decideRule(entry.rule, frame).reminders ?? [] : []);
10418
+ return hit;
10419
+ }
10411
10420
  const ruled = entry.bypassRuleWhen === true ? {
10412
10421
  hit: true,
10413
10422
  outcome: "hit"
@@ -10427,8 +10436,20 @@ function actionMatches(entry, frame) {
10427
10436
  frame.actionDecisions.add(entry);
10428
10437
  report(frame, entry.rule, outcome);
10429
10438
  }
10439
+ frame.reminders.set(entry, hit ? [...ruled.reminders ?? [], ...entry.actionWhen?.soulReminders ?? []] : []);
10430
10440
  return hit;
10431
10441
  }
10442
+ /** 只关联实际命中路径的正向Soul事实;普通分支没有提醒戳。 */
10443
+ function actionSoulReminder(entry, frame) {
10444
+ return frame.reminders.get(entry)?.[0];
10445
+ }
10446
+ function recheckSoulReminder(entry, frame, expected) {
10447
+ if (expected === void 0) return true;
10448
+ const current = ruleFrame(frame.channel, frame.args, frame.warnOnce, void 0);
10449
+ if (!actionMatches(entry, current)) return false;
10450
+ const fact = actionSoulReminder(entry, current);
10451
+ return fact?.predicate === expected.predicate && fact.moduleId === expected.moduleId && fact.pendingVersion === expected.pendingVersion && fact.epoch === expected.epoch;
10452
+ }
10432
10453
  //#endregion
10433
10454
  //#region engine/layers.mjs
10434
10455
  /**
@@ -10716,8 +10737,18 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10716
10737
  for (const config of binding.group) {
10717
10738
  if (!active || context.signal?.aborted || !actionMatches(config, frame) || !matchesAgentScope(config, context.agent)) continue;
10718
10739
  try {
10740
+ const reminder = actionSoulReminder(config, frame);
10719
10741
  const text = config.layer === "runtime-context" && (config.strategy === "placeholder" || !KNOWN_STRATEGIES.has(config.strategy)) ? await resolvedContextText(ctx, config, context, registry.get(config), warnOnce) : configText(config, registry, warnOnce, context);
10720
- if (text.length) blocks.push(text);
10742
+ if (!recheckSoulReminder(config, frame, reminder)) continue;
10743
+ if (text.length) {
10744
+ const stamp = reminder === void 0 ? "" : `\n<!-- soul-reminder:${Buffer.from(JSON.stringify({
10745
+ moduleId: config.sourceModuleId,
10746
+ sessionId: context.agent.session.id,
10747
+ pendingVersion: reminder.pendingVersion,
10748
+ epoch: reminder.epoch
10749
+ })).toString("base64")} -->`;
10750
+ blocks.push(text + stamp);
10751
+ }
10721
10752
  } catch (error) {
10722
10753
  warnOnce(`${name$2}: rule text ${config.id} failed: ${String(error?.message ?? error)}`);
10723
10754
  }
@@ -12117,6 +12148,7 @@ async function runPromptConfigBatch(options) {
12117
12148
  if (config.dedupe === "session") {
12118
12149
  if (alreadyDelivered(config, session, memo)) continue;
12119
12150
  } else if (config.dedupe === "batch" && hasInBatch(config, messages)) continue;
12151
+ const reminder = actionSoulReminder(config, options.ruleFrame);
12120
12152
  const signal = options.payload?.signal;
12121
12153
  if (signal?.aborted) continue;
12122
12154
  const resolved = await config.resolve({
@@ -12130,7 +12162,15 @@ async function runPromptConfigBatch(options) {
12130
12162
  });
12131
12163
  if (signal?.aborted) continue;
12132
12164
  if (resolved === null || resolved === void 0) continue;
12165
+ if (!recheckSoulReminder(config, options.ruleFrame, reminder)) continue;
12133
12166
  const patched = { ...resolved };
12167
+ if (reminder !== void 0) patched.source = {
12168
+ ...patched.source,
12169
+ soulReminder: {
12170
+ pendingVersion: reminder.pendingVersion,
12171
+ epoch: reminder.epoch
12172
+ }
12173
+ };
12134
12174
  const mergedVars = {
12135
12175
  ...config["variablesDisabled"] === true ? {} : config.params?.templateEvaluation !== true && [patched.text, ...config.texts].some((text) => typeof text === "string" && text.includes("{{")) ? config.variableStore?.read(config.sourceModuleId).effective ?? config.variables : config.variables,
12136
12176
  ...resolved.variables !== null && typeof resolved.variables === "object" ? resolved.variables : {}
@@ -14256,6 +14296,24 @@ function createManifestPredicate(options = {}, context = {}) {
14256
14296
  return predicate;
14257
14297
  }
14258
14298
  //#endregion
14299
+ //#region engine/conditions/soul.mjs
14300
+ function createSoulPredicate(options = {}, context = {}) {
14301
+ if (Object.keys(options).some((key) => key !== "pending") || options.pending !== true) throw new TypeError("predicates: soul requires pending: true");
14302
+ const predicate = (payload) => {
14303
+ predicate.soulReminders = [];
14304
+ if (typeof context.soulReminder?.read !== "function" || !context.moduleId) return UNAVAILABLE;
14305
+ const fact = context.soulReminder.read(context.moduleId, agentOf(payload));
14306
+ if (fact?.due === true) predicate.soulReminders = [{
14307
+ ...fact,
14308
+ predicate,
14309
+ moduleId: context.moduleId
14310
+ }];
14311
+ return typeof fact?.due === "boolean" ? fact.due : UNAVAILABLE;
14312
+ };
14313
+ predicate.kind = "soul";
14314
+ return predicate;
14315
+ }
14316
+ //#endregion
14259
14317
  //#region engine/conditions/operators.mjs
14260
14318
  /** 条件树组合算子的唯一清单:纯 ESM,无宿主 import,client 可打包。 */
14261
14319
  const COMPOSITE_OPERATORS = Object.freeze(Object.values(MATCH_LOGIC));
@@ -14284,21 +14342,29 @@ function composite(node) {
14284
14342
  const children = list.map(composite);
14285
14343
  const predicate = {
14286
14344
  [MATCH_LOGIC.ANY]: (payload) => {
14345
+ predicate.soulReminders = [];
14287
14346
  let unknown = false;
14288
14347
  for (const child of children) {
14289
14348
  const result = child(payload);
14290
14349
  if (result === UNAVAILABLE) unknown = true;
14291
- else if (result) return true;
14350
+ else if (result) {
14351
+ predicate.soulReminders = child.soulReminders ?? [];
14352
+ return true;
14353
+ }
14292
14354
  }
14293
14355
  return unknown ? UNAVAILABLE : false;
14294
14356
  },
14295
14357
  [MATCH_LOGIC.ALL]: (payload) => {
14358
+ predicate.soulReminders = [];
14359
+ const reminders = [];
14296
14360
  let unknown = false;
14297
14361
  for (const child of children) {
14298
14362
  const result = child(payload);
14299
14363
  if (result === UNAVAILABLE) unknown = true;
14300
14364
  else if (!result) return false;
14365
+ if (result === true) reminders.push(...child.soulReminders ?? []);
14301
14366
  }
14367
+ if (!unknown) predicate.soulReminders = reminders;
14302
14368
  return unknown ? UNAVAILABLE : true;
14303
14369
  },
14304
14370
  [MATCH_LOGIC.NOT_ANY]: (payload) => {
@@ -14332,7 +14398,8 @@ const PREDICATE_FACTORIES = Object.freeze({
14332
14398
  scope: createScopePredicate,
14333
14399
  anchor: createAnchorPredicate,
14334
14400
  variable: createVariablePredicate,
14335
- manifest: createManifestPredicate
14401
+ manifest: createManifestPredicate,
14402
+ soul: createSoulPredicate
14336
14403
  });
14337
14404
  const objectOrUndefined = (value, label) => {
14338
14405
  if (value === void 0 || value === null) return void 0;
@@ -14373,6 +14440,7 @@ function compileWhen(node, context = {}) {
14373
14440
  manifestCheck: context.manifestCheck,
14374
14441
  moduleId: context.moduleId
14375
14442
  });
14443
+ if (key === "soul") return createSoulPredicate(options, context);
14376
14444
  return withAvailableInput(key, options, factory(options));
14377
14445
  }
14378
14446
  //#endregion
@@ -14487,7 +14555,8 @@ const PREDICATE_EXAMPLES = {
14487
14555
  value: "0",
14488
14556
  scope: "effective"
14489
14557
  },
14490
- manifest: { ok: true }
14558
+ manifest: { ok: true },
14559
+ soul: { pending: true }
14491
14560
  };
14492
14561
  //#endregion
14493
14562
  //#region engine/record.mjs
@@ -14707,6 +14776,7 @@ const RULE_FIELDS = /* @__PURE__ */ new Set([
14707
14776
  "do"
14708
14777
  ]);
14709
14778
  const nonempty$1 = (value) => typeof value === "string" && value.trim().length > 0;
14779
+ const hasSoulCondition = (node) => node !== null && typeof node === "object" && (Object.hasOwn(node, "soul") || Object.values(node).some(hasSoulCondition));
14710
14780
  /** `prepend` 退役文案(动作层与 `inject-text.config` 共用):等价档只有「注册到最外层」一档,而 `inject-text` 连它也不接受。 */
14711
14781
  const PREPEND_RETIRED = "`prepend` 已取消 — 它是未文档化的注册后门(executor 直读 config.prepend),作用只有「注册到最外层」一档:非 inject-text 动作改用 `waterfallPosition: outermost`;`inject-text` 不接受该字段,没有等价写法";
14712
14782
  /**
@@ -14915,7 +14985,7 @@ function normalizeActionGates(action, execution) {
14915
14985
  /** options.promptConfigOptions 拥有模板/策略路径;configOrder 使用规则 id,moduleId 只标记来源。 */
14916
14986
  function compileRules(specs, options = {}) {
14917
14987
  if (!Array.isArray(specs)) throw new TypeError("rules must be an array");
14918
- const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, ...conditionOptions } = options;
14988
+ const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, soulReminder: _soulReminder, ...conditionOptions } = options;
14919
14989
  const ids = /* @__PURE__ */ new Set();
14920
14990
  const pendingConfigs = [];
14921
14991
  const moduleId = options.moduleId ?? options.sourceModuleId;
@@ -14959,7 +15029,10 @@ function compileRules(specs, options = {}) {
14959
15029
  if (source.prepend !== void 0) throw new TypeError(`rule ${spec.id}: action ${source.id}: ${PREPEND_RETIRED}`);
14960
15030
  if (source.match !== void 0) throw new TypeError(`rule ${spec.id}: action ${source.id}.match 已取消 — 声明路径写不了函数,条件改用 rule.if`);
14961
15031
  const action = structuredClone(source);
14962
- if (action.kind === "inject-text") action.config = injectionConfigSpec(spec, action, options);
15032
+ if (action.kind === "inject-text") {
15033
+ action.config = injectionConfigSpec(spec, action, options);
15034
+ if ((hasSoulCondition(ruleIf) || conditions.some(hasSoulCondition)) && !["pre-step", "runtime-context"].includes(action.config.layer)) throw new TypeError("soul reminder conditions support only pre-step or runtime-context delivery");
15035
+ }
14963
15036
  const execution = ruleActionExecution(action);
14964
15037
  normalizeActionGates(action, execution);
14965
15038
  validateConfig(`rule ${spec.id}: action ${action.id}`, action, ACTION_FIELDS[action.kind]);
@@ -15076,6 +15149,7 @@ function needsMountContext(node) {
15076
15149
  if (Object.hasOwn(node, "preset")) return true;
15077
15150
  if (Object.hasOwn(node, "variable")) return true;
15078
15151
  if (Object.hasOwn(node, "manifest")) return true;
15152
+ if (Object.hasOwn(node, "soul")) return true;
15079
15153
  if (Object.hasOwn(node, "phase") && node.phase?.subscribe !== false) return true;
15080
15154
  if (node.anchor?.after !== void 0) return true;
15081
15155
  return Object.values(node).some(needsMountContext);
@@ -26723,6 +26797,125 @@ const SOUL_LEDGER_TYPES = [
26723
26797
  "persona",
26724
26798
  "container"
26725
26799
  ];
26800
+ const METADATA_PATH = "data/.soul-ledger-v2.json";
26801
+ const hash = (value) => createHash("sha256").update(value).digest("hex");
26802
+ const ledgerIdentity = (moduleDir) => hash(canonicalDirectory(moduleDir));
26803
+ function boundaryOf(moduleDir, content) {
26804
+ const stored = readModuleFile(moduleDir, METADATA_PATH).content;
26805
+ if (!stored) return void 0;
26806
+ const value = JSON.parse(stored);
26807
+ if (value.version !== 2 || typeof value.identity !== "string" || !/^[a-f0-9]{64}$/.test(value.identity) || !Number.isSafeInteger(value.length) || value.length < 0 || value.length > content.length || value.prefix !== hash(content.slice(0, value.length))) throw new Error("账本历史分界不匹配,拒绝使用被替换的来源");
26808
+ return value;
26809
+ }
26810
+ function initializeBoundary(moduleDir, content) {
26811
+ if (boundaryOf(moduleDir, content)) return;
26812
+ ensureDataDirectory(moduleDir);
26813
+ const file = resolveModuleFilePath(moduleDir, METADATA_PATH, ["data"]);
26814
+ const temporary = file + "." + randomUUID() + ".tmp";
26815
+ try {
26816
+ writeFileSync(temporary, JSON.stringify({
26817
+ version: 2,
26818
+ identity: ledgerIdentity(moduleDir),
26819
+ length: content.length,
26820
+ prefix: hash(content)
26821
+ }), { flag: "wx" });
26822
+ renameSync(temporary, file);
26823
+ } finally {
26824
+ rmSync(temporary, { force: true });
26825
+ }
26826
+ }
26827
+ function readSoulLedgerProjection(moduleDir) {
26828
+ const content = readModuleFile(moduleDir, SOUL_LEDGER_PATH).content;
26829
+ const parsed = rowsOf(content);
26830
+ const boundary = boundaryOf(moduleDir, content);
26831
+ let prefix = hash(ledgerIdentity(moduleDir) + ":" + (boundary?.identity ?? ledgerIdentity(moduleDir)));
26832
+ let offset = 0;
26833
+ let index = 0;
26834
+ const items = [];
26835
+ const closed = /* @__PURE__ */ new Set();
26836
+ let currentLead;
26837
+ const entries = content.split("\n").flatMap((line) => {
26838
+ const position = offset;
26839
+ offset += line.length + 1;
26840
+ prefix = hash(prefix + ":" + line);
26841
+ if (!line.trim()) return [];
26842
+ const entry = {
26843
+ ...parsed[index++],
26844
+ id: prefix
26845
+ };
26846
+ if (entry.type === "lead") currentLead = entry.by;
26847
+ if (entry.type === "close") {
26848
+ const target = items.find((item) => item.id === entry.targetId);
26849
+ if (!target || closed.has(target.id) || !["completed", "cancelled"].includes(String(entry.outcome)) || !entry["概要"].trim() || entry.by !== currentLead && (target.entry.type === "promise" || target.entry.by !== entry.by)) throw new Error("账本收口事件无效或未获授权");
26850
+ closed.add(target.id);
26851
+ }
26852
+ if (entry.type === "promise" || entry.type === "shelved") items.push({
26853
+ id: prefix,
26854
+ entry,
26855
+ status: !boundary || position < boundary.length ? "pending-review" : "open"
26856
+ });
26857
+ return [entry];
26858
+ });
26859
+ const pending = items.filter((item) => !closed.has(item.id));
26860
+ const open = pending.filter((item) => item.status === "open").length;
26861
+ return {
26862
+ revision: hash(content),
26863
+ initialized: !!boundary,
26864
+ total: entries.length,
26865
+ entries,
26866
+ items: pending,
26867
+ open,
26868
+ pendingReview: pending.length - open,
26869
+ pendingVersion: hash(JSON.stringify(pending.map((item) => [item.id, item.status]))),
26870
+ lead: entries.findLast((entry) => entry.type === "lead")
26871
+ };
26872
+ }
26873
+ function closeSoulLedger(moduleDir, input, caller) {
26874
+ if (!caller.sessionId?.trim()) throw new Error("账本写入需要真实会话身份");
26875
+ if (Object.keys(input).some((key) => ![
26876
+ "targetId",
26877
+ "outcome",
26878
+ "reason",
26879
+ "ref"
26880
+ ].includes(key))) throw new Error("收口包含未知字段");
26881
+ if (typeof input.targetId !== "string" || !/^[a-f0-9]{64}$/.test(input.targetId)) throw new Error("账本引用无效");
26882
+ if (!["completed", "cancelled"].includes(input.outcome)) throw new Error("收口结果必须为 completed 或 cancelled");
26883
+ if (typeof input.reason !== "string" || !input.reason.trim() || Buffer.byteLength(input.reason) > 8192) throw new Error("收口依据必须为非空且不超过 8 KiB");
26884
+ if (input.ref !== void 0 && (typeof input.ref !== "string" || Buffer.byteLength(input.ref) > 8192)) throw new Error("收口 ref 超过 8 KiB");
26885
+ return withModuleLock(dirname(moduleDir), basename(moduleDir), () => {
26886
+ caller.signal?.throwIfAborted();
26887
+ const projection = readSoulLedgerProjection(moduleDir);
26888
+ const target = projection.entries.find((entry) => entry.id === input.targetId);
26889
+ if (!target || !["promise", "shelved"].includes(target.type)) throw new Error("账本引用不存在或不是可收口事项");
26890
+ if (projection.lead?.by !== caller.sessionId && (target.type === "promise" || target.by !== caller.sessionId)) throw new Error("只有当前主导可收口 promise;shelved 允许原作者或当前主导");
26891
+ const existing = projection.entries.find((entry) => entry.type === "close" && entry.targetId === input.targetId);
26892
+ if (existing) {
26893
+ if (existing.outcome !== input.outcome) throw new Error("事项已收口,不能更改结果");
26894
+ const { id: _id, ...entry } = existing;
26895
+ return { entry };
26896
+ }
26897
+ const previous = readModuleFile(moduleDir, SOUL_LEDGER_PATH).content;
26898
+ const entry = {
26899
+ t: (/* @__PURE__ */ new Date()).toISOString(),
26900
+ by: caller.sessionId,
26901
+ type: "close",
26902
+ "概要": input.reason,
26903
+ targetId: input.targetId,
26904
+ outcome: input.outcome,
26905
+ ...input.ref === void 0 ? {} : { ref: input.ref }
26906
+ };
26907
+ persistEntry(moduleDir, previous, entry, caller.signal);
26908
+ return { entry };
26909
+ });
26910
+ }
26911
+ function persistEntry(moduleDir, previous, entry, signal) {
26912
+ const line = (previous.length > 0 && !previous.endsWith("\n") ? "\n" : "") + JSON.stringify(entry) + "\n";
26913
+ if (Buffer.byteLength(previous) + Buffer.byteLength(line) > 8388608) throw new Error("账本超过 8 MiB");
26914
+ initializeBoundary(moduleDir, previous);
26915
+ const file = resolveModuleFilePath(moduleDir, SOUL_LEDGER_PATH, ["data"]);
26916
+ signal?.throwIfAborted();
26917
+ appendFileSync(file, line, "utf8");
26918
+ }
26726
26919
  function rowsOf(content) {
26727
26920
  return content.split("\n").filter((line) => line.trim()).map((line, index) => {
26728
26921
  const row = JSON.parse(line);
@@ -26739,14 +26932,47 @@ function rowsOf(content) {
26739
26932
  function readSoulLedger(moduleDir, options = {}) {
26740
26933
  const limit = options.limit ?? 30;
26741
26934
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new Error("账本读取 limit 必须为 1–100");
26742
- if (options.type !== void 0 && !SOUL_LEDGER_TYPES.includes(options.type)) throw new Error("未知账本类型");
26743
- const all = rowsOf(readModuleFile(moduleDir, SOUL_LEDGER_PATH).content);
26935
+ if (options.type !== void 0 && ![...SOUL_LEDGER_TYPES, "close"].includes(options.type)) throw new Error("未知账本类型");
26936
+ const view = options.view ?? "history";
26937
+ if (![
26938
+ "history",
26939
+ "pending",
26940
+ "open",
26941
+ "pending-review"
26942
+ ].includes(view)) throw new Error("未知账本视图");
26943
+ const projection = readSoulLedgerProjection(moduleDir);
26944
+ const all = view === "history" ? projection.entries : projection.items.filter((item) => view === "pending" || item.status === view).map((item) => item.entry);
26744
26945
  const rows = options.type === void 0 ? all : all.filter((row) => row.type === options.type);
26745
- const entries = rows.slice(-limit);
26946
+ const filter = JSON.stringify([
26947
+ view,
26948
+ options.type ?? null,
26949
+ limit
26950
+ ]);
26951
+ let offset = view === "history" && options.cursor === void 0 ? Math.max(0, rows.length - limit) : 0;
26952
+ if (options.cursor !== void 0) {
26953
+ if (typeof options.cursor !== "string" || options.cursor.length > 2048) throw new Error("账本游标无效");
26954
+ let cursor;
26955
+ try {
26956
+ cursor = JSON.parse(Buffer.from(options.cursor, "base64url").toString("utf8"));
26957
+ } catch {
26958
+ throw new Error("账本游标无效");
26959
+ }
26960
+ if (!cursor || Object.keys(cursor).sort().join(",") !== "filter,offset,revision" || cursor.revision !== projection.revision || cursor.filter !== filter || !Number.isSafeInteger(cursor.offset) || cursor.offset < 0 || cursor.offset >= rows.length) throw new Error("账本游标已失效或不匹配");
26961
+ offset = cursor.offset;
26962
+ }
26963
+ const entries = rows.slice(offset, offset + limit);
26746
26964
  if (Buffer.byteLength(JSON.stringify(entries)) > 65536) throw new Error("账本读取结果超过 64 KiB,请降低 limit");
26965
+ const nextCursor = offset + limit < rows.length ? Buffer.from(JSON.stringify({
26966
+ revision: projection.revision,
26967
+ filter,
26968
+ offset: offset + limit
26969
+ })).toString("base64url") : void 0;
26747
26970
  return {
26748
26971
  entries,
26749
- total: rows.length
26972
+ total: rows.length,
26973
+ initialized: projection.initialized,
26974
+ revision: projection.revision,
26975
+ ...nextCursor ? { nextCursor } : {}
26750
26976
  };
26751
26977
  }
26752
26978
  function appendSoulLedger(moduleDir, input, caller) {
@@ -26766,7 +26992,7 @@ function appendSoulLedger(moduleDir, input, caller) {
26766
26992
  return withModuleLock(dirname(moduleDir), basename(moduleDir), () => {
26767
26993
  caller.signal?.throwIfAborted();
26768
26994
  const previous = readModuleFile(moduleDir, SOUL_LEDGER_PATH).content;
26769
- const rows = rowsOf(previous);
26995
+ const rows = readSoulLedgerProjection(moduleDir).entries;
26770
26996
  if (input.type === "promise" && rows.findLast((row) => row.type === "lead")?.by !== caller.sessionId) throw new Error("只有最后一条 lead 署名的主导会话可以写 promise");
26771
26997
  const entry = {
26772
26998
  t: (/* @__PURE__ */ new Date()).toISOString(),
@@ -26776,12 +27002,7 @@ function appendSoulLedger(moduleDir, input, caller) {
26776
27002
  ...input.ref === void 0 ? {} : { ref: input.ref },
26777
27003
  ...input.feel === void 0 ? {} : { feel: input.feel }
26778
27004
  };
26779
- const line = (previous.length > 0 && !previous.endsWith("\n") ? "\n" : "") + JSON.stringify(entry) + "\n";
26780
- if (Buffer.byteLength(previous) + Buffer.byteLength(line) > 8388608) throw new Error("账本超过 8 MiB");
26781
- ensureDataDirectory(moduleDir);
26782
- const file = resolveModuleFilePath(moduleDir, SOUL_LEDGER_PATH, ["data"]);
26783
- caller.signal?.throwIfAborted();
26784
- appendFileSync(file, line, "utf8");
27005
+ persistEntry(moduleDir, previous, entry, caller.signal);
26785
27006
  return { entry };
26786
27007
  });
26787
27008
  }
@@ -26801,18 +27022,38 @@ function registerSoulLedgerTools(ctx, host) {
26801
27022
  });
26802
27023
  const offTool = toolsCtx.tools.register(defineTool({
26803
27024
  name: MODULE_TOOL_NAMES.soulLedger,
26804
- description: "读取或追加模块只追加账本。作者和时间来自真实执行会话;promise 仅当前 lead 署名者能写。lead 必须由用户指定并附 ref;text 与概要二选一,feel 有感受才写。",
27025
+ description: "读取、追加或逐项收口模块账本。作者时间由真实会话提供;promise 仅当前 lead 可写/收口,shelved 原作者或当前 lead 可收口。close 用 targetId、completed/cancelled 和非空 reason;ref 是证据出处。pending/open/pending-review 视图支持版本游标分页。lead 必须用户指定并附 ref。",
26805
27026
  parameters: {
26806
27027
  moduleId: MODULE_ID_PARAM,
26807
27028
  action: {
26808
27029
  type: "string",
26809
- enum: ["list", "append"],
27030
+ enum: [
27031
+ "list",
27032
+ "append",
27033
+ "close"
27034
+ ],
26810
27035
  required: true
26811
27036
  },
27037
+ targetId: { type: "string" },
27038
+ outcome: {
27039
+ type: "string",
27040
+ enum: ["completed", "cancelled"]
27041
+ },
27042
+ reason: { type: "string" },
26812
27043
  type: {
26813
27044
  type: "string",
26814
- enum: [...SOUL_LEDGER_TYPES]
27045
+ enum: [...SOUL_LEDGER_TYPES, "close"]
26815
27046
  },
27047
+ view: {
27048
+ type: "string",
27049
+ enum: [
27050
+ "history",
27051
+ "pending",
27052
+ "open",
27053
+ "pending-review"
27054
+ ]
27055
+ },
27056
+ cursor: { type: "string" },
26816
27057
  text: { type: "string" },
26817
27058
  "概要": { type: "string" },
26818
27059
  ref: { type: "string" },
@@ -26835,7 +27076,14 @@ function registerSoulLedgerTools(ctx, host) {
26835
27076
  additionalProperties: true
26836
27077
  }
26837
27078
  },
26838
- total: { type: "integer" }
27079
+ total: { type: "integer" },
27080
+ nextCursor: { type: "string" },
27081
+ initialized: { type: "boolean" },
27082
+ revision: { type: "string" },
27083
+ derived: {
27084
+ type: "object",
27085
+ additionalProperties: true
27086
+ }
26839
27087
  }
26840
27088
  },
26841
27089
  render: (_args, value) => [{
@@ -26852,20 +27100,59 @@ function registerSoulLedgerTools(ctx, host) {
26852
27100
  "概要",
26853
27101
  "ref",
26854
27102
  "feel",
26855
- "limit"
27103
+ "limit",
27104
+ "targetId",
27105
+ "outcome",
27106
+ "reason",
27107
+ "view",
27108
+ "cursor"
26856
27109
  ].includes(key))) throw new Error("账本工具含未知字段,不能指定作者或覆盖历史");
26857
27110
  exec.signal.throwIfAborted();
26858
27111
  const target = host.target(exec, args.moduleId);
27112
+ const committed = (result) => {
27113
+ try {
27114
+ const owner = ctx.get?.(SOUL_SELF_SERVICE);
27115
+ const derived = exec.agent && owner?.committed?.(target.id, exec.agent);
27116
+ return {
27117
+ ...result,
27118
+ ...derived ? { derived } : {}
27119
+ };
27120
+ } catch {
27121
+ return {
27122
+ ...result,
27123
+ derived: {
27124
+ state: "failed",
27125
+ error: "账本已提交;派生状态通知失败,请用soul_state check核对,勿重放写入。"
27126
+ }
27127
+ };
27128
+ }
27129
+ };
27130
+ if (args.action === "close") {
27131
+ if (args.type !== void 0 || args.text !== void 0 || args["概要"] !== void 0 || args.feel !== void 0 || args.limit !== void 0 || args.view !== void 0 || args.cursor !== void 0) throw new Error("收口不接受追加或读取字段");
27132
+ if (!args.targetId || !args.outcome || !args.reason) throw new Error("收口需要 targetId、outcome 与 reason");
27133
+ return committed(closeSoulLedger(target.dir, {
27134
+ targetId: args.targetId,
27135
+ outcome: args.outcome,
27136
+ reason: args.reason,
27137
+ ...args.ref === void 0 ? {} : { ref: args.ref }
27138
+ }, {
27139
+ sessionId: exec.agent?.session.id,
27140
+ signal: exec.signal
27141
+ }));
27142
+ }
27143
+ if (args.targetId !== void 0 || args.outcome !== void 0 || args.reason !== void 0) throw new Error("只有收口接受 targetId、outcome 与 reason");
26859
27144
  if (args.action === "list") {
26860
27145
  if (args.text !== void 0 || args["概要"] !== void 0 || args.ref !== void 0 || args.feel !== void 0) throw new Error("读取账本不接受写入字段");
26861
27146
  return readSoulLedger(target.dir, {
26862
27147
  limit: args.limit,
26863
- type: args.type
27148
+ type: args.type,
27149
+ view: args.view,
27150
+ cursor: args.cursor
26864
27151
  });
26865
27152
  }
26866
- if (args.limit !== void 0 || args.type === void 0) throw new Error("追加账本需要 type 且不接受 limit");
27153
+ if (args.limit !== void 0 || args.view !== void 0 || args.cursor !== void 0 || args.type === void 0) throw new Error("追加账本需要 type 且不接受读取字段");
26867
27154
  if (args.type === "lead" && !args.ref?.trim()) throw new Error("lead 必须提供用户指定主导的 ref");
26868
- return appendSoulLedger(target.dir, {
27155
+ return committed(appendSoulLedger(target.dir, {
26869
27156
  type: args.type,
26870
27157
  ...args.text === void 0 ? {} : { text: args.text },
26871
27158
  ...args["概要"] === void 0 ? {} : { "概要": args["概要"] },
@@ -26874,7 +27161,7 @@ function registerSoulLedgerTools(ctx, host) {
26874
27161
  }, {
26875
27162
  sessionId: exec.agent?.session.id,
26876
27163
  signal: exec.signal
26877
- });
27164
+ }));
26878
27165
  }
26879
27166
  }));
26880
27167
  return () => {
@@ -26887,6 +27174,162 @@ function registerSoulLedgerTools(ctx, host) {
26887
27174
  };
26888
27175
  }
26889
27176
  //#endregion
27177
+ //#region src/host/soul-state.ts
27178
+ /** 显式会话短状态;真实会话选路径,正文与账本事件分开持久。 */
27179
+ function statePath(moduleDir, sessionId) {
27180
+ if (typeof sessionId !== "string" || !sessionId.trim() || sessionId.length > 256) throw new Error("Soul状态缺少实际会话身份");
27181
+ assertModuleSnapshotSource(dirname(moduleDir), basename(moduleDir));
27182
+ return `data/.soul/state-${revisionOf$4(sessionId)}.json`;
27183
+ }
27184
+ function readSoulState(moduleDir, sessionId) {
27185
+ const path = statePath(moduleDir, sessionId);
27186
+ const target = join(moduleDir, path);
27187
+ assertNoModuleLinks(target);
27188
+ if (!modulePathExists(target)) return {
27189
+ mark: null,
27190
+ revision: null
27191
+ };
27192
+ const source = readModuleFile(moduleDir, path);
27193
+ if (source.bytes > 4096) throw new Error("Soul状态文件超过上限");
27194
+ let value;
27195
+ try {
27196
+ value = JSON.parse(source.content);
27197
+ } catch {
27198
+ throw new Error("Soul状态文件损坏,不能重置");
27199
+ }
27200
+ if (!record$7(value) || value.version !== 1 || value.sessionId !== sessionId || !record$7(value.mark) || Object.keys(value).some((key) => ![
27201
+ "version",
27202
+ "sessionId",
27203
+ "mark"
27204
+ ].includes(key)) || Object.keys(value.mark).some((key) => !["text", "recordedAt"].includes(key)) || typeof value.mark.text !== "string" || !value.mark.text.trim() || value.mark.text.length > 512 || Buffer.byteLength(value.mark.text) > 2048 || typeof value.mark.recordedAt !== "string" || !Number.isFinite(Date.parse(value.mark.recordedAt))) throw new Error("Soul状态文件无效或会话身份不匹配");
27205
+ return {
27206
+ mark: {
27207
+ text: value.mark.text,
27208
+ recordedAt: value.mark.recordedAt
27209
+ },
27210
+ revision: revisionOf$4(source.content)
27211
+ };
27212
+ }
27213
+ function markSoulState(moduleDir, sessionId, input, signal) {
27214
+ if (typeof input.text !== "string" || !input.text.trim() || input.text.length > 512 || Buffer.byteLength(input.text) > 2048) throw new Error("mark长度须为1–512字符且不超过2048字节");
27215
+ if (input.expectedRevision !== null && (typeof input.expectedRevision !== "string" || !/^[a-f0-9]{64}$/.test(input.expectedRevision))) throw new Error("mark需要当前expectedRevision,首次为null");
27216
+ const text = input.text, expected = input.expectedRevision;
27217
+ assertModuleDirectory(dirname(moduleDir), basename(moduleDir));
27218
+ const path = statePath(moduleDir, sessionId);
27219
+ signal?.throwIfAborted();
27220
+ return withModuleLock(dirname(moduleDir), basename(moduleDir), () => {
27221
+ signal?.throwIfAborted();
27222
+ assertModuleDirectory(dirname(moduleDir), basename(moduleDir));
27223
+ const before = readSoulState(moduleDir, sessionId);
27224
+ if (before.revision !== expected) throw new Error("mark版本冲突,请重读当前状态");
27225
+ if (before.mark?.text === text) return before;
27226
+ const data = ensureDataDirectory(moduleDir), stateDir = join(data, ".soul");
27227
+ assertNoModuleLinks(stateDir);
27228
+ mkdirSync(stateDir, { recursive: true });
27229
+ const target = resolveModuleFilePath(moduleDir, path, ["data"]);
27230
+ const temporary = join(stateDir, ".mark-" + randomUUID() + ".tmp");
27231
+ const content = JSON.stringify({
27232
+ version: 1,
27233
+ sessionId,
27234
+ mark: {
27235
+ text,
27236
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString()
27237
+ }
27238
+ }) + "\n";
27239
+ try {
27240
+ writeFileSync(temporary, content, {
27241
+ flag: "wx",
27242
+ encoding: "utf8"
27243
+ });
27244
+ signal?.throwIfAborted();
27245
+ if (readSoulState(moduleDir, sessionId).revision !== expected) throw new Error("mark版本冲突,请重读当前状态");
27246
+ renameSync(temporary, target);
27247
+ } finally {
27248
+ rmSync(temporary, { force: true });
27249
+ }
27250
+ return readSoulState(moduleDir, sessionId);
27251
+ });
27252
+ }
27253
+ //#endregion
27254
+ //#region src/runtime/soul-state-tools.ts
27255
+ function registerSoulStateTools(ctx, host, observe) {
27256
+ const fiber = ctx.inject(["tools"], (toolsCtx) => toolsCtx.tools.register(defineTool({
27257
+ name: MODULE_TOOL_NAMES.soulState,
27258
+ description: "读取真实当前会话Soul状态或只读运行诊断;check显式includeUpgrade=true才展开完整升级文件候选。mark记录短状态及时间,需要status版本,首次null;标记时间不代表实际活动,不关闭账本事项。",
27259
+ parameters: {
27260
+ moduleId: MODULE_ID_PARAM,
27261
+ action: {
27262
+ type: "string",
27263
+ enum: [
27264
+ "status",
27265
+ "mark",
27266
+ "check"
27267
+ ],
27268
+ required: true
27269
+ },
27270
+ text: { type: "string" },
27271
+ expectedRevision: { oneOf: [{ type: "string" }, { type: "null" }] },
27272
+ includeUpgrade: { type: "boolean" }
27273
+ },
27274
+ output: {
27275
+ schema: {
27276
+ type: "object",
27277
+ additionalProperties: false,
27278
+ properties: {
27279
+ sessionId: { type: "string" },
27280
+ revision: { oneOf: [{ type: "string" }, { type: "null" }] },
27281
+ mark: { oneOf: [{ type: "null" }, {
27282
+ type: "object",
27283
+ additionalProperties: false,
27284
+ properties: {
27285
+ text: { type: "string" },
27286
+ recordedAt: { type: "string" }
27287
+ }
27288
+ }] },
27289
+ observation: { type: "json" }
27290
+ }
27291
+ },
27292
+ render: (_args, value) => [{
27293
+ type: "text",
27294
+ text: JSON.stringify(value)
27295
+ }]
27296
+ },
27297
+ execute: async (args, exec) => {
27298
+ if (Object.keys(args).some((key) => ![
27299
+ "moduleId",
27300
+ "action",
27301
+ "text",
27302
+ "expectedRevision",
27303
+ "includeUpgrade"
27304
+ ].includes(key))) throw new Error("Soul状态请求含未知字段,不能指定其他会话");
27305
+ if (args.includeUpgrade !== void 0 && (typeof args.includeUpgrade !== "boolean" || args.action !== "check")) throw new Error("只有check接受布尔includeUpgrade");
27306
+ exec.signal.throwIfAborted();
27307
+ const target = host.target(exec, args.moduleId), sessionId = exec.agent?.session.id;
27308
+ if (!sessionId) throw new Error("Soul状态缺少实际会话身份");
27309
+ if (args.action === "mark") return {
27310
+ sessionId,
27311
+ ...markSoulState(target.dir, sessionId, {
27312
+ text: args.text,
27313
+ expectedRevision: args.expectedRevision
27314
+ }, exec.signal)
27315
+ };
27316
+ if (args.action !== "status" && args.action !== "check") throw new Error("Soul状态action无效");
27317
+ if (args.text !== void 0 || args.expectedRevision !== void 0) throw new Error("Soul状态读取不接受写入字段");
27318
+ const observation = observe ? await observe[args.action](exec, target.id, args.includeUpgrade) : void 0;
27319
+ exec.signal.throwIfAborted();
27320
+ if (host.target(exec, args.moduleId).dir !== target.dir || exec.agent?.session.id !== sessionId) throw new Error("Soul状态来源或会话已变化");
27321
+ return {
27322
+ sessionId,
27323
+ ...readSoulState(target.dir, sessionId),
27324
+ ...observation === void 0 ? {} : { observation }
27325
+ };
27326
+ }
27327
+ })));
27328
+ return () => {
27329
+ fiber.dispose();
27330
+ };
27331
+ }
27332
+ //#endregion
26890
27333
  //#region src/host/soul-self.ts
26891
27334
  /** SELF 派生快照:骨架与事实共享,运行身份只在模型出口加入。 */
26892
27335
  const SOUL_SELF_SOURCE = "data/SELF.src.md";
@@ -26911,45 +27354,78 @@ function readSoulSelfSnapshot(moduleDir) {
26911
27354
  revision: revisionOf$4(content)
26912
27355
  };
26913
27356
  }
26914
- function refreshSoulSelf(moduleDir, index, options) {
26915
- const budget = validateSelfBudget(options.budgetBytes);
26916
- options.signal?.throwIfAborted();
27357
+ function selfInputs(moduleDir) {
26917
27358
  const source = readModuleFile(moduleDir, SOUL_SELF_SOURCE).content;
27359
+ const ledger = readSoulLedgerProjection(moduleDir);
27360
+ return {
27361
+ source,
27362
+ ledger,
27363
+ version: revisionOf$4(source) + ":" + ledger.revision + ":" + ledger.pendingVersion + ":" + ledger.initialized
27364
+ };
27365
+ }
27366
+ function deriveSoulSelf(moduleDir, index = { state: "unbuilt(未建)" }, budgetBytes = 12288) {
27367
+ for (let attempt = 0; attempt < 3; attempt++) {
27368
+ const input = selfInputs(moduleDir);
27369
+ const candidate = renderSelfFacts(input, index, budgetBytes);
27370
+ if (selfInputs(moduleDir).version === input.version) return candidate;
27371
+ }
27372
+ throw new Error("SELF 来源持续变化,无法形成一致候选");
27373
+ }
27374
+ function renderSelfFacts({ source, ledger }, index, budgetBytes) {
27375
+ const budget = validateSelfBudget(budgetBytes);
26918
27376
  if (!source.trim()) throw new Error("SELF 骨架缺失:data/SELF.src.md");
26919
- const ledger = readSoulLedger(moduleDir, { limit: 1 });
26920
- const lead = readSoulLedger(moduleDir, {
26921
- type: "lead",
26922
- limit: 1
26923
- }).entries[0];
26924
- const promises = readSoulLedger(moduleDir, {
26925
- type: "promise",
26926
- limit: 3
26927
- });
26928
- const shelved = readSoulLedger(moduleDir, {
26929
- type: "shelved",
26930
- limit: 3
26931
- });
26932
27377
  const count = (key) => {
26933
27378
  const value = index[key] ?? 0;
26934
27379
  if (!Number.isSafeInteger(value) || value < 0) throw new Error("SELF 索引状态无效");
26935
27380
  return value;
26936
27381
  };
26937
- const content = seal([
26938
- source.split("## 活体")[0].trim(),
27382
+ const now = (/* @__PURE__ */ new Date()).toISOString();
27383
+ const lines = [
27384
+ seal(source),
26939
27385
  "",
26940
- "## 活体(自动装配)",
26941
- `- 装配时刻:${(/* @__PURE__ */ new Date()).toISOString()}`,
26942
- `- 账本:共 ${ledger.total} 条;未结 ${promises.total + shelved.total} 条`,
26943
- ...lead ? [`- 最后主导记录:${lead["概要"]}`] : ["- 主导:尚无 lead 记录"],
26944
- ...[...promises.entries, ...shelved.entries].sort((a, b) => a.t.localeCompare(b.t)).slice(-3).map((row) => `- [${row.type}] ${row["概要"]}`),
26945
- `- 索引:${index.state};文件 ${count("files")};会话 ${count("sessions")};事件 ${count("events")}`,
26946
- ""
26947
- ].join("\n"));
26948
- if (Buffer.byteLength(content) > budget - 512) throw new Error("SELF 超预算,需精简骨架或账本内容;旧快照保留");
27386
+ "## 活体(当前权威事实)",
27387
+ `- 当前时钟:${now};时区:${Intl.DateTimeFormat().resolvedOptions().timeZone}`,
27388
+ `- 账本:共 ${ledger.total} 条;未结 ${ledger.open} 条;历史待核对 ${ledger.pendingReview} 条${ledger.initialized ? "" : ";历史分界待初始化"}`,
27389
+ `- 主导:${ledger.lead ? seal(ledger.lead["概要"]).slice(0, 160) : "尚无 lead 记录"}`,
27390
+ `- 索引:${index.state}${index.files === void 0 ? "" : `;文件 ${count("files")};会话 ${count("sessions")};事件 ${count("events")}`}`,
27391
+ `- 索引最近成功观测:${index.observedAt ?? "尚未观测"};未扫描外部变化时不保证当前一致。`
27392
+ ];
27393
+ const remaining = (shown) => `- 未展示事项:${ledger.items.length - shown};用 soul_ledger list 的需处理视图分页读取。`;
27394
+ const reserve = 224;
27395
+ if (Buffer.byteLength([...lines, remaining(0)].join("\n")) > budget - reserve) throw new Error("SELF 超预算,需精简骨架;旧快照保留");
27396
+ let shown = 0;
27397
+ for (const item of ledger.items.slice(0, 3)) {
27398
+ const summary = seal(item.entry["概要"]);
27399
+ const line = `- [${item.status}] ${item.id}:${summary.slice(0, 240)}${summary.length > 240 ? "…(摘要)" : ""}`;
27400
+ if (Buffer.byteLength([
27401
+ ...lines,
27402
+ line,
27403
+ remaining(shown + 1)
27404
+ ].join("\n")) > budget - reserve) break;
27405
+ lines.push(line);
27406
+ shown += 1;
27407
+ }
27408
+ lines.push(remaining(shown));
27409
+ return {
27410
+ content: lines.join("\n") + "\n",
27411
+ sourceRevision: revisionOf$4(source),
27412
+ ledgerRevision: ledger.revision,
27413
+ pendingVersion: ledger.pendingVersion,
27414
+ observedAt: now
27415
+ };
27416
+ }
27417
+ function refreshSoulSelf(moduleDir, index, options) {
27418
+ options.signal?.throwIfAborted();
27419
+ const candidate = deriveSoulSelf(moduleDir, index, options.budgetBytes);
27420
+ const content = candidate.content.replace("## 活体(当前权威事实)", `## 诊断快照(非实时)\n- 快照时刻:${candidate.observedAt}`);
26949
27421
  return withModuleLock(dirname(moduleDir), basename(moduleDir), () => {
26950
27422
  options.signal?.throwIfAborted();
26951
27423
  if (readSoulSelfSnapshot(moduleDir).revision !== options.expectedRevision) throw new Error("SELF 快照版本冲突");
26952
- if (readModuleFile(moduleDir, "data/SELF.src.md").content !== source) throw new Error("SELF 骨架版本冲突");
27424
+ const assertCurrent = () => {
27425
+ const current = selfInputs(moduleDir);
27426
+ if (revisionOf$4(current.source) !== candidate.sourceRevision || current.ledger.revision !== candidate.ledgerRevision || current.ledger.pendingVersion !== candidate.pendingVersion) throw new Error("SELF 来源版本冲突");
27427
+ };
27428
+ assertCurrent();
26953
27429
  const data = ensureDataDirectory(moduleDir), stateDir = join(data, ".soul");
26954
27430
  assertNoModuleLinks(stateDir);
26955
27431
  mkdirSync(stateDir, { recursive: true });
@@ -26962,6 +27438,7 @@ function refreshSoulSelf(moduleDir, index, options) {
26962
27438
  });
26963
27439
  options.signal?.throwIfAborted();
26964
27440
  if (readSoulSelfSnapshot(moduleDir).revision !== options.expectedRevision) throw new Error("SELF 快照版本冲突");
27441
+ assertCurrent();
26965
27442
  renameSync(temp, target);
26966
27443
  } finally {
26967
27444
  rmSync(temp, { force: true });
@@ -26973,13 +27450,494 @@ function refreshSoulSelf(moduleDir, index, options) {
26973
27450
  };
26974
27451
  });
26975
27452
  }
26976
- function renderSoulSelf(moduleDir, agent, signal, budgetBytes = 12288) {
27453
+ function renderSoulSelf(moduleDir, agent, signal, budgetBytes = 12288, index, runtimeLines = []) {
26977
27454
  signal?.throwIfAborted();
26978
27455
  if (!agent?.session?.id) throw new Error("SELF 缺少实际 Agent 身份");
26979
- const snapshot = readSoulSelfSnapshot(moduleDir);
26980
- const text = `${snapshot.revision === null ? "[SELF missing:等待骨架初始化与维护]" : seal(snapshot.content)}\n## 当前运行身份\n- 本会话:${agent.session.id}\n- 受众:${(agent.session.header?.delegationDepth ?? 0) > 0 ? "子代理" : "主会话"}\n`;
27456
+ for (const path of [
27457
+ SOUL_SELF_SOURCE,
27458
+ "data/02-soul.jsonl",
27459
+ "data/02-墨.jsonl"
27460
+ ]) assertNoModuleLinks(join(moduleDir, path));
27461
+ let prefix;
27462
+ try {
27463
+ prefix = deriveSoulSelf(moduleDir, index, budgetBytes).content;
27464
+ } catch (error) {
27465
+ const code = error.code;
27466
+ if (code === "EACCES" || code === "EPERM" || /链接|越界|超预算|不是普通文件/.test(String(error.message))) throw error;
27467
+ const reason = /持续变化/.test(String(error.message)) ? "来源持续变化,无法形成一致候选" : "账本或骨架数据不可读取";
27468
+ const snapshot = readSoulSelfSnapshot(moduleDir);
27469
+ const stamp = /^- (?:快照时刻|装配时刻):([^\n]+)/m.exec(snapshot.content)?.[1];
27470
+ const sourceTime = stamp && Number.isFinite(Date.parse(stamp)) ? stamp : "unknown";
27471
+ prefix = snapshot.revision === null ? `[SELF unavailable:${reason};无安全诊断快照;未结数量未知]` : `[SELF degraded:来源时间:${sourceTime};原因:${reason};以下仅为历史诊断,当前未结未知]\n${seal(snapshot.content).replaceAll("当前权威事实", "历史诊断事实").replaceAll("当前时钟", "快照时钟")}`;
27472
+ }
27473
+ let text = seal(`${prefix}\n## 当前运行身份\n- 当前时钟:${(/* @__PURE__ */ new Date()).toISOString()}\n- 本会话:${agent.session.id}\n- 受众:${(agent.session.header?.delegationDepth ?? 0) > 0 ? "子代理" : "主会话"}\n`);
26981
27474
  if (Buffer.byteLength(text) > validateSelfBudget(budgetBytes)) throw new Error("SELF 动态上下文超预算");
26982
- return seal(text);
27475
+ for (const line of runtimeLines) {
27476
+ const available = budgetBytes - Buffer.byteLength(text) - 1;
27477
+ const sealed = seal(line);
27478
+ if (Buffer.byteLength(sealed) <= available) {
27479
+ text += sealed + "\n";
27480
+ continue;
27481
+ }
27482
+ const suffix = "…(状态摘要)";
27483
+ let summary = "";
27484
+ for (const char of sealed) {
27485
+ if (Buffer.byteLength(summary + char + suffix) > available) break;
27486
+ summary += char;
27487
+ }
27488
+ if (summary) text += summary + "…(状态摘要)\n";
27489
+ break;
27490
+ }
27491
+ return text;
27492
+ }
27493
+ //#endregion
27494
+ //#region src/host/soul-diagnostics.ts
27495
+ /** 只读就绪证据;文件齐全、候选渲染与实际投递分别报告。 */
27496
+ function readSoulDiagnostics(moduleDir, observations = {}) {
27497
+ const observedAt = (/* @__PURE__ */ new Date()).toISOString(), checks = [];
27498
+ const read = (name, action) => {
27499
+ try {
27500
+ checks.push({
27501
+ name,
27502
+ observedAt,
27503
+ ...action()
27504
+ });
27505
+ } catch (error) {
27506
+ const message = error instanceof Error ? error.message : "";
27507
+ const reason = /链接|越界|非法资源路径/.test(message) ? "来源路径不安全" : /JSON|UTF-8|格式|无效/.test(message) ? "来源格式无效" : /超过|超限/.test(message) ? "来源超过预算" : "来源不可读取";
27508
+ checks.push({
27509
+ name,
27510
+ state: "failed",
27511
+ observedAt,
27512
+ reason,
27513
+ nextAction: "核对模块声明与来源文件后重新检查"
27514
+ });
27515
+ }
27516
+ };
27517
+ read("manifest", () => {
27518
+ const checked = runModuleManifest(moduleDir, "check"), next = runModuleManifest(moduleDir, "next");
27519
+ if (checked.ok && next.ok) return { state: "ready" };
27520
+ const failed = checked.entries.find((entry) => !entry.ok);
27521
+ return {
27522
+ state: "incomplete",
27523
+ reason: failed?.error ? `文件检查:${failed.error}` : "装配文件尚未齐备",
27524
+ nextAction: "通过正式初始化入口预览缺失项"
27525
+ };
27526
+ });
27527
+ read("skeleton", () => readModuleFile(moduleDir, "data/SELF.src.md").content.trim() ? { state: "ready" } : {
27528
+ state: "missing",
27529
+ nextAction: "初始化或补全SELF骨架"
27530
+ });
27531
+ read("ledger", () => {
27532
+ const ledger = readSoulLedgerProjection(moduleDir);
27533
+ if (!ledger.initialized) return {
27534
+ state: "pending-initialization",
27535
+ reason: "历史分界尚未初始化",
27536
+ nextAction: "显式初始化或通过正式账本写入口建立历史分界"
27537
+ };
27538
+ return ledger.pendingReview ? {
27539
+ state: "pending-review",
27540
+ reason: `有${ledger.pendingReview}项历史记录待核对`,
27541
+ nextAction: "使用soul_ledger逐项核对收口"
27542
+ } : { state: "ready" };
27543
+ });
27544
+ for (const [name, time] of [["self-rendered", observations.selfRenderedAt], ["self-delivered", observations.selfDeliveredAt]]) checks.push(time ? {
27545
+ name,
27546
+ state: "observed",
27547
+ observedAt: time
27548
+ } : {
27549
+ name,
27550
+ state: "not-observed",
27551
+ nextAction: name === "self-delivered" ? "等待实际模型投递确认" : "等待下一次真实装配"
27552
+ });
27553
+ for (const name of [
27554
+ "index",
27555
+ "maintenance",
27556
+ "context",
27557
+ "recovery"
27558
+ ]) {
27559
+ const observation = observations[name];
27560
+ if (!observation) checks.push({
27561
+ name,
27562
+ state: "not-observed",
27563
+ nextAction: "核对能力启用并等待运行观测"
27564
+ });
27565
+ else if (name !== "index" && observation.state === "ready" && observation.last == null && !observation.observedAt) checks.push({
27566
+ name,
27567
+ state: "not-observed",
27568
+ reason: "仅已装配,尚无执行证据"
27569
+ });
27570
+ else checks.push({
27571
+ name,
27572
+ state: observation.state,
27573
+ ...observation.observedAt ? { observedAt: observation.observedAt } : {},
27574
+ ...["ready", "success"].includes(observation.state) ? {} : { nextAction: name === "index" ? "显式刷新授权索引或检查刷新失败" : "核对能力状态与最近执行证据" }
27575
+ });
27576
+ }
27577
+ return {
27578
+ state: checks.every((check) => [
27579
+ "ready",
27580
+ "observed",
27581
+ "success"
27582
+ ].includes(check.state)) ? "ready" : "attention",
27583
+ observedAt,
27584
+ checks
27585
+ };
27586
+ }
27587
+ //#endregion
27588
+ //#region src/host/soul-upgrade.ts
27589
+ /** 定向升级只生成候选,持久化仍由正式导入预览与重建事务拥有。 */
27590
+ const RULE_IDS = ["soul-ledger-open", "soul-check"];
27591
+ const VARIABLE_IDS = ["ledger_open", "ledger_seq"];
27592
+ const BASELINE = `rules:
27593
+ - id: soul-check
27594
+ name: 检查失败处置
27595
+ enabled: true
27596
+ layer: system-section
27597
+ if: { manifest: { ok: false } }
27598
+ then:
27599
+ - id: inject
27600
+ kind: inject-text
27601
+ config:
27602
+ id: soul-check
27603
+ layer: system-section
27604
+ strategy: static
27605
+ order: 247
27606
+ text: |-
27607
+ 清单检查当前有未通过项(由引擎按装配事实求值,不需要手写变量):
27608
+ 先 module_manifest(moduleId: soul)action=check 看全部明细,
27609
+ 再说清是哪一条、证据是什么、下一步改哪个文件或哪条规则;
27610
+ 修完要重新 check,不要用「已修复」代替重新检查,也不要把失败项藏进总结。
27611
+ - id: soul-ledger-open
27612
+ name: 未结账本提醒
27613
+ enabled: true
27614
+ layer: pre-step
27615
+ if: { variable: { key: ledger_open, op: gt, value: '0' } }
27616
+ then:
27617
+ - id: inject
27618
+ kind: inject-text
27619
+ config:
27620
+ id: soul-ledger-open
27621
+ layer: pre-step
27622
+ strategy: static
27623
+ position: before-all
27624
+ order: 126
27625
+ role: user
27626
+ text: |-
27627
+ 账本还有未结事项:回复里逐条收口,或说清为什么先放着。
27628
+ 先用 soul_ledger(moduleId: soul,action: list)核对原始未结记录;
27629
+ 需要追加时用 soul_ledger(moduleId: soul,action: append,type: fact/shelved/gain/loss/persona/container 之一,概要: 一句话,ref: 出处),
27630
+ 作者和时间由宿主提供,promise 只允许最后 lead 署名的主导会话;lead 需用户指定并经审批。
27631
+ 只往后加,不改旧行;收口后把 ledger_open 减到 0。
27632
+ `;
27633
+ async function readSoulUpgradePreview(moduleDir) {
27634
+ const directory = assertModuleDirectory(userModulesDir(), basename(moduleDir));
27635
+ if (directory !== moduleDir && directory.replaceAll("\\", "/") !== moduleDir.replaceAll("\\", "/")) throw new Error("Soul升级目标不在当前用户模块根");
27636
+ const file = join(directory, "module.yml");
27637
+ assertNoModuleLinks(file);
27638
+ if (!statSync(file).isFile() || statSync(file).size > 1048576) throw new Error("Soul升级模块定义无效或超限");
27639
+ const source = readFileSync(file, "utf8");
27640
+ const value = parseDocument(source);
27641
+ if (value.errors.length) throw new Error("Soul升级YAML无效");
27642
+ const rules = value.get("rules");
27643
+ if (!isSeq(rules) || !RULE_IDS.every((id) => rules.items.some((rule) => isMap(rule) && rule.get("id") === id))) return { status: "not-applicable" };
27644
+ const seed = readFileSync(join(packageModulesDir(), "soul/module.yml"), "utf8");
27645
+ const preview = previewSoulUpgrade(source, BASELINE, seed);
27646
+ const result = {
27647
+ ...preview,
27648
+ expectedRevision: revisionOf$4(source),
27649
+ files: [],
27650
+ apply: "仅预览;将完整files交给module_import模型工具正式预览,保留targetId并设overwrite:true;核对expectedRevision后携工具返回的两份凭据提交。该工具保留私有data、memory及既有技能资产并重建;不要用普通覆盖导入替代,不自动更新运行副本。"
27651
+ };
27652
+ if (!preview.candidate) return result;
27653
+ const exported = await exportModulePackage(userModulesDir(), {
27654
+ id: basename(moduleDir),
27655
+ mode: "zip"
27656
+ });
27657
+ const files = await expandModuleSource([{
27658
+ path: "soul.zip",
27659
+ content: exported.content,
27660
+ encoding: "base64"
27661
+ }]);
27662
+ if ((await exportModulePackage(userModulesDir(), {
27663
+ id: basename(moduleDir),
27664
+ mode: "zip",
27665
+ preview: true
27666
+ })).revision !== exported.revision) throw new Error("Soul升级资产已变化,请重新预览");
27667
+ if (revisionOf$4(readFileSync(file, "utf8")) !== result.expectedRevision) throw new Error("Soul升级定义已变化,请重新预览");
27668
+ const path = "assets/retrieval-synonyms.json";
27669
+ const content = readFileSync(join(packageModulesDir(), "soul", path), "utf8");
27670
+ const previous = files.find((file) => file.path === path);
27671
+ if (previous && decodeAssetFile(previous).toString("utf8") !== content) return {
27672
+ ...result,
27673
+ status: "conflict",
27674
+ conflicts: [...result.conflicts, `${path}: 用户已修改近义表`],
27675
+ candidate: null
27676
+ };
27677
+ result.files = files.map((file) => file.path === "module.yml" ? {
27678
+ path: file.path,
27679
+ content: preview.candidate
27680
+ } : file);
27681
+ if (!previous) {
27682
+ result.files.push({
27683
+ path,
27684
+ content
27685
+ });
27686
+ result.changes.push({
27687
+ id: path,
27688
+ before: null,
27689
+ after: "新增近义表资产"
27690
+ });
27691
+ }
27692
+ return result;
27693
+ }
27694
+ function knownShape(value, shape) {
27695
+ if (Array.isArray(shape)) return Array.isArray(value) ? value.map((item, i) => knownShape(item, shape[i])) : value;
27696
+ if (shape && typeof shape === "object" && value && typeof value === "object") return Object.fromEntries(Object.keys(shape).map((key) => [key, knownShape(value[key], shape[key])]));
27697
+ return value;
27698
+ }
27699
+ function mergeValue(current, old, fresh) {
27700
+ if (Array.isArray(fresh)) return fresh.map((item, index) => mergeValue(Array.isArray(current) ? current[index] : void 0, Array.isArray(old) ? old[index] : void 0, item));
27701
+ if (fresh && typeof fresh === "object") {
27702
+ const result = current && typeof current === "object" && !Array.isArray(current) ? structuredClone(current) : {};
27703
+ for (const key of Object.keys(old && typeof old === "object" ? old : {})) if (!(key in fresh) && !["enabled", "order"].includes(key)) delete result[key];
27704
+ for (const [key, value] of Object.entries(fresh)) {
27705
+ if (["enabled", "order"].includes(key) && key in result) continue;
27706
+ result[key] = mergeValue(result[key], old && typeof old === "object" ? old[key] : void 0, value);
27707
+ }
27708
+ return result;
27709
+ }
27710
+ return fresh;
27711
+ }
27712
+ function patchNode(document, current, wanted) {
27713
+ if (isAlias(current)) throw new Error("Soul升级目标含共享引用,请先解除相关引用");
27714
+ if (isMap(current) && wanted && typeof wanted === "object" && !Array.isArray(wanted)) {
27715
+ for (const key of current.items.map((pair) => String(pair.key))) if (!(key in wanted)) current.delete(key);
27716
+ for (const [key, value] of Object.entries(wanted)) current.set(key, patchNode(document, current.get(key, true), value));
27717
+ return current;
27718
+ }
27719
+ if (isSeq(current) && Array.isArray(wanted)) {
27720
+ current.items = wanted.map((value, index) => patchNode(document, current.items[index], value));
27721
+ return current;
27722
+ }
27723
+ if (isScalar(current) && (wanted === null || typeof wanted !== "object")) {
27724
+ current.value = wanted;
27725
+ return current;
27726
+ }
27727
+ const next = document.createNode(wanted);
27728
+ if (current && typeof current === "object") {
27729
+ for (const key of [
27730
+ "comment",
27731
+ "commentBefore",
27732
+ "spaceBefore"
27733
+ ]) if (key in current) next[key] = current[key];
27734
+ }
27735
+ return next;
27736
+ }
27737
+ function behavior(rule) {
27738
+ const value = structuredClone(rule);
27739
+ delete value.enabled;
27740
+ delete value.order;
27741
+ if (Array.isArray(value.then)) {
27742
+ for (const action of value.then) if (action && typeof action === "object" && action.config && typeof action.config === "object") delete action.config.order;
27743
+ }
27744
+ return JSON.stringify(value);
27745
+ }
27746
+ function previewSoulUpgrade(source, baseline, newSeed) {
27747
+ const [document, base, seed] = [
27748
+ source,
27749
+ baseline,
27750
+ newSeed
27751
+ ].map((text) => {
27752
+ if (Buffer.byteLength(text) > 1048576) throw new Error("Soul升级YAML超过1 MiB");
27753
+ const doc = parseDocument(text);
27754
+ if (doc.errors.length || !isMap(doc.contents)) throw new Error("Soul升级YAML无效");
27755
+ return doc;
27756
+ });
27757
+ const expected = JSON.parse(JSON.stringify(document.toJS()));
27758
+ const conflicts = [];
27759
+ const changes = [];
27760
+ const sourceRules = document.get("rules"), baseRules = base.get("rules"), seedRules = seed.get("rules");
27761
+ if (!isSeq(sourceRules) || !isSeq(baseRules) || !isSeq(seedRules)) throw new Error("Soul升级YAML缺少rules数组");
27762
+ for (const id of RULE_IDS) {
27763
+ const current = sourceRules.items.filter((rule) => isMap(rule) && rule.get("id") === id);
27764
+ const old = baseRules.items.find((rule) => isMap(rule) && rule.get("id") === id);
27765
+ const fresh = seedRules.items.find((rule) => isMap(rule) && rule.get("id") === id);
27766
+ if (current.length !== 1 || !isMap(current[0]) || !isMap(old) || !isMap(fresh)) {
27767
+ conflicts.push(`${id}: 目标规则缺失或重复`);
27768
+ continue;
27769
+ }
27770
+ const node = current[0], before = node.toJSON();
27771
+ const oldValue = old.toJSON(), freshValue = fresh.toJSON();
27772
+ if (behavior(knownShape(before, freshValue)) === behavior(freshValue)) continue;
27773
+ if (behavior(knownShape(before, oldValue)) !== behavior(oldValue)) {
27774
+ conflicts.push(`${id}: 用户已修改规则行为`);
27775
+ continue;
27776
+ }
27777
+ const after = mergeValue(before, oldValue, freshValue);
27778
+ expected.rules[sourceRules.items.indexOf(node)] = after;
27779
+ patchNode(document, node, after);
27780
+ changes.push({
27781
+ id,
27782
+ before,
27783
+ after
27784
+ });
27785
+ }
27786
+ const external = document.toJS();
27787
+ external.rules = external.rules.filter((rule) => !RULE_IDS.includes(String(rule.id)));
27788
+ for (const id of VARIABLE_IDS) if (external.variables) delete external.variables[id];
27789
+ const consumers = JSON.stringify(external);
27790
+ for (const id of VARIABLE_IDS) {
27791
+ if (consumers.includes(id)) {
27792
+ conflicts.push(`${id}: 目标规则以外仍有引用`);
27793
+ continue;
27794
+ }
27795
+ const variables = document.get("variables");
27796
+ if (isMap(variables) && variables.has(id)) {
27797
+ changes.push({
27798
+ id,
27799
+ before: expected.variables?.[id],
27800
+ after: null
27801
+ });
27802
+ variables.delete(id);
27803
+ delete expected.variables[id];
27804
+ }
27805
+ }
27806
+ for (const [capability, keys] of [
27807
+ ["retrieval", ["synonymsFile"]],
27808
+ ["soul-maintenance", ["pressureThresholds"]],
27809
+ ["generation-recovery", [
27810
+ "lineRepeatChars",
27811
+ "crossCallRepeatChars",
27812
+ "maxCycleChars"
27813
+ ]]
27814
+ ]) for (const key of keys) {
27815
+ const path = [
27816
+ "moduleConfigs",
27817
+ capability,
27818
+ key
27819
+ ];
27820
+ const fresh = seed.getIn(path);
27821
+ if (fresh === void 0) continue;
27822
+ const freshValue = isSeq(fresh) || isMap(fresh) || isScalar(fresh) ? fresh.toJSON() : fresh;
27823
+ const current = document.getIn(path);
27824
+ const currentValue = isSeq(current) || isMap(current) || isScalar(current) ? current.toJSON() : current;
27825
+ if (currentValue !== void 0) {
27826
+ if (!isDeepStrictEqual(currentValue, freshValue)) conflicts.push(`${capability}.${key}: 用户已配置不同值`);
27827
+ continue;
27828
+ }
27829
+ expected.moduleConfigs ??= {};
27830
+ expected.moduleConfigs[capability] ??= {};
27831
+ expected.moduleConfigs[capability][key] = structuredClone(freshValue);
27832
+ document.setIn(path, document.createNode(freshValue));
27833
+ changes.push({
27834
+ id: `${capability}.${key}`,
27835
+ before: null,
27836
+ after: freshValue
27837
+ });
27838
+ }
27839
+ return {
27840
+ status: conflicts.length ? "conflict" : "ready",
27841
+ conflicts,
27842
+ changes,
27843
+ candidate: conflicts.length ? null : serializeWithoutSharedAlias(document, expected, "Soul升级共享引用会连带改变用户定义,请先解除相关引用")
27844
+ };
27845
+ }
27846
+ //#endregion
27847
+ //#region src/runtime/soul-pressure.ts
27848
+ function readSoulPressure(ctx, agent, thresholds) {
27849
+ if (thresholds.length !== 3 || thresholds.some((value, i) => !Number.isFinite(value) || value <= 0 || value >= 1 || i > 0 && value <= thresholds[i - 1])) throw new Error("SOUL水位阈值必须为三个递增的0–1比例");
27850
+ const observedAt = (/* @__PURE__ */ new Date()).toISOString();
27851
+ try {
27852
+ const header = agent.session.requestHeader(), route = agent.session.requestContext();
27853
+ if (!header || !route || header.config.provider !== route.provider || header.config.model !== route.model) return {
27854
+ state: "unknown",
27855
+ observedAt,
27856
+ reason: "当前请求缺少匹配的模型窗口事实"
27857
+ };
27858
+ const capacity = route.contextWindow;
27859
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) return {
27860
+ state: "unknown",
27861
+ observedAt,
27862
+ reason: "当前模型窗口未知"
27863
+ };
27864
+ const meter = ctx.get("tokenMeter");
27865
+ if (!meter || typeof meter.measure !== "function") return {
27866
+ state: "unknown",
27867
+ observedAt,
27868
+ reason: "tokenMeter不可用"
27869
+ };
27870
+ const tokens = meter.measure(agent.session).totalTokens;
27871
+ if (!Number.isSafeInteger(tokens) || tokens < 0) return {
27872
+ state: "unknown",
27873
+ observedAt,
27874
+ reason: "压力测量无效"
27875
+ };
27876
+ const ratio = tokens / capacity;
27877
+ const level = ratio >= thresholds[2] ? "urgent" : ratio >= thresholds[1] ? "persist" : ratio >= thresholds[0] ? "organize" : "normal";
27878
+ return {
27879
+ state: "known",
27880
+ observedAt,
27881
+ tokens,
27882
+ capacity,
27883
+ ratio,
27884
+ level,
27885
+ source: "tokenMeter.measure + session.requestContext",
27886
+ guidance: level === "urgent" ? "优先落盘未结与关键依据,再整理上下文" : level === "persist" ? "先落盘关键事实与承诺" : level === "organize" ? "可开始整理上下文与未结事项" : "当前低于提示阈值"
27887
+ };
27888
+ } catch {
27889
+ return {
27890
+ state: "unknown",
27891
+ observedAt,
27892
+ reason: "当前会话压力不可测量"
27893
+ };
27894
+ }
27895
+ }
27896
+ //#endregion
27897
+ //#region src/runtime/soul-reminder.ts
27898
+ /** 未结提醒的确认态来自真实会话事件;候选求值不产生状态。 */
27899
+ function createSoulReminder(readProjection) {
27900
+ return { read(moduleId, value) {
27901
+ const agent = value, session = agent?.session;
27902
+ if (!session?.id || typeof session.snapshotEvents !== "function") return void 0;
27903
+ try {
27904
+ const projection = readProjection(moduleId, agent), count = projection.open + projection.pendingReview;
27905
+ if (!Number.isSafeInteger(count) || count < 0 || !projection.pendingVersion) return void 0;
27906
+ let epoch = -1;
27907
+ let confirmed;
27908
+ const events = session.snapshotEvents();
27909
+ for (let index = 0; index < events.length; index++) {
27910
+ const event = events[index];
27911
+ if (isSuccessfulCompactionEnd(event)) epoch = event.seq ?? index;
27912
+ const source = eventMessage(event)?.source;
27913
+ let mark = source?.moduleId === moduleId && source?.sessionId === session.id && String(source.kind).startsWith("plugin:") ? source.soulReminder : void 0;
27914
+ if (source?.kind === "runtime-context" && source.form === "snapshot" && Array.isArray(source.sections)) for (const section of source.sections) {
27915
+ if (typeof section?.text !== "string") continue;
27916
+ for (const match of section.text.matchAll(/<!-- soul-reminder:([A-Za-z0-9+/=]+) -->/g)) {
27917
+ let candidate;
27918
+ try {
27919
+ candidate = JSON.parse(Buffer.from(match[1], "base64").toString("utf8"));
27920
+ } catch {
27921
+ continue;
27922
+ }
27923
+ if (candidate.moduleId === moduleId && candidate.sessionId === session.id) mark = candidate;
27924
+ }
27925
+ }
27926
+ if (typeof mark === "object" && mark !== null && "pendingVersion" in mark && "epoch" in mark && typeof mark.pendingVersion === "string" && typeof mark.epoch === "number" && Number.isSafeInteger(mark.epoch)) confirmed = {
27927
+ pendingVersion: mark.pendingVersion,
27928
+ epoch: mark.epoch
27929
+ };
27930
+ }
27931
+ return {
27932
+ count,
27933
+ pendingVersion: projection.pendingVersion,
27934
+ epoch,
27935
+ due: count > 0 && (session.header?.delegationDepth ?? 0) === 0 && (confirmed?.pendingVersion !== projection.pendingVersion || confirmed.epoch !== epoch)
27936
+ };
27937
+ } catch {
27938
+ return;
27939
+ }
27940
+ } };
26983
27941
  }
26984
27942
  //#endregion
26985
27943
  //#region engine/retrieval-reference.mjs
@@ -27014,10 +27972,122 @@ function makeSessionReference(moduleId, sessionId, events, seqs, range = {}) {
27014
27972
  };
27015
27973
  }
27016
27974
  //#endregion
27975
+ //#region engine/context-provenance.mjs
27976
+ const configObject = passthrough((value, plugin, field) => {
27977
+ if (value === void 0) return {};
27978
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${plugin}: ${field} must be an object`);
27979
+ return value;
27980
+ });
27981
+ defineConfig({
27982
+ enabled: bool({ default: false }),
27983
+ audience: enumOf([
27984
+ "all",
27985
+ "main",
27986
+ "subagent"
27987
+ ], { default: "all" }),
27988
+ auto: bool({ default: true }),
27989
+ modelSwitch: bool({ default: true }),
27990
+ prune: bool({ default: true }),
27991
+ basic: configObject,
27992
+ pruner: configObject,
27993
+ recoveryRetainTokens: int$2({
27994
+ min: 0,
27995
+ default: 1024
27996
+ })
27997
+ });
27998
+ const runtimes = /* @__PURE__ */ new WeakMap();
27999
+ const contextRuntimeOf = (agent) => runtimes.get(agent);
28000
+ //#endregion
28001
+ //#region engine/generation-recovery.mjs
28002
+ const observations = /* @__PURE__ */ new WeakMap();
28003
+ const recoveryObservationOf = (agent) => observations.get(agent);
28004
+ const recoveryLimit = passthrough((value, plugin, field) => {
28005
+ if (value === void 0) return 0;
28006
+ if (!Number.isSafeInteger(value) || value < 0 || value > 1e3) throw new TypeError(`${plugin}: ${field} must be an integer from 0 to 1000`);
28007
+ return value;
28008
+ });
28009
+ defineConfig({
28010
+ enabled: bool({ default: false }),
28011
+ audience: enumOf([
28012
+ "all",
28013
+ "main",
28014
+ "subagent"
28015
+ ], { default: "all" }),
28016
+ textRepeatChars: int$2({
28017
+ min: 3,
28018
+ default: 4096
28019
+ }),
28020
+ reasoningRepeatChars: int$2({
28021
+ min: 3,
28022
+ default: 8192
28023
+ }),
28024
+ silentReasoningChars: int$2({
28025
+ min: 0,
28026
+ default: 0
28027
+ }),
28028
+ lineRepeatChars: int$2({
28029
+ min: 0,
28030
+ default: 0
28031
+ }),
28032
+ crossCallRepeatChars: int$2({
28033
+ min: 0,
28034
+ default: 0
28035
+ }),
28036
+ recoverEmpty: bool({ default: false }),
28037
+ mainRecoveryLimit: recoveryLimit,
28038
+ subagentRecoveryLimit: recoveryLimit,
28039
+ maintenanceCompression: bool({ default: false }),
28040
+ allowFullFallback: bool({ default: false }),
28041
+ maxCycleChars: passthrough((value, plugin, field) => {
28042
+ if (value === void 0) return 256;
28043
+ if (!Number.isSafeInteger(value) || value < 1 || value > 1024) throw new TypeError(`${plugin}: ${field} must be an integer from 1 to 1024`);
28044
+ return value;
28045
+ })
28046
+ });
28047
+ //#endregion
27017
28048
  //#region src/host/module-retrieval.ts
27018
28049
  /** 检索只读权威来源;引用携定位和版本,不携访问权限。 */
27019
28050
  const digest$1 = (value) => createHash("sha256").update(value).digest("hex");
27020
28051
  const MAX_CHUNK = MAX_REFERENCE_CHARS;
28052
+ const normalize = (text) => text.normalize("NFKC").toLowerCase().replace(/(?<![0-9,])[0-9]{1,3}(?:,[0-9]{3})+(?![0-9,])/g, (value) => value.replaceAll(",", ""));
28053
+ function includesQuery(text, query) {
28054
+ let start = text.indexOf(query);
28055
+ while (start >= 0) {
28056
+ if (!/^[0-9]+$/.test(query) || !/[0-9]/.test(text[start - 1] ?? "") && !/[0-9]/.test(text[start + query.length] ?? "")) return true;
28057
+ start = text.indexOf(query, start + 1);
28058
+ }
28059
+ return false;
28060
+ }
28061
+ function matchKind(text, query) {
28062
+ if (includesQuery(text.toLowerCase(), query.toLowerCase())) return "exact";
28063
+ if (includesQuery(normalize(text), normalize(query))) return "normalized";
28064
+ }
28065
+ function synonymTerms(scope, query) {
28066
+ if (!scope.synonymsFile) return { terms: [] };
28067
+ resolveModuleFilePath(scope.dir, scope.synonymsFile, ["assets", "data"]);
28068
+ const content = readModuleFile(scope.dir, scope.synonymsFile).content;
28069
+ if (Buffer.byteLength(content) > 65536) throw new Error("检索近义表超过64 KiB");
28070
+ let groups;
28071
+ try {
28072
+ groups = JSON.parse(content);
28073
+ } catch {
28074
+ throw new Error("检索近义表必须为JSON词组数组");
28075
+ }
28076
+ if (!Array.isArray(groups) || groups.length > 128 || groups.some((group) => !Array.isArray(group) || group.length < 2 || group.length > 16 || group.some((term) => typeof term !== "string" || !term.trim() || term.length > 128))) throw new Error("检索近义表词组无效或超限");
28077
+ const needle = normalize(query), terms = /* @__PURE__ */ new Set();
28078
+ for (const group of groups) if (group.some((term) => normalize(term) === needle)) {
28079
+ for (const term of group) if (normalize(term) !== needle) terms.add(term);
28080
+ }
28081
+ if (terms.size > 32) throw new Error("检索近义扩展超过32项");
28082
+ return {
28083
+ terms: [...terms],
28084
+ content
28085
+ };
28086
+ }
28087
+ function timeOf(value) {
28088
+ const time = typeof value === "number" ? value : typeof value === "string" && /^\d{4}-\d\d-\d\dT.*(?:Z|[+-]\d\d:\d\d)$/.test(value) ? Date.parse(value) : NaN;
28089
+ return Number.isSafeInteger(time) && time >= 0 && time <= 864e13 ? time : void 0;
28090
+ }
27021
28091
  function fileContent(scope, path) {
27022
28092
  if (!scope.paths.some((allowed) => allowed === path || allowed.endsWith("/") && path.startsWith(allowed))) throw new Error("检索来源未授权");
27023
28093
  resolveModuleFilePath(scope.dir, path, ["assets", "data"], { allowRootMemory: true });
@@ -27051,7 +28121,7 @@ function createModuleRetrieval(scopeOf, loadSession) {
27051
28121
  const indexes = /* @__PURE__ */ new Map();
27052
28122
  const assertSessionAuthorized = (scope, id) => {
27053
28123
  const current = scopeOf();
27054
- if (current.moduleId !== scope.moduleId || current.session?.id !== id && !current.sessions?.includes(id)) throw new Error("检索会话未授权");
28124
+ if (current.moduleId !== scope.moduleId || current.dir !== scope.dir || current.session?.id !== id && !current.sessions?.includes(id)) throw new Error("检索会话未授权");
27055
28125
  };
27056
28126
  async function authorizedSession(scope, id) {
27057
28127
  assertSessionAuthorized(scope, id);
@@ -27068,7 +28138,7 @@ function createModuleRetrieval(scopeOf, loadSession) {
27068
28138
  return session;
27069
28139
  }
27070
28140
  async function indexSession(scope, id) {
27071
- const events = [...(await authorizedSession(scope, id)).snapshotEvents()];
28141
+ const session = await authorizedSession(scope, id), events = [...session.snapshotEvents()];
27072
28142
  assertSessionAuthorized(scope, id);
27073
28143
  const prior = indexes.get(id);
27074
28144
  const appendOnly = prior !== void 0 && events.length >= prior.events.length && prior.events.every((event, i) => events[i] === event);
@@ -27088,7 +28158,8 @@ function createModuleRetrieval(scopeOf, loadSession) {
27088
28158
  });
27089
28159
  return {
27090
28160
  events,
27091
- rows
28161
+ rows,
28162
+ session
27092
28163
  };
27093
28164
  }
27094
28165
  const sessionIds = (scope) => /* @__PURE__ */ new Set([...scope.session ? [scope.session.id] : [], ...scope.sessions ?? []]);
@@ -27097,22 +28168,170 @@ function createModuleRetrieval(scopeOf, loadSession) {
27097
28168
  if (current.moduleId !== scope.moduleId || current.dir !== scope.dir) throw new Error("检索模块未授权");
27098
28169
  for (const { reference } of hits) if (reference.kind === "session") assertSessionAuthorized(current, reference.sessionId);
27099
28170
  else if (digest$1(fileContent(current, reference.path)) !== reference.revision) throw new Error("检索引用已失效,请重新搜索");
27100
- return { hits };
28171
+ const selected = [];
28172
+ let bytes = 0;
28173
+ for (const hit of hits) {
28174
+ bytes += Buffer.byteLength(JSON.stringify(hit));
28175
+ if (bytes > 65536) break;
28176
+ selected.push(hit);
28177
+ }
28178
+ return { hits: selected };
28179
+ };
28180
+ const timeline = async (input, limit) => {
28181
+ const scope = scopeOf(), hits = [], versions = [], revalidate = [];
28182
+ const since = input.since === void 0 ? void 0 : timeOf(input.since), until = input.until === void 0 ? void 0 : timeOf(input.until);
28183
+ if (input.since !== void 0 && since === void 0 || input.until !== void 0 && until === void 0 || since !== void 0 && until !== void 0 && since > until) throw new Error("时间线需要明确有效的since/until时间");
28184
+ let bytes = 0;
28185
+ const add = (reference, content, value) => {
28186
+ bytes += Buffer.byteLength(content);
28187
+ if (bytes > 67108864) throw new Error("时间线来源超过64 MiB");
28188
+ const time = timeOf(value);
28189
+ if ((since !== void 0 || until !== void 0) && (time === void 0 || since !== void 0 && time < since || until !== void 0 && time > until)) return;
28190
+ if (input.query !== void 0 && !matchKind(content, input.query)) return;
28191
+ hits.push({
28192
+ reference,
28193
+ content,
28194
+ time: time === void 0 ? null : new Date(time).toISOString(),
28195
+ timeState: time === void 0 ? "unknown" : "known"
28196
+ });
28197
+ };
28198
+ for (const path of filePaths(scope)) {
28199
+ if (!["02-soul.jsonl", "02-墨.jsonl"].includes(path.split("/").at(-1))) continue;
28200
+ const content = fileContent(scope, path), revision = digest$1(content);
28201
+ versions.push([
28202
+ "file",
28203
+ path,
28204
+ revision
28205
+ ]);
28206
+ revalidate.push(() => {
28207
+ if (digest$1(fileContent(scopeOf(), path)) !== revision) throw new Error("时间线来源已变化,请重新查询");
28208
+ });
28209
+ let offset = 0;
28210
+ for (const line of content.split("\n")) {
28211
+ if (line.trim()) {
28212
+ let row;
28213
+ try {
28214
+ row = JSON.parse(line);
28215
+ } catch {
28216
+ throw new Error("时间线账本含无效记录");
28217
+ }
28218
+ if (!row || typeof row !== "object" || Array.isArray(row)) throw new Error("时间线账本含无效记录");
28219
+ for (let part = 0; part < line.length; part += MAX_CHUNK) add({
28220
+ kind: "file",
28221
+ moduleId: scope.moduleId,
28222
+ path,
28223
+ revision,
28224
+ start: offset + part,
28225
+ end: offset + Math.min(line.length, part + MAX_CHUNK)
28226
+ }, line.slice(part, part + MAX_CHUNK), row.t);
28227
+ }
28228
+ offset += line.length + 1;
28229
+ }
28230
+ }
28231
+ for (const id of sessionIds(scope)) {
28232
+ const { events, rows, session } = await indexSession(scope, id);
28233
+ const revision = digest$1(JSON.stringify(events));
28234
+ versions.push([
28235
+ "session",
28236
+ id,
28237
+ revision
28238
+ ]);
28239
+ revalidate.push(() => {
28240
+ assertSessionAuthorized(scope, id);
28241
+ if (digest$1(JSON.stringify(session.snapshotEvents())) !== revision) throw new Error("时间线来源已变化,请重新查询");
28242
+ });
28243
+ const times = new Map(events.map((event) => [event.seq, event.time]));
28244
+ for (const row of rows) for (let start = 0; start < row.content.length; start += MAX_CHUNK) {
28245
+ const end = Math.min(row.content.length, start + MAX_CHUNK);
28246
+ add(makeSessionReference(scope.moduleId, id, events, [row.seq], {
28247
+ start,
28248
+ end
28249
+ }), row.content.slice(start, end), times.get(row.seq));
28250
+ }
28251
+ }
28252
+ resultOf(scope, hits);
28253
+ const current = scopeOf();
28254
+ if (JSON.stringify([
28255
+ current.moduleId,
28256
+ current.dir,
28257
+ current.paths,
28258
+ [...sessionIds(current)]
28259
+ ]) !== JSON.stringify([
28260
+ scope.moduleId,
28261
+ scope.dir,
28262
+ scope.paths,
28263
+ [...sessionIds(scope)]
28264
+ ])) throw new Error("时间线来源声明已变化");
28265
+ for (const check of revalidate) check();
28266
+ const sourceKey = (hit) => hit.reference.kind === "file" ? "0:" + hit.reference.path + ":" + String(hit.reference.start).padStart(12, "0") : "1:" + hit.reference.sessionId + ":" + String(hit.reference.seqs[0]).padStart(16, "0") + ":" + hit.reference.start;
28267
+ hits.sort((a, b) => (a.time === b.time ? 0 : a.time === null ? 1 : b.time === null ? -1 : a.time.localeCompare(b.time)) || sourceKey(a).localeCompare(sourceKey(b)));
28268
+ const version = digest$1(JSON.stringify([
28269
+ scope.moduleId,
28270
+ scope.dir,
28271
+ scope.paths,
28272
+ [...sessionIds(scope)],
28273
+ versions,
28274
+ input.query ?? null,
28275
+ since ?? null,
28276
+ until ?? null,
28277
+ limit
28278
+ ]));
28279
+ let offset = 0;
28280
+ if (input.cursor !== void 0) {
28281
+ let cursor;
28282
+ try {
28283
+ cursor = JSON.parse(Buffer.from(input.cursor, "base64url").toString("utf8"));
28284
+ } catch {
28285
+ throw new Error("时间线游标无效");
28286
+ }
28287
+ if (!cursor || Object.keys(cursor).sort().join(",") !== "offset,version" || cursor.version !== version || !Number.isSafeInteger(cursor.offset) || cursor.offset < 0 || cursor.offset >= hits.length) throw new Error("时间线游标已失效,请重新查询");
28288
+ offset = cursor.offset;
28289
+ }
28290
+ const selected = [];
28291
+ let outputBytes = 0;
28292
+ for (const hit of hits.slice(offset, offset + limit)) {
28293
+ outputBytes += Buffer.byteLength(JSON.stringify(hit));
28294
+ if (outputBytes > 65536) break;
28295
+ selected.push(hit);
28296
+ }
28297
+ const next = offset + selected.length;
28298
+ return {
28299
+ hits: selected,
28300
+ ...next < hits.length ? { nextCursor: Buffer.from(JSON.stringify({
28301
+ version,
28302
+ offset: next
28303
+ })).toString("base64url") } : {}
28304
+ };
27101
28305
  };
27102
28306
  return {
27103
28307
  async search(input) {
27104
- if (typeof input.query !== "string" || input.query.trim().length === 0 || input.query.length > 1024) throw new Error("检索 query 必须为1–1024字符");
28308
+ if (input.mode !== void 0 && input.mode !== "search" && input.mode !== "timeline") throw new Error("检索模式无效");
28309
+ if ((input.query !== void 0 || input.mode !== "timeline") && (typeof input.query !== "string" || input.query.trim().length === 0 || input.query.length > 1024)) throw new Error("检索 query 必须为1–1024字符");
27105
28310
  const limit = input.limit ?? 20;
27106
28311
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("检索 limit 必须为1–50");
27107
- const scope = scopeOf(), query = input.query.toLowerCase(), hits = [];
28312
+ if (input.cursor !== void 0 && (typeof input.cursor !== "string" || input.cursor.length > 2048)) throw new Error("时间线游标无效");
28313
+ if (input.mode === "timeline") return timeline(input, limit);
28314
+ if (input.since !== void 0 || input.until !== void 0 || input.cursor !== void 0) throw new Error("时间过滤和游标只用于时间线");
28315
+ const scope = scopeOf(), query = input.query, candidates = [];
28316
+ const synonyms = synonymTerms(scope, query);
28317
+ let candidateBytes = 0;
28318
+ const add = (hit) => {
28319
+ const match = matchKind(hit.content, query);
28320
+ if (!match && !scope.synonymsFile) return;
28321
+ candidateBytes += Buffer.byteLength(hit.content);
28322
+ if (candidateBytes > 67108864) throw new Error("检索候选超过64 MiB");
28323
+ candidates.push({
28324
+ ...hit,
28325
+ ...match ? { match } : {}
28326
+ });
28327
+ };
27108
28328
  for (const path of filePaths(scope)) {
27109
28329
  const content = fileContent(scope, path), revision = digest$1(content);
27110
28330
  let offset = 0;
27111
28331
  for (const line of content.split("\n")) {
27112
28332
  for (let part = 0; part < line.length; part += MAX_CHUNK) {
27113
28333
  const text = line.slice(part, part + MAX_CHUNK);
27114
- if (!text.toLowerCase().includes(query)) continue;
27115
- hits.push({
28334
+ add({
27116
28335
  reference: {
27117
28336
  kind: "file",
27118
28337
  moduleId: scope.moduleId,
@@ -27123,29 +28342,51 @@ function createModuleRetrieval(scopeOf, loadSession) {
27123
28342
  },
27124
28343
  content: text
27125
28344
  });
27126
- if (hits.length === limit) return resultOf(scope, hits);
28345
+ if (!scope.synonymsFile && candidates.length === limit) return resultOf(scope, candidates);
27127
28346
  }
27128
28347
  offset += line.length + 1;
27129
28348
  }
27130
28349
  }
27131
28350
  for (const id of sessionIds(scope)) {
27132
28351
  const { events, rows } = await indexSession(scope, id);
27133
- for (const row of rows) {
27134
- const match = row.content.toLowerCase().indexOf(query);
27135
- if (match < 0) continue;
27136
- const start = Math.max(0, match - 128), end = Math.min(row.content.length, start + MAX_CHUNK);
27137
- const reference = makeSessionReference(scope.moduleId, id, events, [row.seq], {
27138
- start,
27139
- end
27140
- });
27141
- hits.push({
27142
- reference,
27143
- content: row.content.slice(start, end)
28352
+ for (const row of rows) for (let start = 0; start < row.content.length; start += MAX_CHUNK) {
28353
+ const end = Math.min(row.content.length, start + MAX_CHUNK), text = row.content.slice(start, end);
28354
+ add({
28355
+ reference: makeSessionReference(scope.moduleId, id, events, [row.seq], {
28356
+ start,
28357
+ end
28358
+ }),
28359
+ content: text
27144
28360
  });
27145
- if (hits.length === limit) return resultOf(scope, hits);
28361
+ if (!scope.synonymsFile && candidates.length === limit) return resultOf(scope, candidates);
27146
28362
  }
27147
28363
  }
27148
- return resultOf(scope, hits);
28364
+ const hits = [];
28365
+ for (const candidate of candidates) {
28366
+ const match = matchKind(candidate.content, query);
28367
+ if (match) hits.push({
28368
+ ...candidate,
28369
+ match
28370
+ });
28371
+ }
28372
+ hits.sort((a, b) => Number(a.match === "normalized") - Number(b.match === "normalized"));
28373
+ if (hits.length < 3 && synonyms.terms.length) {
28374
+ for (const candidate of candidates) if (!matchKind(candidate.content, query) && synonyms.terms.some((term) => matchKind(candidate.content, term))) hits.push({
28375
+ ...candidate,
28376
+ match: "synonym"
28377
+ });
28378
+ }
28379
+ const current = scopeOf();
28380
+ if (current.synonymsFile !== scope.synonymsFile || scope.synonymsFile && readModuleFile(current.dir, scope.synonymsFile).content !== synonyms.content) throw new Error("检索近义表已变化,请重新搜索");
28381
+ const selected = [];
28382
+ let bytes = 0;
28383
+ for (const hit of hits.slice(0, limit)) {
28384
+ const size = Buffer.byteLength(JSON.stringify(hit));
28385
+ if (bytes + size > 65536) break;
28386
+ bytes += size;
28387
+ selected.push(hit);
28388
+ }
28389
+ return resultOf(scope, selected);
27149
28390
  },
27150
28391
  async open(reference) {
27151
28392
  const scope = scopeOf();
@@ -27215,10 +28456,14 @@ function registerModuleRetrievalTools(ctx, retrievalOf) {
27215
28456
  description: "搜索 moduleId 声明模块已授权的文件、账本或历史会话。结果中的 reference 可交 module_open 回取相同原文。",
27216
28457
  parameters: {
27217
28458
  moduleId: MODULE_ID_PARAM,
27218
- query: {
28459
+ query: { type: "string" },
28460
+ mode: {
27219
28461
  type: "string",
27220
- required: true
28462
+ enum: ["search", "timeline"]
27221
28463
  },
28464
+ since: { type: "string" },
28465
+ until: { type: "string" },
28466
+ cursor: { type: "string" },
27222
28467
  limit: { type: "number" }
27223
28468
  },
27224
28469
  output: {
@@ -27235,11 +28480,19 @@ function registerModuleRetrievalTools(ctx, retrievalOf) {
27235
28480
  if (Object.keys(args).some((key) => ![
27236
28481
  "moduleId",
27237
28482
  "query",
28483
+ "mode",
28484
+ "since",
28485
+ "until",
28486
+ "cursor",
27238
28487
  "limit"
27239
28488
  ].includes(key))) throw new Error("检索请求含未知字段");
27240
28489
  exec.signal?.throwIfAborted();
27241
28490
  const value = await retrievalOf(exec, args.moduleId).search({
27242
28491
  query: args.query,
28492
+ mode: args.mode,
28493
+ since: args.since,
28494
+ until: args.until,
28495
+ cursor: args.cursor,
27243
28496
  limit: args.limit
27244
28497
  });
27245
28498
  exec.signal?.throwIfAborted();
@@ -27285,6 +28538,15 @@ function registerModuleRetrievalTools(ctx, retrievalOf) {
27285
28538
  }
27286
28539
  function createModuleRetrievalProvider(host, loadSession) {
27287
28540
  const agents = /* @__PURE__ */ new WeakMap();
28541
+ const moduleStates = /* @__PURE__ */ new Map();
28542
+ const declarationOf = (state, moduleId) => [...state.modules.get(moduleId)?.values() ?? []].at(-1);
28543
+ const invalidate = (state, moduleId) => {
28544
+ const previous = state.observations.get(moduleId);
28545
+ state.observations.set(moduleId, {
28546
+ ...previous,
28547
+ state: previous ? "stale" : "unbuilt"
28548
+ });
28549
+ };
27288
28550
  const retrievalFor = (state, moduleId, agent) => {
27289
28551
  let retrieval = state.indexes.get(moduleId);
27290
28552
  if (retrieval === void 0) {
@@ -27296,6 +28558,7 @@ function createModuleRetrievalProvider(host, loadSession) {
27296
28558
  dir: current.dir,
27297
28559
  paths: current.config.paths,
27298
28560
  sessions: current.config.sessions,
28561
+ synonymsFile: current.config.synonymsFile,
27299
28562
  session: agent.session
27300
28563
  };
27301
28564
  }, loadSession);
@@ -27304,38 +28567,69 @@ function createModuleRetrievalProvider(host, loadSession) {
27304
28567
  return retrieval;
27305
28568
  };
27306
28569
  return {
28570
+ status(agent, moduleId) {
28571
+ const state = agents.get(agent);
28572
+ if (!state?.modules.has(moduleId)) return { state: "unauthorized" };
28573
+ return { ...state.observations.get(moduleId) };
28574
+ },
28575
+ invalidate(agent, moduleId) {
28576
+ const state = agents.get(agent);
28577
+ if (state?.modules.has(moduleId)) invalidate(state, moduleId);
28578
+ },
28579
+ invalidateModule(moduleId) {
28580
+ for (const state of moduleStates.get(moduleId) ?? []) invalidate(state, moduleId);
28581
+ },
27307
28582
  async refresh(agent, moduleId, signal) {
27308
28583
  signal?.throwIfAborted();
27309
28584
  const state = agents.get(agent);
27310
28585
  if (!state?.modules.has(moduleId)) throw new Error("检索模块未授权");
27311
- const result = await retrievalFor(state, moduleId, agent).refresh();
27312
- signal?.throwIfAborted();
27313
- if (!state.modules.has(moduleId)) throw new Error("检索模块未授权");
27314
- return result;
28586
+ const declaration = declarationOf(state, moduleId);
28587
+ const observation = {
28588
+ ...state.observations.get(moduleId),
28589
+ state: "refreshing"
28590
+ };
28591
+ state.observations.set(moduleId, observation);
28592
+ try {
28593
+ const result = await retrievalFor(state, moduleId, agent).refresh();
28594
+ signal?.throwIfAborted();
28595
+ if (agents.get(agent) !== state || declarationOf(state, moduleId) !== declaration) throw new Error("检索来源声明已变化或未授权");
28596
+ if (state.observations.get(moduleId) !== observation) throw new Error("检索来源状态已变化,请重新刷新");
28597
+ state.observations.set(moduleId, {
28598
+ ...result,
28599
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
28600
+ });
28601
+ return result;
28602
+ } catch (error) {
28603
+ if (agents.get(agent) === state && state.observations.get(moduleId) === observation) state.observations.set(moduleId, {
28604
+ ...observation,
28605
+ state: "failed"
28606
+ });
28607
+ throw error;
28608
+ }
27315
28609
  },
27316
28610
  mount(_ctx, config, options) {
27317
28611
  const { agent, sourceModuleId, sourceModuleDir } = options;
27318
28612
  if (!agent?.ctx || !sourceModuleId || !sourceModuleDir) throw new Error("检索缺少宿主来源模块身份");
27319
28613
  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("检索来源白名单无效或超限");
28614
+ if (config.synonymsFile !== void 0 && (typeof config.synonymsFile !== "string" || !config.synonymsFile || config.synonymsFile.length > 1024)) throw new Error("检索近义表路径无效");
27320
28615
  let state = agents.get(agent);
27321
28616
  if (state === void 0) {
27322
28617
  const modules = /* @__PURE__ */ new Map();
27323
28618
  const indexes = /* @__PURE__ */ new Map();
27324
- const dispose = registerModuleRetrievalTools(agent.ctx, (exec, moduleId) => {
27325
- if (exec.agent !== agent) throw new Error("检索 Agent 未授权");
27326
- const target = host.target(exec, moduleId);
27327
- const declaration = [...modules.get(target.id)?.values() ?? []].at(-1);
27328
- if (!declaration || declaration.dir !== target.dir) throw new Error("检索模块未授权");
27329
- return retrievalFor({
27330
- modules,
27331
- indexes,
27332
- dispose
27333
- }, target.id, agent);
27334
- });
27335
28619
  state = {
27336
28620
  modules,
27337
28621
  indexes,
27338
- dispose
28622
+ observations: /* @__PURE__ */ new Map(),
28623
+ dispose: registerModuleRetrievalTools(agent.ctx, (exec, moduleId) => {
28624
+ if (exec.agent !== agent) throw new Error("检索 Agent 未授权");
28625
+ const target = host.target(exec, moduleId);
28626
+ const declaration = [...modules.get(target.id)?.values() ?? []].at(-1);
28627
+ if (!declaration || declaration.dir !== target.dir) throw new Error("检索模块未授权");
28628
+ return retrievalFor({
28629
+ modules,
28630
+ indexes
28631
+ }, target.id, agent);
28632
+ })
27339
28633
  };
27340
28634
  agents.set(agent, state);
27341
28635
  }
@@ -27343,19 +28637,31 @@ function createModuleRetrievalProvider(host, loadSession) {
27343
28637
  if (declarations === void 0) {
27344
28638
  declarations = /* @__PURE__ */ new Map();
27345
28639
  state.modules.set(sourceModuleId, declarations);
28640
+ let members = moduleStates.get(sourceModuleId);
28641
+ if (!members) {
28642
+ members = /* @__PURE__ */ new Set();
28643
+ moduleStates.set(sourceModuleId, members);
28644
+ }
28645
+ members.add(state);
27346
28646
  }
27347
28647
  const token = Symbol(sourceModuleId);
27348
28648
  declarations.set(token, {
27349
28649
  config,
27350
28650
  dir: sourceModuleDir
27351
28651
  });
28652
+ invalidate(state, sourceModuleId);
27352
28653
  const owner = state;
27353
28654
  return () => {
28655
+ const wasCurrent = [...declarations.keys()].at(-1) === token;
27354
28656
  if (!declarations.delete(token)) return;
27355
28657
  if (declarations.size === 0) {
27356
28658
  owner.modules.delete(sourceModuleId);
27357
28659
  owner.indexes.delete(sourceModuleId);
27358
- }
28660
+ owner.observations.delete(sourceModuleId);
28661
+ const members = moduleStates.get(sourceModuleId);
28662
+ members?.delete(owner);
28663
+ if (!members?.size) moduleStates.delete(sourceModuleId);
28664
+ } else if (wasCurrent) invalidate(owner, sourceModuleId);
27359
28665
  if (owner.modules.size === 0) {
27360
28666
  owner.dispose();
27361
28667
  agents.delete(agent);
@@ -27366,6 +28672,19 @@ function createModuleRetrievalProvider(host, loadSession) {
27366
28672
  }
27367
28673
  //#endregion
27368
28674
  //#region src/runtime/soul-maintenance.ts
28675
+ async function waitForIdle(agent, signal) {
28676
+ signal.throwIfAborted();
28677
+ let aborted;
28678
+ try {
28679
+ await Promise.race([agent.whenIdle(), new Promise((_resolve, reject) => {
28680
+ aborted = () => reject(signal.reason);
28681
+ signal.addEventListener("abort", aborted, { once: true });
28682
+ if (signal.aborted) aborted();
28683
+ })]);
28684
+ } finally {
28685
+ signal.removeEventListener("abort", aborted);
28686
+ }
28687
+ }
27369
28688
  function createSoulMaintenanceProvider(options) {
27370
28689
  const states = /* @__PURE__ */ new Map();
27371
28690
  const status = (id) => {
@@ -27391,18 +28710,32 @@ function createSoulMaintenanceProvider(options) {
27391
28710
  state.running = Promise.resolve().then(async () => {
27392
28711
  try {
27393
28712
  if (!valid()) return;
27394
- await owner.agent.runMaintenance(async (activitySignal) => {
27395
- signal = AbortSignal.any([controller.signal, activitySignal]);
27396
- signal.throwIfAborted();
27397
- const revision = readSoulSelfSnapshot(owner.sourceModuleDir).revision;
27398
- const index = await options.refreshRetrieval(owner.agent, id, signal);
27399
- if (!valid()) return;
27400
- refreshSoulSelf(owner.sourceModuleDir, index, {
27401
- expectedRevision: revision,
27402
- signal,
27403
- budgetBytes: owner.config.budgetBytes
28713
+ state.state = "waiting";
28714
+ await waitForIdle(owner.agent, signal);
28715
+ if (!valid()) return;
28716
+ let entered = false;
28717
+ try {
28718
+ await owner.agent.runMaintenance(async (activitySignal) => {
28719
+ entered = true;
28720
+ state.state = "running";
28721
+ signal = AbortSignal.any([controller.signal, activitySignal]);
28722
+ signal.throwIfAborted();
28723
+ const revision = readSoulSelfSnapshot(owner.sourceModuleDir).revision;
28724
+ const index = await options.refreshRetrieval(owner.agent, id, signal);
28725
+ if (!valid()) return;
28726
+ refreshSoulSelf(owner.sourceModuleDir, index, {
28727
+ expectedRevision: revision,
28728
+ signal,
28729
+ budgetBytes: owner.config.budgetBytes
28730
+ });
27404
28731
  });
27405
- });
28732
+ } catch (error) {
28733
+ if (!entered && valid()) {
28734
+ state.state = "waiting";
28735
+ return;
28736
+ }
28737
+ throw error;
28738
+ }
27406
28739
  if (!valid()) return;
27407
28740
  state.state = "ready";
27408
28741
  if (owner.config.wake) owner.agent.followup(pluginMessage("soul-maintenance", "书房索引与 SELF 已维护。请核对当前未结事项;无需重复写入已有事实。", "书房维护心跳"));
@@ -27417,10 +28750,13 @@ function createSoulMaintenanceProvider(options) {
27417
28750
  if (state.state === "running") state.state = signal.aborted ? "cancelled" : "idle";
27418
28751
  if (states.get(id) === state && state.members.size) {
27419
28752
  const current = [...state.members].at(-1);
27420
- state.timer = setTimeout(() => {
27421
- tick(id);
27422
- }, current.config.intervalMs);
27423
- state.timer.unref();
28753
+ if (current !== owner && controller.signal.aborted) tick(id);
28754
+ else {
28755
+ state.timer = setTimeout(() => {
28756
+ tick(id);
28757
+ }, current.config.intervalMs);
28758
+ state.timer.unref();
28759
+ }
27424
28760
  }
27425
28761
  }
27426
28762
  });
@@ -27473,6 +28809,31 @@ function createSoulMaintenanceProvider(options) {
27473
28809
  //#region src/runtime/soul-runtime.ts
27474
28810
  /** Soul宿主接线:历史只读句柄、检索授权与SELF/维护共享owner。 */
27475
28811
  function installSoulRuntime(ctx, host, moduleIds) {
28812
+ let active = true;
28813
+ const rendered = /* @__PURE__ */ new Map();
28814
+ const digest = (text) => createHash("sha256").update(text).digest("hex");
28815
+ const delivered = /* @__PURE__ */ new Map();
28816
+ const source = (id, agent, signal) => {
28817
+ signal?.throwIfAborted();
28818
+ if (!active || !agent?.session?.id || !moduleIds(agent.id).includes(id)) throw new Error("SELF来源模块未参与本Agent装配");
28819
+ return assertModuleSnapshotSource(host.root, id);
28820
+ };
28821
+ const keyOf = (id, agent) => JSON.stringify([id, agent.session.id]);
28822
+ const activityOf = (agent) => {
28823
+ const events = agent.session.snapshotEvents();
28824
+ for (let i = events.length - 1; i >= 0; i--) {
28825
+ const event = events[i];
28826
+ if ([
28827
+ "user/message",
28828
+ "assistant/message",
28829
+ "tool/call",
28830
+ "tool/result",
28831
+ "step/start",
28832
+ "turn/end"
28833
+ ].includes(event.type) && Number.isFinite(event.time)) return new Date(event.time).toISOString();
28834
+ }
28835
+ return null;
28836
+ };
27476
28837
  const budgets = /* @__PURE__ */ new Map();
27477
28838
  const readSelfBudget = (dir) => {
27478
28839
  const stat = statSync(join(dir, "module.yml"), { throwIfNoEntry: false });
@@ -27509,18 +28870,134 @@ function installSoulRuntime(ctx, host, moduleIds) {
27509
28870
  }
27510
28871
  });
27511
28872
  const maintenance = createSoulMaintenanceProvider({ refreshRetrieval: (agent, id, signal) => retrieval.refresh(agent, id, signal) });
28873
+ const reminder = createSoulReminder((id, agent) => readSoulLedgerProjection(source(id, agent)));
28874
+ const pressure = (dir, agent) => {
28875
+ try {
28876
+ const thresholds = readModuleFacts(dir).effectiveConfigs?.["soul-maintenance"]?.pressureThresholds;
28877
+ return Array.isArray(thresholds) ? readSoulPressure(ctx, agent, thresholds) : { state: "disabled" };
28878
+ } catch {
28879
+ return {
28880
+ state: "unknown",
28881
+ reason: "水位配置或会话观测不可用"
28882
+ };
28883
+ }
28884
+ };
27512
28885
  ctx.provide(toolServiceName("retrieval"), retrieval);
27513
28886
  ctx.provide(SOUL_MAINTENANCE_SERVICE, maintenance);
27514
- ctx.provide(SOUL_SELF_SERVICE, { render: (id, agent, signal) => {
27515
- signal?.throwIfAborted();
27516
- if (!moduleIds(agent.id).includes(id)) throw new Error("SELF来源模块未参与本Agent装配");
27517
- const dir = assertModuleSnapshotSource(host.root, id);
27518
- return renderSoulSelf(dir, agent, signal, readSelfBudget(dir));
27519
- } });
28887
+ const status = (id, agent) => {
28888
+ const dir = source(id, agent), ledger = readSoulLedgerProjection(dir), state = readSoulState(dir, agent.session.id);
28889
+ return {
28890
+ clock: (/* @__PURE__ */ new Date()).toISOString(),
28891
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
28892
+ sessionId: agent.session.id,
28893
+ revision: state.revision,
28894
+ mark: state.mark ? { ...state.mark } : null,
28895
+ activityAt: activityOf(agent),
28896
+ ledger: {
28897
+ total: ledger.total,
28898
+ open: ledger.open,
28899
+ pendingReview: ledger.pendingReview,
28900
+ initialized: ledger.initialized,
28901
+ pendingVersion: ledger.pendingVersion
28902
+ },
28903
+ index: { ...retrieval.status(agent, id) },
28904
+ pressure: pressure(dir, agent),
28905
+ renderedAt: rendered.get(keyOf(id, agent))?.at ?? null,
28906
+ deliveredAt: delivered.get(keyOf(id, agent)) ?? null
28907
+ };
28908
+ };
28909
+ ctx.provide(SOUL_SELF_SERVICE, {
28910
+ status,
28911
+ reminder,
28912
+ committed: (id, agent) => {
28913
+ source(id, agent);
28914
+ retrieval.invalidateModule(id);
28915
+ maintenance.tick(id);
28916
+ return { state: maintenance.status(id).state === "disabled" ? "stale-maintenance-disabled" : "refresh-pending" };
28917
+ },
28918
+ render: (id, agent, signal) => {
28919
+ const dir = source(id, agent, signal), budget = readSelfBudget(dir);
28920
+ return Promise.resolve().then(() => {
28921
+ if (source(id, agent, signal) !== dir) throw new Error("SELF来源发生变化");
28922
+ const state = readSoulState(dir, agent.session.id);
28923
+ const water = pressure(dir, agent);
28924
+ const text = renderSoulSelf(dir, agent, signal, budget, retrieval.status(agent, id), [
28925
+ `- 显式状态:${state.mark?.text ?? "尚未标记"};标记时刻:${state.mark?.recordedAt ?? "无"}`,
28926
+ `- 实际活动时刻:${activityOf(agent) ?? "未观测"}`,
28927
+ `- SELF实际投递观测:${delivered.get(keyOf(id, agent)) ?? "尚未观测"}`,
28928
+ ...water.state === "disabled" ? [] : [water.state === "known" ? `- 上下文水位:${(water.ratio * 100).toFixed(1)}%(${water.tokens}/${water.capacity};${water.source});${water.guidance}。此为提示,不自动压缩。` : `- 上下文水位:未知;${water.reason}`]
28929
+ ]);
28930
+ source(id, agent, signal);
28931
+ if (rendered.size > 1024) rendered.delete(rendered.keys().next().value);
28932
+ const key = keyOf(id, agent), candidates = [...rendered.get(key)?.candidates ?? [], digest(text)].slice(-16);
28933
+ rendered.set(key, {
28934
+ at: (/* @__PURE__ */ new Date()).toISOString(),
28935
+ candidates,
28936
+ sessionId: agent.session.id
28937
+ });
28938
+ return text;
28939
+ });
28940
+ }
28941
+ });
28942
+ ctx.effect(() => ctx.on("session/event", (session, event) => {
28943
+ const observed = event.data.message?.source;
28944
+ if (observed?.kind !== "runtime-context" || observed.form !== "snapshot") return;
28945
+ for (const [key, candidate] of rendered) if (candidate.sessionId === session.id && observed.sections?.some((section) => typeof section.text === "string" && candidate.candidates.includes(digest(section.text)))) {
28946
+ if (delivered.size > 1024) delivered.delete(delivered.keys().next().value);
28947
+ delivered.set(key, new Date(event.time).toISOString());
28948
+ }
28949
+ }));
27520
28950
  ctx.effect(() => () => {
28951
+ active = false;
27521
28952
  maintenance.dispose();
27522
28953
  budgets.clear();
28954
+ rendered.clear();
28955
+ delivered.clear();
27523
28956
  });
28957
+ const observe = (exec, id) => {
28958
+ if (!exec.agent) throw new Error("Soul状态缺少实际Agent");
28959
+ return status(id, exec.agent);
28960
+ };
28961
+ return {
28962
+ status: observe,
28963
+ check: async (exec, id, includeUpgrade) => {
28964
+ if (!exec.agent) throw new Error("Soul状态缺少实际Agent");
28965
+ const agent = exec.agent, dir = source(id, agent), key = keyOf(id, agent);
28966
+ const diagnostics = readSoulDiagnostics(dir, {
28967
+ index: retrieval.status(agent, id),
28968
+ selfRenderedAt: rendered.get(key)?.at,
28969
+ selfDeliveredAt: delivered.get(key),
28970
+ maintenance: maintenance.status(id),
28971
+ context: contextRuntimeOf(agent)?.status(),
28972
+ recovery: recoveryObservationOf(agent)
28973
+ });
28974
+ const sessionId = agent.session.id;
28975
+ const upgrade = includeUpgrade ? await readSoulUpgradePreview(dir) : { status: "not-applicable" };
28976
+ if (source(id, agent, exec.signal) !== dir || agent.session.id !== sessionId) throw new Error("Soul诊断来源或会话已变化");
28977
+ return {
28978
+ ...diagnostics,
28979
+ checks: diagnostics.checks.map((check) => ({ ...check })),
28980
+ upgrade: upgrade.status === "not-applicable" ? {
28981
+ status: includeUpgrade ? upgrade.status : "not-requested",
28982
+ expectedRevision: null,
28983
+ conflicts: [],
28984
+ candidate: null,
28985
+ files: [],
28986
+ apply: null
28987
+ } : {
28988
+ status: upgrade.status,
28989
+ expectedRevision: upgrade.expectedRevision,
28990
+ conflicts: upgrade.conflicts,
28991
+ candidate: upgrade.candidate,
28992
+ files: upgrade.files.map((file) => ({ ...file })),
28993
+ apply: upgrade.apply
28994
+ }
28995
+ };
28996
+ }
28997
+ };
28998
+ }
28999
+ function soulReminderOf(ctx) {
29000
+ return ctx.get(SOUL_SELF_SERVICE)?.reminder;
27524
29001
  }
27525
29002
  //#endregion
27526
29003
  //#region engine/promotion-pool.mjs
@@ -28979,32 +30456,6 @@ function createGenerationRecoveryState(root = join(storageRoot(), "generation-re
28979
30456
  };
28980
30457
  }
28981
30458
  //#endregion
28982
- //#region engine/context-provenance.mjs
28983
- const configObject = passthrough((value, plugin, field) => {
28984
- if (value === void 0) return {};
28985
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${plugin}: ${field} must be an object`);
28986
- return value;
28987
- });
28988
- defineConfig({
28989
- enabled: bool({ default: false }),
28990
- audience: enumOf([
28991
- "all",
28992
- "main",
28993
- "subagent"
28994
- ], { default: "all" }),
28995
- auto: bool({ default: true }),
28996
- modelSwitch: bool({ default: true }),
28997
- prune: bool({ default: true }),
28998
- basic: configObject,
28999
- pruner: configObject,
29000
- recoveryRetainTokens: int$2({
29001
- min: 0,
29002
- default: 1024
29003
- })
29004
- });
29005
- const runtimes = /* @__PURE__ */ new WeakMap();
29006
- const contextRuntimeOf = (agent) => runtimes.get(agent);
29007
- //#endregion
29008
30459
  //#region engine/rule-runtime.mjs
29009
30460
  const noNext = /* @__PURE__ */ new Set([
29010
30461
  "agent/inbox/inserted",
@@ -29095,7 +30546,8 @@ function mountSources(ctx, sources, options = {}) {
29095
30546
  standingMountFor: options.standingMountFor,
29096
30547
  promotionPool: pool,
29097
30548
  variableStore: options.variableStore,
29098
- manifestCheck: options.manifestCheck
30549
+ manifestCheck: options.manifestCheck,
30550
+ soulReminder: options.soulReminder
29099
30551
  });
29100
30552
  } catch (error) {
29101
30553
  throw sourceError(error, source.moduleId);
@@ -29778,7 +31230,8 @@ function createAgentAssembly(ctx, options) {
29778
31230
  onOutcome: ruleDiagnostics.record,
29779
31231
  variableStore,
29780
31232
  standingMountFor: await loadStandingMountFor(),
29781
- manifestCheck
31233
+ manifestCheck,
31234
+ soulReminder: soulReminderOf(ctx)
29782
31235
  });
29783
31236
  } catch (error) {
29784
31237
  await root.dispose();
@@ -30488,7 +31941,7 @@ function apply(ctx, configIn) {
30488
31941
  target: (exec, moduleId) => resolveModuleToolTarget(exec, modulesDir(), (sessionId) => assembly?.moduleIds(sessionId) ?? [], moduleId),
30489
31942
  rebuild: (id) => refreshModuleRuntime(id)
30490
31943
  };
30491
- installSoulRuntime(ctx, moduleToolHost, (id) => assembly?.moduleIds(id) ?? []);
31944
+ const soulRuntime = installSoulRuntime(ctx, moduleToolHost, (id) => assembly?.moduleIds(id) ?? []);
30492
31945
  ctx.provide(toolServiceName("module-import-tools"), { mount: (scopeCtx) => registerModuleImportTools(scopeCtx, {
30493
31946
  ...moduleToolHost,
30494
31947
  rebuild: async (id) => {
@@ -30498,7 +31951,19 @@ function apply(ctx, configIn) {
30498
31951
  ctx.provide(toolServiceName("module-rule-tools"), { mount: (scopeCtx) => registerModuleRuleTools(scopeCtx, moduleToolHost) });
30499
31952
  ctx.provide(toolServiceName("module-file-tools"), { mount: (scopeCtx) => registerModuleFileTools(scopeCtx, moduleToolHost) });
30500
31953
  ctx.provide(toolServiceName("module-manifest-tools"), { mount: (scopeCtx) => registerModuleManifestTools(scopeCtx, moduleToolHost) });
30501
- ctx.provide(toolServiceName("soul-ledger"), { mount: (scopeCtx) => registerSoulLedgerTools(scopeCtx, moduleToolHost) });
31954
+ ctx.provide(toolServiceName("soul-ledger"), { mount: (scopeCtx) => {
31955
+ const ledger = registerSoulLedgerTools(scopeCtx, moduleToolHost);
31956
+ try {
31957
+ const state = registerSoulStateTools(scopeCtx, moduleToolHost, soulRuntime);
31958
+ return () => {
31959
+ state();
31960
+ ledger();
31961
+ };
31962
+ } catch (error) {
31963
+ ledger();
31964
+ throw error;
31965
+ }
31966
+ } });
30502
31967
  ctx.provide(DISCOVERY_SERVICE, createDiscoveryRegistry());
30503
31968
  ctx.provide(MANIFEST_CHECK_SERVICE, createManifestCheckReader(modulesDir()));
30504
31969
  ctx.provide(toolServiceName("module-variable-tools"), { mount: (scopeCtx) => registerModuleVariableTools(scopeCtx, moduleToolHost) });