dsh-plugin-prompt-tool 3.1.4 → 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/README.md +9 -3
- package/engine/compaction-epoch.d.mts +1 -0
- package/engine/conditions/composite.mjs +6 -1
- package/engine/conditions/evaluation.mjs +22 -3
- package/engine/conditions/examples.mjs +1 -0
- package/engine/conditions/index.mjs +4 -0
- package/engine/conditions/soul.mjs +15 -0
- package/engine/delivery.d.mts +1 -0
- package/engine/executor.mjs +4 -1
- package/engine/fillers.mjs +2 -1
- package/engine/generation-recovery.d.mts +6 -0
- package/engine/generation-recovery.mjs +53 -4
- package/engine/layers.mjs +7 -2
- package/engine/retrieval-reference.d.mts +1 -1
- package/engine/retrieval.mjs +2 -1
- package/engine/rule-runtime.d.mts +4 -0
- package/engine/rule-runtime.mjs +3 -2
- package/engine/rule-spec.mjs +7 -1
- package/engine/soul-maintenance.mjs +5 -0
- package/lib/client.js +2 -0
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +1631 -166
- package/modules/soul/assets/ledger.md +4 -0
- package/modules/soul/assets/retrieval-synonyms.json +8 -0
- package/modules/soul/assets/tools.md +4 -0
- package/modules/soul/module.yml +27 -21
- package/package.json +1 -1
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
|
-
|
|
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))
|
|
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 (
|
|
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)
|
|
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")
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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: "
|
|
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: [
|
|
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
|
|
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
|
|
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
|
|
26938
|
-
|
|
27382
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27383
|
+
const lines = [
|
|
27384
|
+
seal(source),
|
|
26939
27385
|
"",
|
|
26940
|
-
"##
|
|
26941
|
-
`-
|
|
26942
|
-
`- 账本:共 ${ledger.total} 条;未结 ${
|
|
26943
|
-
|
|
26944
|
-
|
|
26945
|
-
`-
|
|
26946
|
-
|
|
26947
|
-
|
|
26948
|
-
|
|
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
|
-
|
|
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
|
|
26980
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
27135
|
-
|
|
27136
|
-
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
|
|
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 (
|
|
28361
|
+
if (!scope.synonymsFile && candidates.length === limit) return resultOf(scope, candidates);
|
|
27146
28362
|
}
|
|
27147
28363
|
}
|
|
27148
|
-
|
|
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
|
-
|
|
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
|
|
27312
|
-
|
|
27313
|
-
|
|
27314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
|
|
27398
|
-
|
|
27399
|
-
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
signal,
|
|
27403
|
-
|
|
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
|
-
|
|
27421
|
-
|
|
27422
|
-
|
|
27423
|
-
|
|
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
|
-
|
|
27515
|
-
|
|
27516
|
-
|
|
27517
|
-
|
|
27518
|
-
|
|
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) =>
|
|
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) });
|