dsh-plugin-prompt-tool 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.mjs CHANGED
@@ -10475,7 +10475,7 @@ function officialAliasOf(name) {
10475
10475
  * 非法官方名(中文/大写/连字符)改写成确定性别名并改写文本引用;注册失败的名字退回静态解析。
10476
10476
  * @returns 各配置独立的引用白名单与别名;同一作用域内等价绑定只注册一次。
10477
10477
  */
10478
- function registerOfficialVariables(ctx, configs, warnOnce, keep) {
10478
+ function registerOfficialVariables(ctx, configs, warnOnce, keep, shared) {
10479
10479
  const registries = new Map(configs.map((config) => [config, {
10480
10480
  protect: /* @__PURE__ */ new Set(),
10481
10481
  alias: /* @__PURE__ */ new Map(),
@@ -10497,8 +10497,9 @@ function registerOfficialVariables(ctx, configs, warnOnce, keep) {
10497
10497
  }
10498
10498
  }
10499
10499
  for (const fact of facts) reserved.add(fact);
10500
- const bindings = /* @__PURE__ */ new Map();
10501
- const usedNames = /* @__PURE__ */ new Set();
10500
+ const bindings = shared?.bindings ?? /* @__PURE__ */ new Map();
10501
+ const usedNames = shared?.usedNames ?? /* @__PURE__ */ new Set();
10502
+ const retained = /* @__PURE__ */ new Set();
10502
10503
  for (const [config, referenced] of references) {
10503
10504
  const registry = registries.get(config);
10504
10505
  for (const name of referenced) {
@@ -10522,20 +10523,34 @@ function registerOfficialVariables(ctx, configs, warnOnce, keep) {
10522
10523
  usedNames.add(official);
10523
10524
  const source = isFact ? name.toLowerCase() : name;
10524
10525
  const variablesDisabled = config[SESSION_VARIABLES_DISABLED] === true;
10525
- keep(systemPrompt.variable(official, (context) => {
10526
+ const release = systemPrompt.variable(official, (context) => {
10526
10527
  const session = context?.agent?.session;
10527
10528
  const variables = variablesDisabled ? {} : config.variableStore?.read(config.sourceModuleId).effective ?? config.variables;
10528
10529
  return officialChannelText(interpolateVariables(Object.hasOwn(variables, source) ? String(variables[source]) : runtimeFactValue(source, session) ?? "", variables, session, void 0, `variable:${config.sourceModuleId}:${config.id}:${name}`), `variable ${official}`, {
10529
10530
  alias: /* @__PURE__ */ new Map(),
10530
10531
  registered: /* @__PURE__ */ new Set()
10531
10532
  }, warnOnce);
10532
- }), `${name}: official prompt variable ${official}`);
10533
- binding = official;
10533
+ });
10534
+ binding = {
10535
+ name: official,
10536
+ release,
10537
+ refs: 0
10538
+ };
10534
10539
  bindings.set(signature, binding);
10535
10540
  }
10536
- registry.registered.add(binding);
10541
+ if (!retained.has(signature)) {
10542
+ retained.add(signature);
10543
+ binding.refs++;
10544
+ keep(() => {
10545
+ if (--binding.refs > 0) return;
10546
+ bindings.delete(signature);
10547
+ usedNames.delete(binding.name);
10548
+ binding.release?.();
10549
+ }, `${name}: official prompt variable ${binding.name}`);
10550
+ }
10551
+ registry.registered.add(binding.name);
10537
10552
  registry.protect.add(name);
10538
- if (binding !== name) registry.alias.set(name, binding);
10553
+ if (binding.name !== name) registry.alias.set(name, binding.name);
10539
10554
  } catch (error) {
10540
10555
  warnOnce(`${name}: 官方 prompt 变量 ${official} 注册失败,该引用退回静态解析:${String(error?.message ?? error)}`);
10541
10556
  }
@@ -10575,7 +10590,7 @@ function textLayerGroups(configs) {
10575
10590
  for (const config of sorted) {
10576
10591
  const previous = groups.at(-1);
10577
10592
  const base = previous?.[0];
10578
- if (config.mergeMode === "merged" && base?.mergeMode === "merged" && config.sourceModuleId === base.sourceModuleId && config.params?.complete === true === (base.params?.complete === true) && config.params?.suppressRuntimeContext === true === (base.params?.suppressRuntimeContext === true) && config.position === base.position && config.order === base.order) previous.push(config);
10593
+ if (config.mergeMode === "merged" && base?.mergeMode === "merged" && config.sourceModuleId === base.sourceModuleId && config.params?.complete === true === (base.params?.complete === true) && config.params?.suppressRuntimeContext === true === (base.params?.suppressRuntimeContext === true) && config.runtimePoint?.waterfallPosition === base.runtimePoint?.waterfallPosition && config.position === base.position && config.order === base.order) previous.push(config);
10579
10594
  else groups.push([config]);
10580
10595
  }
10581
10596
  return groups;
@@ -11059,9 +11074,9 @@ function orderedNamesOf(entries) {
11059
11074
  * 回填到它们原来的槽位。只换本插件段之间的位置:平台段与其他插件段占住的槽位不动。
11060
11075
  */
11061
11076
  function registerDeliveryOrder(ctx, keep, collection, names) {
11062
- if (names.length < 2) return;
11063
- const rank = new Map(names.map((entryName, index) => [entryName, index]));
11077
+ if (typeof names !== "function" && names.length < 2) return;
11064
11078
  keep(ctx.on("system-prompt/assemble", async (assembly, context, next) => {
11079
+ const rank = new Map((typeof names === "function" ? names() : names).map((entryName, index) => [entryName, index]));
11065
11080
  const result = await next();
11066
11081
  const list = result?.[collection];
11067
11082
  if (!Array.isArray(list)) return result;
@@ -11117,11 +11132,11 @@ function wireLayers(ctx, configs, warnOnce, options = {}) {
11117
11132
  };
11118
11133
  try {
11119
11134
  /** 实际注册名与序号:重排器只认这里,不从 configs 反推。 */
11120
- const ordered = {
11135
+ const ordered = options.ordered ?? {
11121
11136
  sections: [],
11122
11137
  contexts: []
11123
11138
  };
11124
- const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === "system-section" || config.layer === "runtime-context"), warnOnce, keep);
11139
+ const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === "system-section" || config.layer === "runtime-context"), warnOnce, keep, options.sharedVariables);
11125
11140
  const conditional = configs.filter((config) => ["system-section", "runtime-context"].includes(config.layer) && (typeof config.rule?.when === "function" || typeof config.actionWhen === "function" || (config.rule !== void 0 || config.params?.templateEvaluation === true) && config.params?.complete !== true && config.params?.suppressRuntimeContext !== true));
11126
11141
  const regular = configs.filter((config) => !conditional.includes(config));
11127
11142
  wireSystemSections(ctx, regular.filter((config) => config.layer === "system-section"), registry, warnOnce, keep, ordered, options.sourceError);
@@ -11129,13 +11144,113 @@ function wireLayers(ctx, configs, warnOnce, options = {}) {
11129
11144
  wireRuleTextContributions(ctx, conditional, registry, warnOnce, keep, on, ordered, options.sourceError);
11130
11145
  wireTurnStops(ctx, configs.filter((config) => config.layer === "turn-stop"), warnOnce, on, options.turnStopBudgets);
11131
11146
  wireSubagentEvents(ctx, configs.filter((config) => config.layer === "subagent-start" || config.layer === "subagent-end"), warnOnce, on);
11132
- for (const collection of ["sections", "contexts"]) registerDeliveryOrder(ctx, keep, collection, orderedNamesOf(ordered[collection]));
11147
+ for (const collection of options.deliveryOrder === false ? [] : ["sections", "contexts"]) registerDeliveryOrder(ctx, keep, collection, orderedNamesOf(ordered[collection]));
11133
11148
  return dispose;
11134
11149
  } catch (error) {
11135
11150
  dispose();
11136
11151
  throw error;
11137
11152
  }
11138
11153
  }
11154
+ /** 按实际文本组保留注册;共享排序投影变化不撤销未变化组的 provider。 */
11155
+ function createLayerRuntime(ctx, warnOnce, options = {}) {
11156
+ let records = [];
11157
+ let active = true;
11158
+ const listeners = [];
11159
+ const sharedVariables = {
11160
+ bindings: /* @__PURE__ */ new Map(),
11161
+ usedNames: /* @__PURE__ */ new Set()
11162
+ };
11163
+ const keep = (release) => {
11164
+ listeners.push(release);
11165
+ };
11166
+ try {
11167
+ for (const collection of ["sections", "contexts"]) registerDeliveryOrder(ctx, keep, collection, () => orderedNamesOf(records.flatMap((record) => record.ordered[collection])));
11168
+ } catch (error) {
11169
+ for (const release of listeners.reverse()) release();
11170
+ throw error;
11171
+ }
11172
+ const groupsOf = (configs) => {
11173
+ const groups = [];
11174
+ for (const layer of ["system-section", "runtime-context"]) {
11175
+ const selected = configs.filter((config) => config.layer === layer);
11176
+ const conditional = selected.filter((config) => typeof config.rule?.when === "function" || typeof config.actionWhen === "function" || (config.rule !== void 0 || config.params?.templateEvaluation === true) && config.params?.complete !== true && config.params?.suppressRuntimeContext !== true);
11177
+ const conditionalSet = new Set(conditional);
11178
+ const regular = selected.filter((config) => !conditionalSet.has(config));
11179
+ groups.push(...textLayerGroups(conditional));
11180
+ if (layer === "system-section") groups.push(...textLayerGroups(regular));
11181
+ else {
11182
+ const resolver = regular.filter((config) => config.strategy === "placeholder" || !KNOWN_STRATEGIES.has(config.strategy));
11183
+ const resolverSet = new Set(resolver);
11184
+ groups.push(...textLayerGroups(regular.filter((config) => !resolverSet.has(config))), ...resolver.map((config) => [config]));
11185
+ }
11186
+ }
11187
+ const other = configs.filter((config) => !["system-section", "runtime-context"].includes(config.layer));
11188
+ return [...groups, ...other.map((config) => [config])];
11189
+ };
11190
+ const mount = (configs) => {
11191
+ const ordered = {
11192
+ sections: [],
11193
+ contexts: []
11194
+ };
11195
+ const handlers = [];
11196
+ return {
11197
+ configs,
11198
+ ordered,
11199
+ handlers,
11200
+ dispose: wireLayers(ctx, configs, warnOnce, {
11201
+ ...options,
11202
+ sharedVariables,
11203
+ ordered,
11204
+ deliveryOrder: false,
11205
+ on(channel, handler) {
11206
+ handlers.push({
11207
+ channel,
11208
+ handler,
11209
+ config: configs[0]
11210
+ });
11211
+ return () => {};
11212
+ }
11213
+ })
11214
+ };
11215
+ };
11216
+ return {
11217
+ replace(configs) {
11218
+ if (!active) throw new Error("rule text runtime disposed");
11219
+ const groups = groupsOf(configs);
11220
+ const byFirst = new Map(records.map((record) => [record.configs[0], record]));
11221
+ const retained = groups.map((group) => {
11222
+ const record = byFirst.get(group[0]);
11223
+ return record?.configs.length === group.length && group.every((config, index) => record.configs[index] === config) ? record : void 0;
11224
+ });
11225
+ const retainedSet = new Set(retained);
11226
+ const removed = records.filter((record) => !retainedSet.has(record));
11227
+ for (const record of removed) record.dispose();
11228
+ const created = [];
11229
+ try {
11230
+ records = groups.map((group, index) => {
11231
+ if (retained[index] !== void 0) return retained[index];
11232
+ const record = mount(group);
11233
+ created.push(record);
11234
+ return record;
11235
+ });
11236
+ const owners = new Set(configs.map((config) => config.sourceModuleId ?? ""));
11237
+ for (const owner of options.turnStopBudgets?.keys() ?? []) if (!owners.has(owner)) options.turnStopBudgets.delete(owner);
11238
+ } catch (error) {
11239
+ for (const record of created.reverse()) record.dispose();
11240
+ records = records.filter((record) => !removed.includes(record));
11241
+ for (const record of removed) records.push(mount(record.configs));
11242
+ throw error;
11243
+ }
11244
+ },
11245
+ entries: () => records.flatMap((record) => record.handlers),
11246
+ dispose() {
11247
+ if (!active) return;
11248
+ active = false;
11249
+ for (const record of records.splice(0).reverse()) record.dispose();
11250
+ for (const release of listeners.splice(0).reverse()) release();
11251
+ }
11252
+ };
11253
+ }
11139
11254
  //#endregion
11140
11255
  //#region engine/anchor-match.mjs
11141
11256
  /**
@@ -20017,6 +20132,14 @@ function createBridgeRuntime(options) {
20017
20132
  };
20018
20133
  const runOverridesChange = (moduleId) => finishModuleChange(() => deps.overridesChanged?.(moduleId));
20019
20134
  const refreshModuleList = () => finishModuleChange(() => deps.moduleListChanged?.());
20135
+ const schedulePublication = async (moduleId, reason) => {
20136
+ const schedule = deps.schedulePublication;
20137
+ if (schedule !== void 0) {
20138
+ const result = await finishModuleChange(() => schedule(moduleId, reason));
20139
+ return result.publicationPending ? result : { persisted: true };
20140
+ }
20141
+ return reason === "installed" ? finishModuleChange(() => deps.moduleInstalled?.(moduleId)) : reason === "capability" ? finishModuleChange(() => deps.capabilityChanged?.(moduleId)) : reason === "list" ? refreshModuleList() : runOverridesChange(moduleId);
20142
+ };
20020
20143
  const writeModuleOrderError = (write, error) => {
20021
20144
  write(error instanceof ModuleConfigOrderError ? error.status : 500, {
20022
20145
  ok: false,
@@ -20121,6 +20244,8 @@ function createBridgeRuntime(options) {
20121
20244
  const globalLayerError = readGlobalLayerSettingsSafe(userModulesDir()).error;
20122
20245
  if (globalLayerError !== void 0) warnings.push(`全局层 layerSettings 解析失败,已按「无全局层」继续(指令卡的全局开关暂不可用):${globalLayerError}`);
20123
20246
  if (warnings.length > 0) meta.moduleWarnings = warnings;
20247
+ const publicationFailures = deps.publicationFailures?.();
20248
+ if (publicationFailures !== void 0 && publicationFailures.length > 0) meta.publicationFailures = publicationFailures;
20124
20249
  meta.editorGroups = ENGINE_EDITOR_GROUP_MAP.map(({ id, displayLayer, relatedLayers, hook }) => ({
20125
20250
  id,
20126
20251
  displayLayer,
@@ -20258,7 +20383,7 @@ function createBridgeRuntime(options) {
20258
20383
  ...pendingVariableSettings.has(dir) ? { publicationError: pendingVariableSettings.get(dir) } : {}
20259
20384
  });
20260
20385
  const refreshVariableSettings = async (dir) => {
20261
- if (!pendingVariableSettings.has(dir)) return;
20386
+ if (!pendingVariableSettings.has(dir) && deps.schedulePublication === void 0) return;
20262
20387
  try {
20263
20388
  await deps.overridesChanged?.(basename(dir));
20264
20389
  pendingVariableSettings.delete(dir);
@@ -20274,6 +20399,15 @@ function createBridgeRuntime(options) {
20274
20399
  ...committed,
20275
20400
  ...published(committed.publicationError ?? "变量已保存,等待运行发布")
20276
20401
  };
20402
+ if (settingsChanged && deps.schedulePublication !== void 0) {
20403
+ const publication = await schedulePublication(basename(dir));
20404
+ if (publication.publicationPending) pendingVariableSettings.set(dir, publication.publicationError ?? "变量开关已保存,等待运行发布");
20405
+ else pendingVariableSettings.delete(dir);
20406
+ return {
20407
+ ...committed,
20408
+ ...publication
20409
+ };
20410
+ }
20277
20411
  if (settingsChanged) await refreshVariableSettings(dir);
20278
20412
  return {
20279
20413
  ...committed,
@@ -20314,6 +20448,7 @@ function createBridgeRuntime(options) {
20314
20448
  finishModuleChange,
20315
20449
  runOverridesChange,
20316
20450
  refreshModuleList,
20451
+ schedulePublication,
20317
20452
  writeModuleOrderError,
20318
20453
  loadEngineMeta,
20319
20454
  collectSkills,
@@ -20727,7 +20862,7 @@ function registerDefinitions(runtime) {
20727
20862
  appendModuleCapabilities(doc, customToolModules(customTools));
20728
20863
  }
20729
20864
  }, { owns: ["customTools", "modules"] });
20730
- const publication = await runtime.runOverridesChange(basename(dir));
20865
+ const publication = await runtime.schedulePublication(basename(dir));
20731
20866
  tx.write(200, {
20732
20867
  ok: true,
20733
20868
  value: {
@@ -20787,7 +20922,7 @@ function registerDefinitions(runtime) {
20787
20922
  if (!creating && (typeof record.expectedRevision !== "string" || !SHA256_HEX_RE.test(record.expectedRevision))) throw new TypeError("保存人设必须提供读取时的 expectedRevision");
20788
20923
  saved = saveModulePersona(root, id, persona, creating ? readModulePersona(dir).revision : record.expectedRevision);
20789
20924
  }
20790
- const publication = await runtime.runOverridesChange(basename(dir));
20925
+ const publication = record.refreshOnly === true ? await runtime.runOverridesChange(basename(dir)) : await runtime.schedulePublication(basename(dir));
20791
20926
  tx.write(200, {
20792
20927
  ok: true,
20793
20928
  value: {
@@ -20836,7 +20971,7 @@ function registerDefinitions(runtime) {
20836
20971
  }
20837
20972
  if (record.refreshOnly !== true && record.expectedRevision !== null && (typeof record.expectedRevision !== "string" || !SHA256_HEX_RE.test(record.expectedRevision))) throw new TypeError("保存人设来源必须提供读取时的 expectedRevision");
20838
20973
  const saved = record.refreshOnly === true ? readPersonaSources(root) : writePersonaSources(root, record.sources, record.expectedRevision);
20839
- const publication = await runtime.runOverridesChange();
20974
+ const publication = record.refreshOnly === true ? await runtime.runOverridesChange() : await runtime.schedulePublication();
20840
20975
  tx.write(200, {
20841
20976
  ok: true,
20842
20977
  value: {
@@ -20921,7 +21056,7 @@ function registerDefinitions(runtime) {
20921
21056
  action: "create-recipe",
20922
21057
  recipeId: id.trim()
20923
21058
  });
20924
- const publication = result.changed ? await runtime.finishModuleChange(() => runtime.deps.capabilityChanged?.(basename(dir))) : published();
21059
+ const publication = result.changed ? await runtime.schedulePublication(basename(dir), "capability") : published();
20925
21060
  tx.write(200, {
20926
21061
  ok: true,
20927
21062
  value: {
@@ -22013,9 +22148,8 @@ function registerInstructions(runtime) {
22013
22148
  ...record.activateRuleId === void 0 ? {} : { activateRuleId: record.activateRuleId },
22014
22149
  ...record.validateOnly === void 0 ? {} : { validateOnly: record.validateOnly }
22015
22150
  }) : isEditableModuleDir(dir) ? readModuleRules(dir) : ensureModuleSlices(dir, { writable: false });
22016
- if (writing) try {
22017
- await runtime.deps.overridesChanged?.(basename(dir));
22018
- } catch (error) {
22151
+ const publication = writing ? await runtime.schedulePublication(basename(dir)) : void 0;
22152
+ if (publication?.publicationPending) {
22019
22153
  tx.write(200, {
22020
22154
  ok: true,
22021
22155
  value: {
@@ -22023,7 +22157,7 @@ function registerInstructions(runtime) {
22023
22157
  revisions: candidate.revisions,
22024
22158
  meta: await ruleEditorMeta(),
22025
22159
  ...createdRuleId === void 0 ? {} : { createdRuleId },
22026
- ...published(`规则已保存,但运行刷新失败:${String(error)}`)
22160
+ ...publication
22027
22161
  }
22028
22162
  });
22029
22163
  return;
@@ -22044,7 +22178,7 @@ function registerInstructions(runtime) {
22044
22178
  revisions: result.revisions,
22045
22179
  meta: await ruleEditorMeta(),
22046
22180
  ...createdRuleId === void 0 ? {} : { createdRuleId },
22047
- ...writing ? published() : {}
22181
+ ...publication
22048
22182
  }
22049
22183
  });
22050
22184
  } catch (error) {
@@ -22167,7 +22301,7 @@ function registerLifecycle(runtime) {
22167
22301
  });
22168
22302
  return;
22169
22303
  }
22170
- const publication = await runtime.refreshModuleList();
22304
+ const publication = await runtime.schedulePublication(void 0, "list");
22171
22305
  if (publication.publicationPending) runtime.deps.warn?.(`删除模块 ${id} 后刷新运行实例失败:${publication.publicationError}`);
22172
22306
  tx.write(200, {
22173
22307
  ok: true,
@@ -22217,7 +22351,7 @@ function registerLifecycle(runtime) {
22217
22351
  });
22218
22352
  return;
22219
22353
  }
22220
- const publication = await runtime.refreshModuleList();
22354
+ const publication = await runtime.schedulePublication(void 0, "list");
22221
22355
  tx.write(200, {
22222
22356
  ok: true,
22223
22357
  value: {
@@ -22257,7 +22391,7 @@ function registerLifecycle(runtime) {
22257
22391
  });
22258
22392
  return;
22259
22393
  }
22260
- const publication = await runtime.refreshModuleList();
22394
+ const publication = await runtime.schedulePublication(void 0, "list");
22261
22395
  tx.write(200, {
22262
22396
  ok: true,
22263
22397
  value: {
@@ -22320,7 +22454,7 @@ function registerLifecycle(runtime) {
22320
22454
  }
22321
22455
  if (record.expectedRevision === void 0 || record.expectedModuleId === void 0) throw new ModuleNameError("保存名称必须提供 expectedRevision 和 expectedModuleId");
22322
22456
  const value = writeModuleName(dir, record);
22323
- const publication = await runtime.runOverridesChange(basename(dir));
22457
+ const publication = await runtime.schedulePublication(basename(dir));
22324
22458
  tx.write(200, {
22325
22459
  ok: true,
22326
22460
  value: {
@@ -22398,26 +22532,9 @@ function registerLifecycle(runtime) {
22398
22532
  const enabled = record.enabled;
22399
22533
  await runtime.moduleOrderQueue.run(async () => {
22400
22534
  try {
22401
- let publication = published();
22402
- if (enabled) {
22403
- const changed = appendModuleConfigOrder(userModulesDir(), id);
22404
- if (!changed.includes(id) && !enabledModuleIds(userModulesDir()).includes(id) && readModuleConfigOrder(userModulesDir(), id).entries.length > 0) changed.push(id);
22405
- for (const changedId of changed) {
22406
- publication = await runtime.runOverridesChange(changedId);
22407
- if (publication.publicationPending) {
22408
- tx.write(200, {
22409
- ok: true,
22410
- value: {
22411
- enabled: enabledModuleIds(userModulesDir()),
22412
- ...publication
22413
- }
22414
- });
22415
- return;
22416
- }
22417
- }
22418
- }
22535
+ if (enabled) appendModuleConfigOrder(userModulesDir(), id);
22419
22536
  setModuleEnabled(userModulesDir(), id, enabled);
22420
- publication = await runtime.runOverridesChange();
22537
+ const publication = await runtime.schedulePublication();
22421
22538
  tx.write(200, {
22422
22539
  ok: true,
22423
22540
  value: {
@@ -22478,7 +22595,7 @@ function registerLifecycle(runtime) {
22478
22595
  if (writing) {
22479
22596
  const changedIds = saveModuleConfigOrder(userModulesDir(), body.expectedRevision, body.entries, moduleId);
22480
22597
  for (const id of changedIds) {
22481
- publication = await runtime.runOverridesChange(id);
22598
+ publication = await runtime.schedulePublication(id);
22482
22599
  if (publication.publicationPending) break;
22483
22600
  }
22484
22601
  }
@@ -22724,7 +22841,7 @@ function registerParams(runtime) {
22724
22841
  try {
22725
22842
  if (global) writeGlobalLayerSettings(userModulesDir(), rawOverrides ?? {});
22726
22843
  else saveModuleParams(moduleRoot, templateName, rawOverrides, void 0);
22727
- const publication = record.rebuild === false ? published() : await runtime.runOverridesChange(global ? void 0 : basename(dir));
22844
+ const publication = record.rebuild === false ? published() : await runtime.schedulePublication(global ? void 0 : basename(dir));
22728
22845
  tx.write(200, {
22729
22846
  ok: true,
22730
22847
  value: {
@@ -25586,7 +25703,7 @@ function registerTransfer(runtime) {
25586
25703
  }
25587
25704
  const installed = await installModulePackage(userModulesDir(), files, request.params);
25588
25705
  runtime.descriptor.invalidate();
25589
- const publication = await runtime.finishModuleChange(() => runtime.deps.moduleInstalled?.(installed.id));
25706
+ const publication = await runtime.schedulePublication(installed.id, "installed");
25590
25707
  tx.write(200, {
25591
25708
  ok: true,
25592
25709
  value: {
@@ -28702,6 +28819,110 @@ function createModuleInitialization(ctx) {
28702
28819
  };
28703
28820
  }
28704
28821
  //#endregion
28822
+ //#region src/runtime/publication-queue.ts
28823
+ /** 保存防抖与请求前冲刷共用待发布集合;回调只消费当批快照。 */
28824
+ function createPublicationQueue(options) {
28825
+ const serial = createTargetQueue();
28826
+ const ids = /* @__PURE__ */ new Set();
28827
+ const retries = /* @__PURE__ */ new Set();
28828
+ const errors = /* @__PURE__ */ new Map();
28829
+ let all = false, retryAll = false, active = true;
28830
+ let timer;
28831
+ let timerDone;
28832
+ let resolveTimer;
28833
+ let running;
28834
+ const cancelTimer = () => {
28835
+ if (timer !== void 0) clearTimeout(timer);
28836
+ timer = void 0;
28837
+ resolveTimer?.();
28838
+ resolveTimer = void 0;
28839
+ timerDone = void 0;
28840
+ };
28841
+ const assertActive = () => {
28842
+ if (!active) throw new Error("publication queue released");
28843
+ };
28844
+ const include = (id) => {
28845
+ if (id === void 0) {
28846
+ all = true;
28847
+ ids.clear();
28848
+ } else if (!all) ids.add(id);
28849
+ };
28850
+ const start = () => {
28851
+ cancelTimer();
28852
+ if (running !== void 0) return running.catch(() => {}).then(start);
28853
+ if (!active || !all && ids.size === 0) return Promise.resolve();
28854
+ const modules = all ? void 0 : [...ids];
28855
+ const retry = retryAll ? true : [...retries];
28856
+ all = false;
28857
+ retryAll = false;
28858
+ ids.clear();
28859
+ retries.clear();
28860
+ const task = serial.run("publication", async () => {
28861
+ try {
28862
+ const complete = await options.publish(modules, retry);
28863
+ if (modules === void 0 || complete === true) errors.clear();
28864
+ else for (const id of modules) errors.delete(id);
28865
+ } catch (error) {
28866
+ const message = error instanceof Error ? error.message : String(error);
28867
+ const at = Date.now();
28868
+ for (const id of modules ?? [void 0]) errors.set(id ?? "", {
28869
+ ...id === void 0 ? {} : { moduleId: id },
28870
+ message,
28871
+ at
28872
+ });
28873
+ try {
28874
+ options.warn(message);
28875
+ } catch {}
28876
+ throw error;
28877
+ }
28878
+ });
28879
+ running = task;
28880
+ const finished = () => {
28881
+ if (running === task) running = void 0;
28882
+ };
28883
+ task.then(finished, finished);
28884
+ return task;
28885
+ };
28886
+ return {
28887
+ schedule(moduleId) {
28888
+ assertActive();
28889
+ include(moduleId);
28890
+ cancelTimer();
28891
+ timerDone = new Promise((resolve) => {
28892
+ resolveTimer = resolve;
28893
+ });
28894
+ timer = setTimeout(() => {
28895
+ start().catch(() => {});
28896
+ }, 300);
28897
+ },
28898
+ flush(moduleId, retry = true) {
28899
+ assertActive();
28900
+ if (retry) {
28901
+ include(moduleId);
28902
+ if (moduleId === void 0) retryAll = true;
28903
+ else retries.add(moduleId);
28904
+ } else if (moduleId !== void 0) include(moduleId);
28905
+ return start();
28906
+ },
28907
+ async settled() {
28908
+ while (timerDone !== void 0 || running !== void 0) await Promise.all([timerDone, running?.catch(() => {})]);
28909
+ },
28910
+ failures() {
28911
+ return active ? [...errors.values()].map((error) => ({ ...error })) : [];
28912
+ },
28913
+ async dispose() {
28914
+ active = false;
28915
+ cancelTimer();
28916
+ all = false;
28917
+ retryAll = false;
28918
+ ids.clear();
28919
+ retries.clear();
28920
+ errors.clear();
28921
+ await running?.catch(() => {});
28922
+ }
28923
+ };
28924
+ }
28925
+ //#endregion
28705
28926
  //#region src/host/generation-recovery-state.ts
28706
28927
  /** 会话恢复额度的唯一持久owner,重挂不重置,先扣额再允许唤醒。 */
28707
28928
  function createGenerationRecoveryState(root = join(storageRoot(), "generation-recovery")) {
@@ -28793,37 +29014,74 @@ const noNext = /* @__PURE__ */ new Set([
28793
29014
  ]);
28794
29015
  const compare = (a, b) => a.action.channelOrder - b.action.channelOrder || a.moduleId.localeCompare(b.moduleId) || a.ruleIndex - b.ruleIndex || a.action.actionIndex - b.action.actionIndex;
28795
29016
  const sourceError = (error, moduleId) => Object.assign(new Error(String(error?.message ?? error), { cause: error }), { sourceModuleId: moduleId });
29017
+ const pointKey = (point) => `${point.channel}:${point.phase}:${point.waterfallPosition}`;
29018
+ const compareEntries = (a, b) => a.sequence - b.sequence || a.sourceModuleId.localeCompare(b.sourceModuleId) || a.ruleIndex - b.ruleIndex || a.ruleActionIndex - b.ruleActionIndex;
29019
+ function pointHandler(ctx, point, snapshot, warnOnce, report, isActive) {
29020
+ return (...args) => {
29021
+ const handlers = snapshot();
29022
+ const free = noNext.has(point.channel);
29023
+ const payload = free ? args : args.slice(0, -1);
29024
+ const next = free ? () => void 0 : args.at(-1);
29025
+ if (!isActive()) return next();
29026
+ let frame;
29027
+ try {
29028
+ frame = ruleFrame(point.channel, payload, warnOnce, ctx, report);
29029
+ } catch (error) {
29030
+ warnOnce(`rule engine: frame construction failed on ${point.channel}: ${String(error?.message ?? error)}`);
29031
+ return next();
29032
+ }
29033
+ if (free) {
29034
+ let pending;
29035
+ for (const entry of handlers) {
29036
+ const invoke = () => {
29037
+ if (!isActive()) return void 0;
29038
+ if (!actionMatches(entry, frame)) {
29039
+ entry.miss?.(frame);
29040
+ return;
29041
+ }
29042
+ return entry.handler(...payload, void 0, frame);
29043
+ };
29044
+ pending = pending?.then ? pending.then(invoke) : invoke();
29045
+ }
29046
+ return pending;
29047
+ }
29048
+ if (point.phase === "after-next") return Promise.resolve(next()).then(async (initial) => {
29049
+ let result = initial;
29050
+ for (const entry of handlers) {
29051
+ if (!isActive()) continue;
29052
+ if (!actionMatches(entry, frame)) {
29053
+ entry.miss?.(frame);
29054
+ continue;
29055
+ }
29056
+ result = await entry.handler(...payload, () => result, frame);
29057
+ }
29058
+ return result;
29059
+ });
29060
+ const invoke = (index) => {
29061
+ if (!isActive() || index === handlers.length) return next();
29062
+ const entry = handlers[index];
29063
+ if (!actionMatches(entry, frame)) {
29064
+ entry.miss?.(frame);
29065
+ return invoke(index + 1);
29066
+ }
29067
+ return entry.handler(...payload, () => invoke(index + 1), frame);
29068
+ };
29069
+ return invoke(0);
29070
+ };
29071
+ }
28796
29072
  /** 所有来源只进入这个装配入口;受众限定固定效果要求 ctx 属于 options.agent 的 scope。 */
28797
- function mountRuleSources(ctx, sources, options = {}) {
29073
+ function mountSources(ctx, sources, options = {}) {
28798
29074
  validateDeliverySources(sources);
28799
- const coordinator = (() => {
28800
- const service = getService(ctx, PRE_STEP_COORDINATOR_SERVICE);
28801
- return service !== null && typeof service === "object" && typeof service.registerPreset === "function" ? service : void 0;
28802
- })();
28803
- const pool = options.promotionPool ?? coordinator?.promotionPool ?? createPromotionPool(ctx);
28804
- const ownsPool = options.promotionPool === void 0 && coordinator?.promotionPool === void 0;
29075
+ const pool = options.promotionPool;
28805
29076
  const warnOnce = options.warnOnce ?? createWarnOnce(ctx, options.plugin ?? "rule-engine");
28806
- const report = options.onOutcome === void 0 ? void 0 : (() => {
28807
- const moduleOfRule = /* @__PURE__ */ new Map();
28808
- for (const source of sources) for (const rule of source.rules) if (!moduleOfRule.has(rule)) moduleOfRule.set(rule, source.moduleId);
28809
- return (rule, channel, outcome) => options.onOutcome({
28810
- moduleId: moduleOfRule.get(rule),
28811
- ruleId: rule.id,
28812
- channel,
28813
- outcome
28814
- });
28815
- })();
28816
29077
  const releases = [];
28817
- if (ownsPool) releases.push(() => pool.dispose());
28818
29078
  const points = /* @__PURE__ */ new Map();
28819
- const turnStopBudgets = /* @__PURE__ */ new Map();
28820
29079
  const injections = new Map(sources.map((source) => [source.moduleId, {
28821
29080
  moduleId: source.moduleId,
28822
29081
  sourceId: moduleSourceId(source.moduleId),
28823
29082
  configs: [],
28824
29083
  ruleActions: []
28825
29084
  }]));
28826
- let active = true;
28827
29085
  const bind = (item, on) => prepareAction(item.action, {
28828
29086
  plugin: options.plugin,
28829
29087
  warnOnce,
@@ -28877,16 +29135,7 @@ function mountRuleSources(ctx, sources, options = {}) {
28877
29135
  let textConfigs = [];
28878
29136
  const flushLayers = () => {
28879
29137
  if (!textConfigs.length) return;
28880
- releases.push(wireLayers(ctx, textConfigs, warnOnce, {
28881
- agent: options.agent,
28882
- turnStopBudgets,
28883
- sourceError,
28884
- on: (channel, handler) => {
28885
- if (channel !== point.channel) throw new TypeError(`rule action registered outside ${point.channel}: ${channel}`);
28886
- handlers.push({ handler });
28887
- return () => {};
28888
- }
28889
- }));
29138
+ options.collectText(point, textConfigs);
28890
29139
  textConfigs = [];
28891
29140
  };
28892
29141
  for (const item of point.items) {
@@ -28901,7 +29150,9 @@ function mountRuleSources(ctx, sources, options = {}) {
28901
29150
  exclusive: false,
28902
29151
  enabled: true,
28903
29152
  actionWhen: action.actionWhen,
28904
- bypassRuleWhen: action.bypassRuleWhen
29153
+ bypassRuleWhen: action.bypassRuleWhen,
29154
+ runtimeOrder: action.channelOrder,
29155
+ runtimeRuleIndex: item.ruleIndex
28905
29156
  };
28906
29157
  if (config.layer === "pre-step") injections.get(moduleId).configs.push(config);
28907
29158
  else textConfigs.push(config);
@@ -28917,6 +29168,7 @@ function mountRuleSources(ctx, sources, options = {}) {
28917
29168
  sequence: action.channelOrder,
28918
29169
  sourceModuleId: moduleId,
28919
29170
  ruleId: rule.id,
29171
+ ruleIndex: item.ruleIndex,
28920
29172
  ruleActionIndex: action.actionIndex,
28921
29173
  id: action.id,
28922
29174
  actionWhen: action.actionWhen,
@@ -28938,61 +29190,9 @@ function mountRuleSources(ctx, sources, options = {}) {
28938
29190
  }
28939
29191
  flushLayers();
28940
29192
  if (!handlers.length) continue;
28941
- releases.push(ctx.on(point.channel, (...args) => {
28942
- const free = noNext.has(point.channel);
28943
- const payload = free ? args : args.slice(0, -1);
28944
- const next = free ? () => void 0 : args[args.length - 1];
28945
- if (!active) return next();
28946
- let frame;
28947
- try {
28948
- frame = ruleFrame(point.channel, payload, warnOnce, ctx, report);
28949
- } catch (error) {
28950
- warnOnce(`rule engine: frame construction failed on ${point.channel}: ${String(error?.message ?? error)}`);
28951
- return next();
28952
- }
28953
- if (free) {
28954
- let pending;
28955
- for (const entry of handlers) {
28956
- const invoke = () => {
28957
- if (!active) return void 0;
28958
- if (!actionMatches(entry, frame)) {
28959
- entry.miss?.(frame);
28960
- return;
28961
- }
28962
- return entry.handler(...payload, void 0, frame);
28963
- };
28964
- pending = pending?.then ? pending.then(invoke) : invoke();
28965
- }
28966
- return pending;
28967
- }
28968
- if (point.phase === "after-next") return Promise.resolve(next()).then(async (initial) => {
28969
- let result = initial;
28970
- for (const entry of handlers) {
28971
- if (!active) continue;
28972
- if (!actionMatches(entry, frame)) {
28973
- entry.miss?.(frame);
28974
- continue;
28975
- }
28976
- result = await entry.handler(...payload, () => result, frame);
28977
- }
28978
- return result;
28979
- });
28980
- const invoke = (index) => {
28981
- if (!active || index === handlers.length) return next();
28982
- const entry = handlers[index];
28983
- if (!actionMatches(entry, frame)) {
28984
- entry.miss?.(frame);
28985
- return invoke(index + 1);
28986
- }
28987
- return entry.handler(...payload, () => invoke(index + 1), frame);
28988
- };
28989
- return invoke(0);
28990
- }, point.waterfallPosition === "outermost" ? { prepend: true } : void 0));
29193
+ options.collectPoint(point, handlers);
28991
29194
  }
28992
- const promotion = options.promotion ?? coordinator?.promotion ?? {
28993
- main: pool.tracker(PROMOTE_EVENTS.either, { includeSubagents: false }),
28994
- withSubagents: pool.tracker(PROMOTE_EVENTS.either, { includeSubagents: true })
28995
- };
29195
+ const promotion = options.promotion;
28996
29196
  const preStep = [...injections.values()].filter((source) => source.configs.length || source.ruleActions.length).map((source) => createPreStepSource({
28997
29197
  sourceId: source.sourceId,
28998
29198
  ctx,
@@ -29004,21 +29204,233 @@ function mountRuleSources(ctx, sources, options = {}) {
29004
29204
  }),
29005
29205
  promotion
29006
29206
  }));
29007
- if (preStep.length) releases.push(applyPromptConfigSources(preStep, { layers: false }));
29207
+ if (preStep.length) releases.push(options.registerPreStep(preStep));
29008
29208
  } catch (error) {
29009
- active = false;
29010
29209
  for (const dispose of releases.reverse()) try {
29011
29210
  dispose?.();
29012
29211
  } catch {}
29013
29212
  throw error;
29014
29213
  }
29015
29214
  return () => {
29016
- active = false;
29017
29215
  for (const dispose of releases.splice(0).reverse()) try {
29018
29216
  dispose?.();
29019
29217
  } catch {}
29020
29218
  };
29021
29219
  }
29220
+ /** 稳定执行点持有有序快照;来源仅在规则实例改变时重新绑定和注册。 */
29221
+ function createRuleRuntime(ctx, options = {}) {
29222
+ const warnOnce = options.warnOnce ?? createWarnOnce(ctx, options.plugin ?? "rule-engine");
29223
+ const coordinatorOf = () => {
29224
+ const value = getService(ctx, PRE_STEP_COORDINATOR_SERVICE);
29225
+ return typeof value?.registerPreset === "function" ? value : void 0;
29226
+ };
29227
+ const initialCoordinator = coordinatorOf();
29228
+ const pool = options.promotionPool ?? initialCoordinator?.promotionPool ?? createPromotionPool(ctx);
29229
+ const ownsPool = options.promotionPool === void 0 && initialCoordinator?.promotionPool === void 0;
29230
+ const promotion = options.promotion ?? initialCoordinator?.promotion ?? {
29231
+ main: pool.tracker(PROMOTE_EVENTS.either, { includeSubagents: false }),
29232
+ withSubagents: pool.tracker(PROMOTE_EVENTS.either, { includeSubagents: true })
29233
+ };
29234
+ const standalone = /* @__PURE__ */ new Map();
29235
+ const registrations = /* @__PURE__ */ new Map();
29236
+ let registeredCoordinator = initialCoordinator;
29237
+ let active = true;
29238
+ let records = /* @__PURE__ */ new Map();
29239
+ let points = /* @__PURE__ */ new Map();
29240
+ const listeners = /* @__PURE__ */ new Map();
29241
+ const ruleOwners = /* @__PURE__ */ new WeakMap();
29242
+ const memo = /* @__PURE__ */ new Map();
29243
+ const report = options.onOutcome === void 0 ? void 0 : (rule, channel, outcome) => options.onOutcome({
29244
+ moduleId: ruleOwners.get(rule),
29245
+ ruleId: rule.id,
29246
+ channel,
29247
+ outcome
29248
+ });
29249
+ let layers;
29250
+ let releaseMemo, releaseFallback;
29251
+ const register = (coordinator, source) => {
29252
+ if (coordinator.promotion === void 0) throw new TypeError("prompt-tool: pre-step coordinator v2 must expose a promotion owner");
29253
+ return coordinator.registerPreset(source.sourceId, {
29254
+ ...source,
29255
+ promotion: coordinator.promotion
29256
+ }) ?? (() => {});
29257
+ };
29258
+ const ensureFallback = () => {
29259
+ if (releaseFallback !== void 0) return;
29260
+ releaseMemo = ctx.on("session/event", (session, event) => confirmDelivered(memo, session, event));
29261
+ try {
29262
+ releaseFallback = ctx.on("agent/pre-step", async (payload, next) => {
29263
+ const coordinator = coordinatorOf();
29264
+ if (coordinator !== registeredCoordinator) {
29265
+ for (const release of registrations.values()) release();
29266
+ registrations.clear();
29267
+ registeredCoordinator = coordinator;
29268
+ if (coordinator !== void 0) for (const source of standalone.values()) registrations.set(source.sourceId, register(coordinator, source));
29269
+ }
29270
+ const sources = [...standalone.values()];
29271
+ const decision = await next();
29272
+ if (!active || coordinator !== void 0 || sources.length === 0) return decision;
29273
+ return runPreStepBatch({
29274
+ sources,
29275
+ ctx,
29276
+ payload,
29277
+ agent: payload?.agent,
29278
+ decision,
29279
+ memo,
29280
+ warnOnce
29281
+ });
29282
+ }, { prepend: true });
29283
+ } catch (error) {
29284
+ releaseMemo();
29285
+ releaseMemo = void 0;
29286
+ throw error;
29287
+ }
29288
+ };
29289
+ const mount = (source) => {
29290
+ const record = {
29291
+ source,
29292
+ points: [],
29293
+ text: [],
29294
+ dispose: void 0
29295
+ };
29296
+ for (const rule of source.rules) ruleOwners.set(rule, source.moduleId);
29297
+ record.dispose = mountSources(ctx, [source], {
29298
+ ...options,
29299
+ warnOnce,
29300
+ promotionPool: pool,
29301
+ promotion,
29302
+ registerPreStep(sources) {
29303
+ const installed = [];
29304
+ const dispose = () => {
29305
+ for (const item of installed) if (standalone.get(item.sourceId) === item) {
29306
+ standalone.delete(item.sourceId);
29307
+ registrations.get(item.sourceId)?.();
29308
+ registrations.delete(item.sourceId);
29309
+ }
29310
+ };
29311
+ try {
29312
+ ensureFallback();
29313
+ for (const item of sources) {
29314
+ if (standalone.has(item.sourceId)) throw new TypeError(`prompt-tool: duplicate pre-step source ${item.sourceId}`);
29315
+ standalone.set(item.sourceId, item);
29316
+ installed.push(item);
29317
+ const coordinator = coordinatorOf();
29318
+ if (coordinator !== void 0) try {
29319
+ registrations.set(item.sourceId, register(coordinator, item));
29320
+ } catch (error) {
29321
+ throw sourceError(error, source.moduleId);
29322
+ }
29323
+ }
29324
+ return dispose;
29325
+ } catch (error) {
29326
+ dispose();
29327
+ throw error;
29328
+ }
29329
+ },
29330
+ collectPoint(point, handlers) {
29331
+ record.points.push({
29332
+ point,
29333
+ handlers
29334
+ });
29335
+ },
29336
+ collectText(point, configs) {
29337
+ for (const config of configs) {
29338
+ config.runtimePoint = point;
29339
+ record.text.push(config);
29340
+ }
29341
+ }
29342
+ });
29343
+ return record;
29344
+ };
29345
+ const publishPoints = () => {
29346
+ const next = /* @__PURE__ */ new Map();
29347
+ const add = (point, entry) => {
29348
+ const key = pointKey(point);
29349
+ const value = next.get(key) ?? {
29350
+ point,
29351
+ handlers: []
29352
+ };
29353
+ value.handlers.push(entry);
29354
+ next.set(key, value);
29355
+ };
29356
+ for (const record of records.values()) for (const { point, handlers } of record.points) for (const entry of handlers) add(point, entry);
29357
+ for (const { channel, handler, config } of layers?.entries() ?? []) add({
29358
+ ...config.runtimePoint,
29359
+ channel
29360
+ }, {
29361
+ handler,
29362
+ sequence: config.runtimeOrder,
29363
+ sourceModuleId: config.sourceModuleId,
29364
+ ruleIndex: config.runtimeRuleIndex,
29365
+ ruleActionIndex: config.ruleActionIndex ?? 0
29366
+ });
29367
+ for (const [key, value] of next) {
29368
+ value.handlers.sort(compareEntries);
29369
+ if (!listeners.has(key)) listeners.set(key, ctx.on(value.point.channel, pointHandler(ctx, value.point, () => points.get(key)?.handlers ?? [], warnOnce, report, () => active), value.point.waterfallPosition === "outermost" ? { prepend: true } : void 0));
29370
+ }
29371
+ points = next;
29372
+ };
29373
+ return {
29374
+ replace(sources) {
29375
+ if (!active) throw new Error("rule runtime disposed");
29376
+ validateDeliverySources(sources);
29377
+ const desired = /* @__PURE__ */ new Map();
29378
+ for (const source of sources) {
29379
+ if (desired.has(source.moduleId)) throw sourceError(/* @__PURE__ */ new TypeError(`duplicate rule source ${source.moduleId}`), source.moduleId);
29380
+ desired.set(source.moduleId, source);
29381
+ }
29382
+ const old = records;
29383
+ const changed = [...old.values()].filter((record) => desired.get(record.source.moduleId)?.rules !== record.source.rules);
29384
+ if (changed.length === 0 && sources.length === old.size) return;
29385
+ for (const record of changed) record.dispose();
29386
+ const created = [];
29387
+ try {
29388
+ const next = /* @__PURE__ */ new Map();
29389
+ for (const source of sources) {
29390
+ let record = old.get(source.moduleId);
29391
+ if (record?.source.rules !== source.rules) {
29392
+ record = mount(source);
29393
+ created.push(record);
29394
+ }
29395
+ next.set(source.moduleId, record);
29396
+ }
29397
+ records = next;
29398
+ const text = [...records.values()].flatMap((record) => record.text);
29399
+ if (text.length > 0) layers ??= createLayerRuntime(ctx, warnOnce, {
29400
+ agent: options.agent,
29401
+ turnStopBudgets: /* @__PURE__ */ new Map(),
29402
+ sourceError
29403
+ });
29404
+ layers?.replace(text);
29405
+ publishPoints();
29406
+ } catch (error) {
29407
+ for (const record of created.reverse()) record.dispose();
29408
+ records = new Map(old);
29409
+ try {
29410
+ for (const record of changed) records.set(record.source.moduleId, mount(record.source));
29411
+ layers?.replace([...records.values()].flatMap((record) => record.text));
29412
+ publishPoints();
29413
+ } catch (restoreError) {
29414
+ throw Object.assign(new AggregateError([error, restoreError], `rule source replacement and recovery failed: ${String(error.message)}; ${String(restoreError.message)}`), { sourceModuleId: error.sourceModuleId });
29415
+ }
29416
+ throw error;
29417
+ }
29418
+ },
29419
+ dispose() {
29420
+ if (!active) return;
29421
+ active = false;
29422
+ for (const record of records.values()) record.dispose();
29423
+ records.clear();
29424
+ layers?.dispose();
29425
+ for (const release of listeners.values()) release();
29426
+ listeners.clear();
29427
+ points.clear();
29428
+ releaseFallback?.();
29429
+ releaseMemo?.();
29430
+ if (ownsPool) pool.dispose();
29431
+ }
29432
+ };
29433
+ }
29022
29434
  //#endregion
29023
29435
  //#region src/runtime/agent-assembly.ts
29024
29436
  /**
@@ -29238,6 +29650,7 @@ function createAgentAssembly(ctx, options) {
29238
29650
  const watched = /* @__PURE__ */ new Map();
29239
29651
  const disposed = /* @__PURE__ */ new WeakSet();
29240
29652
  let pending = Promise.resolve();
29653
+ let publishingContent;
29241
29654
  const serialQueue = createTargetQueue();
29242
29655
  let active = true;
29243
29656
  const warn = (error) => {
@@ -29323,7 +29736,7 @@ function createAgentAssembly(ctx, options) {
29323
29736
  mounts.delete(sessionId);
29324
29737
  await current.fiber.dispose();
29325
29738
  };
29326
- const reject = (agent, item) => {
29739
+ const reject = (agent, item, error) => {
29327
29740
  let rejected = rejectedVersions.get(agent);
29328
29741
  if (rejected === void 0) {
29329
29742
  rejected = /* @__PURE__ */ new Map();
@@ -29331,186 +29744,255 @@ function createAgentAssembly(ctx, options) {
29331
29744
  }
29332
29745
  let versions = rejected.get(item.moduleId);
29333
29746
  if (versions === void 0) {
29334
- versions = /* @__PURE__ */ new Set();
29747
+ versions = /* @__PURE__ */ new Map();
29335
29748
  rejected.set(item.moduleId, versions);
29336
29749
  }
29337
- versions.add(sourceVersions.get(item) ?? "");
29750
+ versions.set(sourceVersions.get(item) ?? "", failureReason(error));
29338
29751
  if (lastGood.get(agent)?.get(item.moduleId) === item) lastGood.get(agent)?.delete(item.moduleId);
29339
29752
  };
29340
- const mount = async (agent, prepared, plan, duplicateIdentities = [], stamp = "", content = "") => {
29341
- let mounted = false, live = true;
29342
- const initializationErrors = [];
29343
- const waiting = new Map(prepared.map((item) => [item, /* @__PURE__ */ new Set()]));
29344
- const observers = [];
29345
- const initializationRoots = [];
29753
+ const mount = async (agent, prepared, plan, duplicateIdentities = [], stamp = "", content = "", personaSignature = "") => {
29754
+ for (const item of prepared) for (const name of item.services) if (agent.ctx.get(name) === void 0) throw Object.assign(/* @__PURE__ */ new Error(`模块 ${item.moduleId} 挂载所需宿主能力不可用:${name}`), { sourceModuleId: item.moduleId });
29755
+ let current = mounts.get(agent.id);
29756
+ if (current === void 0) {
29757
+ let scope;
29758
+ const root = agent.ctx.plugin({
29759
+ name: "prompt-tool-assembly",
29760
+ apply: (scopeCtx) => {
29761
+ scope = scopeCtx;
29762
+ }
29763
+ });
29764
+ try {
29765
+ await root;
29766
+ } catch (error) {
29767
+ await root.dispose();
29768
+ throw error;
29769
+ }
29770
+ if (scope === void 0) {
29771
+ await root.dispose();
29772
+ throw new Error("运行时配装作用域未初始化");
29773
+ }
29774
+ let rules;
29775
+ try {
29776
+ rules = createRuleRuntime(scope, {
29777
+ agent,
29778
+ onOutcome: ruleDiagnostics.record,
29779
+ variableStore,
29780
+ standingMountFor: await loadStandingMountFor(),
29781
+ manifestCheck
29782
+ });
29783
+ } catch (error) {
29784
+ await root.dispose();
29785
+ throw error;
29786
+ }
29787
+ const record = {
29788
+ agent,
29789
+ scope,
29790
+ rules,
29791
+ modules: /* @__PURE__ */ new Map(),
29792
+ shared: /* @__PURE__ */ new Map(),
29793
+ prepared: [],
29794
+ moduleIds: [],
29795
+ stamp: "",
29796
+ content: "",
29797
+ personaSignature: "",
29798
+ plan: {
29799
+ sections: [],
29800
+ suppressRuntimeContext: false
29801
+ },
29802
+ live: true,
29803
+ committed: false,
29804
+ mutating: false,
29805
+ fiber: { dispose: () => {} }
29806
+ };
29807
+ const disposeFiber = ctx.effect(() => () => {
29808
+ record.live = false;
29809
+ for (const slot of [...record.modules.values(), ...record.shared.values()]) {
29810
+ slot.live = false;
29811
+ slot.off();
29812
+ }
29813
+ rules.dispose();
29814
+ return root.dispose();
29815
+ }, `prompt-tool assembly ${agent.id}`);
29816
+ record.fiber = { dispose: async () => {
29817
+ await disposeFiber();
29818
+ } };
29819
+ mounts.set(agent.id, record);
29820
+ current = record;
29821
+ }
29822
+ const record = current;
29346
29823
  const promote = () => {
29347
- if (!live || !mounted || !active || disposed.has(agent)) return;
29824
+ if (!record.live || !record.committed || record.mutating || !active || disposed.has(agent)) return;
29348
29825
  let good = lastGood.get(agent);
29349
29826
  if (good === void 0) {
29350
29827
  good = /* @__PURE__ */ new Map();
29351
29828
  lastGood.set(agent, good);
29352
29829
  }
29353
- for (const item of prepared) if (waiting.get(item)?.size === 0 && !initializationErrors.some((failure) => failure.owners.includes(item))) good.set(item.moduleId, item);
29830
+ for (const item of record.prepared) if ([...record.modules.values(), ...record.shared.values()].filter((slot) => slot.owners.includes(item)).every((slot) => slot.ready && slot.failed === void 0)) good.set(item.moduleId, item);
29354
29831
  };
29355
- const observe = (child, owners) => {
29356
- initializationRoots.push(child);
29357
- const token = {};
29358
- for (const item of owners) waiting.get(item)?.add(token);
29359
- observers.push(initialization.observe(child, {
29832
+ const disposeSlot = async (slot) => {
29833
+ slot.live = false;
29834
+ slot.off();
29835
+ await slot.fiber.dispose();
29836
+ };
29837
+ const applyCapability = async (scopeCtx, item, module) => {
29838
+ const result = await module.apply(scopeCtx, module.config, {
29839
+ agent,
29840
+ sourceModuleId: item.moduleId,
29841
+ sourceModuleDir: resolveModuleDir(item.moduleId, options.moduleRoot),
29842
+ recoveryState,
29843
+ contextRuntimeOf
29844
+ });
29845
+ if (typeof result === "function") scopeCtx.effect(() => result, module.id);
29846
+ };
29847
+ const createSlot = async (map, key, owners, apply, signature) => {
29848
+ const fiber = record.scope.plugin({
29849
+ name: `prompt-tool-${map === record.modules ? "module" : "shared"}:${key}`,
29850
+ inject: [...new Set(owners.flatMap((item) => [...item.services]))],
29851
+ apply
29852
+ });
29853
+ const slot = {
29854
+ fiber,
29855
+ owners,
29856
+ ready: false,
29857
+ live: true,
29858
+ off: () => {},
29859
+ ...signature === void 0 ? {} : { signature }
29860
+ };
29861
+ map.set(key, slot);
29862
+ slot.off = initialization.observe(fiber, {
29360
29863
  ready: () => {
29361
- for (const item of owners) waiting.get(item)?.delete(token);
29362
- promote();
29864
+ if (slot.live) {
29865
+ slot.ready = true;
29866
+ promote();
29867
+ }
29363
29868
  },
29364
29869
  failed: (error) => {
29365
- if (!live || !active || disposed.has(agent)) return;
29366
- initializationErrors.push({
29367
- owners,
29368
- error
29369
- });
29370
- if (!mounted) return;
29371
- for (const item of owners) reject(agent, item);
29372
- serial(async () => {
29373
- if (!live || !active || disposed.has(agent)) return;
29374
- try {
29375
- await replace([agent]);
29376
- } catch (failure) {
29377
- warn(failure);
29378
- }
29379
- });
29380
- warn(new Error(`模块 ${owners.map((item) => item.moduleId).join("、")} 初始化失败:${failureReason(error)}`, { cause: error }));
29870
+ if (!slot.live || !record.live || !active || disposed.has(agent) || map.get(key) !== slot) return;
29871
+ slot.failed = error;
29872
+ if (record.mutating || !record.committed) return;
29873
+ for (const item of slot.owners) {
29874
+ reject(agent, item, error);
29875
+ publication.schedule(item.moduleId);
29876
+ }
29877
+ warn(new Error(`模块 ${slot.owners.map((item) => item.moduleId).join("、")} 初始化失败:${failureReason(error)}`, { cause: error }));
29381
29878
  }
29382
- }));
29879
+ });
29880
+ try {
29881
+ await fiber;
29882
+ await initialization.settled([fiber]);
29883
+ if (slot.failed !== void 0) throw slot.failed;
29884
+ } catch (error) {
29885
+ await disposeSlot(slot);
29886
+ map.delete(key);
29887
+ throw Object.assign(new Error(`模块 ${owners.map((item) => item.moduleId).join("、")} 挂载失败:${failureReason(error)}`, { cause: error }), { sourceModuleIds: owners.map((item) => item.moduleId) });
29888
+ }
29383
29889
  };
29384
- const services = /* @__PURE__ */ new Set();
29385
- for (const item of prepared) for (const name of item.services) {
29386
- if (agent.ctx.get(name) === void 0) throw Object.assign(/* @__PURE__ */ new Error(`模块 ${item.moduleId} 挂载所需宿主能力不可用:${name}`), { sourceModuleId: item.moduleId });
29387
- services.add(name);
29388
- }
29389
- const personaActive = plan.sections.length > 0 || plan.suppressRuntimeContext;
29390
- if (personaActive) services.add("systemPrompt");
29391
- const fiber = agent.ctx.plugin({
29392
- name: "prompt-tool-assembly",
29393
- inject: [...services],
29394
- apply: async (scopeCtx) => {
29395
- for (const item of duplicateIdentities) {
29396
- const where = item.channel === "kind" ? `显式 sourceKind ${JSON.stringify(item.identity)}` : `去重身份 ${JSON.stringify(item.identity)}`;
29397
- warnOnce(item.suspectedCopy ? `模块 ${item.moduleIds.join("、")} 疑似由复制产生同一个${where}(dedupe: session/batch):去重按模块各记一份,两份正文都会注入,请改 rule id 或显式 identity 以区分` : `模块 ${item.moduleIds.join("、")} 声明了同一个${where}(dedupe: session/batch):去重按模块各记一份,两模块各自注入;同一模块内才共享这份去重`);
29890
+ record.mutating = true;
29891
+ try {
29892
+ record.rules.replace(prepared.filter((item) => item.rules.length > 0).map((item) => ({
29893
+ moduleId: item.moduleId,
29894
+ rules: item.rules
29895
+ })));
29896
+ const desired = new Set(prepared.map((item) => item.moduleId));
29897
+ for (const [id, slot] of record.modules) if (!desired.has(id) || !prepared.includes(slot.owners[0]) || !slot.live || slot.failed !== void 0) {
29898
+ await disposeSlot(slot);
29899
+ record.modules.delete(id);
29900
+ }
29901
+ for (const item of prepared) {
29902
+ const capabilities = item.modules.filter((module) => PRIVATE_SERVICES[module.id] === void 0 || module.id === "retrieval");
29903
+ if (capabilities.length > 0 && !record.modules.has(item.moduleId)) await createSlot(record.modules, item.moduleId, [item], async (scopeCtx) => {
29904
+ for (const module of capabilities) await applyCapability(scopeCtx, item, module);
29905
+ });
29906
+ }
29907
+ const shared = /* @__PURE__ */ new Map();
29908
+ for (const item of prepared) for (const module of item.modules) {
29909
+ if (PRIVATE_SERVICES[module.id] === void 0 || module.id === "retrieval" || shared.has(module.id)) continue;
29910
+ const owners = prepared.filter((source) => source.modules.some((candidate) => candidate.id === module.id));
29911
+ shared.set(module.id, {
29912
+ owners,
29913
+ item,
29914
+ module,
29915
+ signature: JSON.stringify([
29916
+ owners.map((owner) => owner.moduleId),
29917
+ item.moduleId,
29918
+ module.config
29919
+ ])
29920
+ });
29921
+ }
29922
+ for (const [id, slot] of record.shared) if (shared.get(id)?.signature !== slot.signature || !slot.live || slot.failed !== void 0) {
29923
+ await disposeSlot(slot);
29924
+ record.shared.delete(id);
29925
+ }
29926
+ for (const [id, entry] of shared) {
29927
+ const old = record.shared.get(id);
29928
+ if (old !== void 0) old.owners = entry.owners;
29929
+ else await createSlot(record.shared, id, entry.owners, (scopeCtx) => applyCapability(scopeCtx, entry.item, entry.module), entry.signature);
29930
+ }
29931
+ const personaKey = JSON.stringify([
29932
+ personaSignature,
29933
+ plan.suppressRuntimeContext,
29934
+ plan.sections.map((section) => ({
29935
+ ...section,
29936
+ text: String(section.text)
29937
+ }))
29938
+ ]);
29939
+ if (record.persona?.signature !== personaKey) {
29940
+ if (record.persona !== void 0) {
29941
+ await record.persona.fiber.dispose();
29942
+ delete record.persona;
29398
29943
  }
29399
- const standingMountFor = await loadStandingMountFor();
29400
- const ruleSources = prepared.filter((item) => item.rules.length > 0).map((item) => ({
29401
- moduleId: item.moduleId,
29402
- rules: item.rules
29403
- }));
29404
- scopeCtx.effect(() => mountRuleSources(scopeCtx, ruleSources, {
29405
- agent,
29406
- onOutcome: ruleDiagnostics.record,
29407
- variableStore,
29408
- standingMountFor,
29409
- manifestCheck
29410
- }));
29411
- if (personaActive) {
29412
- const capture = options.personaCapture;
29413
- const scope = scopeOf(scopeCtx);
29414
- scopeCtx.effect(() => {
29415
- const applied = [];
29416
- const rollback = () => {
29417
- while (applied.length > 0) applied.pop()?.();
29418
- };
29419
- try {
29420
- for (const section of plan.sections) {
29421
- (scope === void 0 ? void 0 : capture?.records(scope).find((record) => record.name === section.name))?.off();
29422
- applied.push(scopeCtx.systemPrompt.section(section));
29944
+ if (plan.sections.length > 0 || plan.suppressRuntimeContext) {
29945
+ const fiber = record.scope.plugin({
29946
+ name: "prompt-tool-persona",
29947
+ inject: ["systemPrompt"],
29948
+ apply: (scopeCtx) => {
29949
+ const applied = [], scope = scopeOf(scopeCtx);
29950
+ const rollback = () => {
29951
+ while (applied.length > 0) applied.pop()?.();
29952
+ };
29953
+ try {
29954
+ for (const section of plan.sections) {
29955
+ if (scope !== void 0) options.personaCapture?.records(scope).find((record) => record.name === section.name)?.off();
29956
+ applied.push(scopeCtx.systemPrompt.section(section));
29957
+ }
29958
+ if (plan.suppressRuntimeContext) applied.push(scopeCtx.systemPrompt.suppressRuntimeContext());
29959
+ } catch (error) {
29960
+ rollback();
29961
+ throw error;
29423
29962
  }
29424
- if (plan.suppressRuntimeContext) applied.push(scopeCtx.systemPrompt.suppressRuntimeContext());
29425
- } catch (error) {
29426
- rollback();
29427
- throw error;
29428
- }
29429
- return rollback;
29430
- }, "prompt-tool persona");
29431
- }
29432
- const applyCapability = async (capabilityCtx, item, module) => {
29433
- const result = await module.apply(capabilityCtx, module.config, {
29434
- agent,
29435
- sourceModuleId: item.moduleId,
29436
- sourceModuleDir: resolveModuleDir(item.moduleId, options.moduleRoot),
29437
- recoveryState,
29438
- contextRuntimeOf
29439
- });
29440
- if (typeof result === "function") capabilityCtx.effect(() => result, module.id);
29441
- };
29442
- const ownTokens = new Map(prepared.map((item) => [item, {}]));
29443
- for (const item of prepared) waiting.get(item)?.add(ownTokens.get(item));
29444
- for (const item of prepared) {
29445
- const capabilities = item.modules.filter((module) => PRIVATE_SERVICES[module.id] === void 0 || module.id === "retrieval");
29446
- if (capabilities.length === 0) continue;
29447
- const child = scopeCtx.plugin({
29448
- name: `prompt-tool-module:${item.moduleId}`,
29449
- apply: async (moduleCtx) => {
29450
- for (const module of capabilities) await applyCapability(moduleCtx, item, module);
29451
- }
29452
- });
29453
- observe(child, [item]);
29454
- try {
29455
- await child;
29456
- } catch (error) {
29457
- await child.dispose();
29458
- throw Object.assign(new Error(`模块 ${item.moduleId} 挂载失败:${failureReason(error)}`, { cause: error }), { sourceModuleId: item.moduleId });
29459
- }
29460
- }
29461
- const privateMounted = /* @__PURE__ */ new Set();
29462
- for (const item of prepared) for (const module of item.modules) {
29463
- if (PRIVATE_SERVICES[module.id] === void 0 || module.id === "retrieval" || privateMounted.has(module.id)) continue;
29464
- const owners = prepared.filter((source) => source.modules.some((candidate) => candidate.id === module.id));
29465
- const child = scopeCtx.plugin({
29466
- name: `prompt-tool-shared:${module.id}`,
29467
- apply: async (sharedCtx) => {
29468
- await applyCapability(sharedCtx, item, module);
29963
+ scopeCtx.effect(() => rollback, "prompt-tool persona");
29469
29964
  }
29470
29965
  });
29471
- observe(child, owners);
29472
29966
  try {
29473
- await child;
29967
+ await fiber;
29474
29968
  } catch (error) {
29475
- throw Object.assign(new Error(`模块 ${owners.map((owner) => owner.moduleId).join("、")} 共享能力 ${module.id} 初始化失败:${failureReason(error)}`, { cause: error }), { sourceModuleIds: owners.map((owner) => owner.moduleId) });
29969
+ await fiber.dispose();
29970
+ throw error;
29476
29971
  }
29477
- privateMounted.add(module.id);
29972
+ record.persona = {
29973
+ fiber,
29974
+ signature: personaKey
29975
+ };
29478
29976
  }
29479
- for (const item of prepared) waiting.get(item)?.delete(ownTokens.get(item));
29480
29977
  }
29481
- });
29482
- const disposeFiber = ctx.effect(() => () => {
29483
- live = false;
29484
- for (const off of observers) off();
29485
- return fiber.dispose();
29486
- }, `prompt-tool assembly ${agent.id}`);
29487
- try {
29488
- await fiber;
29489
- await initialization.settled(initializationRoots);
29490
- if (initializationErrors.length > 0) {
29491
- const failure = initializationErrors[0];
29492
- throw Object.assign(new Error(`模块 ${failure.owners.map((item) => item.moduleId).join("、")} 初始化失败:${failureReason(failure.error)}`, { cause: failure.error }), { sourceModuleIds: failure.owners.map((item) => item.moduleId) });
29978
+ if (!active || disposed.has(agent)) {
29979
+ await release(agent.id, agent);
29980
+ return;
29493
29981
  }
29494
- } catch (error) {
29495
- await disposeFiber();
29496
- throw error;
29497
- }
29498
- if (!active || disposed.has(agent)) {
29499
- await disposeFiber();
29500
- return;
29982
+ record.prepared = prepared;
29983
+ record.moduleIds = prepared.map((item) => item.moduleId);
29984
+ record.stamp = stamp;
29985
+ record.content = content;
29986
+ record.plan = plan;
29987
+ record.personaSignature = personaSignature;
29988
+ record.committed = true;
29989
+ for (const item of duplicateIdentities) {
29990
+ const where = item.channel === "kind" ? `显式 sourceKind ${JSON.stringify(item.identity)}` : `去重身份 ${JSON.stringify(item.identity)}`;
29991
+ warnOnce(item.suspectedCopy ? `模块 ${item.moduleIds.join("、")} 疑似由复制产生同一个${where}(dedupe: session/batch):去重按模块各记一份,两份正文都会注入,请改 rule id 或显式 identity 以区分` : `模块 ${item.moduleIds.join("、")} 声明了同一个${where}(dedupe: session/batch):去重按模块各记一份,两模块各自注入;同一模块内才共享这份去重`);
29992
+ }
29993
+ } finally {
29994
+ record.mutating = false;
29501
29995
  }
29502
- mounts.set(agent.id, {
29503
- agent,
29504
- fiber: { dispose: async () => {
29505
- await disposeFiber();
29506
- } },
29507
- prepared,
29508
- moduleIds: prepared.map((item) => item.moduleId),
29509
- stamp,
29510
- content,
29511
- plan
29512
- });
29513
- mounted = true;
29514
29996
  promote();
29515
29997
  };
29516
29998
  const assemblyStamp = (ids, personas) => JSON.stringify(personas) + readModulesRevision(options.moduleRoot, ids);
@@ -29534,7 +30016,23 @@ function createAgentAssembly(ctx, options) {
29534
30016
  }
29535
30017
  for (const file of files) if (!watched.has(file)) {
29536
30018
  const listener = () => {
29537
- if (active) for (const agent of knownAgents) ensureFresh(agent);
30019
+ if (!active) return;
30020
+ try {
30021
+ const ids = options.enabledModules(), personas = options.personaModules?.(), stamp = assemblyStamp(ids, personas);
30022
+ if (publishingContent !== void 0 && contentSignature(ids, personas) === publishingContent) return;
30023
+ const agents = /* @__PURE__ */ new Set([...knownAgents, ...ctx.get("agents")?.list() ?? []]);
30024
+ let changed = false;
30025
+ for (const agent of agents) {
30026
+ if (disposed.has(agent)) continue;
30027
+ const current = mounts.get(agent.id);
30028
+ if (current?.stamp === stamp) continue;
30029
+ if (current !== void 0 && current.content === contentSignature(ids, personas, current.content)) current.stamp = stamp;
30030
+ else changed = true;
30031
+ }
30032
+ if (changed) publication.schedule();
30033
+ } catch {
30034
+ publication.schedule();
30035
+ }
29538
30036
  };
29539
30037
  watched.set(file, listener);
29540
30038
  watchFile(file, {
@@ -29543,7 +30041,7 @@ function createAgentAssembly(ctx, options) {
29543
30041
  }, listener);
29544
30042
  }
29545
30043
  };
29546
- const replace = async (targets, retry = false) => {
30044
+ const replace = async (targets, retry = false, attempt = 0) => {
29547
30045
  readGlobalLayerSettings(options.moduleRoot);
29548
30046
  const ids = [...options.enabledModules()];
29549
30047
  const personaModules = options.personaModules?.();
@@ -29551,36 +30049,48 @@ function createAgentAssembly(ctx, options) {
29551
30049
  const live = targets.filter((agent) => active && !disposed.has(agent));
29552
30050
  const failures = [];
29553
30051
  const candidates = [];
30052
+ const signatureSnapshot = contentSignature(ids, personaModules);
30053
+ const versions = new Map(ids.map((id) => [id, contentSignature([id], void 0)]));
29554
30054
  for (const agent of live) {
29555
30055
  const previous = mounts.get(agent.id);
29556
30056
  if (retry === true) rejectedVersions.delete(agent);
29557
- else if (typeof retry === "string") rejectedVersions.get(agent)?.delete(retry);
30057
+ else if (Array.isArray(retry)) for (const id of retry) rejectedVersions.get(agent)?.delete(id);
29558
30058
  const good = lastGood.get(agent);
29559
30059
  const goodSnapshot = good === void 0 ? void 0 : new Map(good);
29560
30060
  const prepared = [];
29561
- for (const id of ids) try {
29562
- const item = await prepareAssembly(options.moduleRoot, id, (name) => agent.ctx.get(name) !== void 0);
29563
- const version = contentSignature([id], void 0);
29564
- sourceVersions.set(item, version);
29565
- if (rejectedVersions.get(agent)?.get(id)?.has(version)) {
30061
+ for (const id of ids) {
30062
+ try {
30063
+ const version = versions.get(id);
30064
+ const old = previous?.prepared.find((item) => item.moduleId === id);
30065
+ const item = old !== void 0 && sourceVersions.get(old) === version ? old : await prepareAssembly(options.moduleRoot, id, (name) => agent.ctx.get(name) !== void 0);
30066
+ sourceVersions.set(item, version);
30067
+ if (rejectedVersions.get(agent)?.get(id)?.has(version)) {
30068
+ failures.push(/* @__PURE__ */ new Error(`模块 ${id}(Agent ${agent.id})上次发布失败:${rejectedVersions.get(agent).get(id).get(version)}`));
30069
+ const fallback = good?.get(id);
30070
+ if (fallback !== void 0) prepared.push(fallback);
30071
+ } else {
30072
+ rejectedVersions.get(agent)?.delete(id);
30073
+ prepared.push(item);
30074
+ }
30075
+ } catch (error) {
30076
+ failures.push(new Error(`模块 ${id}(Agent ${agent.id})准备失败:${failureReason(error)}`, { cause: error }));
29566
30077
  const fallback = good?.get(id);
29567
30078
  if (fallback !== void 0) prepared.push(fallback);
29568
- } else {
29569
- rejectedVersions.get(agent)?.delete(id);
29570
- prepared.push(item);
29571
30079
  }
29572
- } catch (error) {
29573
- failures.push(new Error(`模块 ${id}(Agent ${agent.id})准备失败:${failureReason(error)}`, { cause: error }));
29574
- const fallback = good?.get(id);
29575
- if (fallback !== void 0) prepared.push(fallback);
30080
+ await new Promise((resolve) => setImmediate(resolve));
29576
30081
  }
29577
30082
  candidates.push({
29578
30083
  agent,
29579
30084
  prepared,
29580
- previous,
30085
+ previous: previous === void 0 ? void 0 : { ...previous },
29581
30086
  goodSnapshot
29582
30087
  });
29583
30088
  }
30089
+ if (JSON.stringify(options.enabledModules()) !== JSON.stringify(ids) || contentSignature(ids, options.personaModules?.()) !== signatureSnapshot) {
30090
+ if (attempt < 3) return replace(targets, retry, attempt + 1);
30091
+ publication.schedule();
30092
+ throw new Error("模块定义在准备期间持续变化,已安排重新发布");
30093
+ }
29584
30094
  const stamp = assemblyStamp(ids, personaModules);
29585
30095
  for (const item of candidates.flatMap((candidate) => candidate.prepared)) for (const id of item.skippedOfficialRows) warnOnce(`模块 ${item.moduleId} 声明官方 recipe 行 ${id};该行只能由会话原有预设提供,本插件不挂载官方包`);
29586
30096
  if (!active) return;
@@ -29611,11 +30121,15 @@ function createAgentAssembly(ctx, options) {
29611
30121
  for (const [index, candidate] of candidates.entries()) {
29612
30122
  if (!active || disposed.has(candidate.agent)) continue;
29613
30123
  changed.push(candidate);
29614
- await release(candidate.agent.id);
29615
- const content = contentSignature(ids, personaModules, candidate.previous?.content);
30124
+ const content = signatureSnapshot;
30125
+ const personaSignature = JSON.stringify(personaModules ?? candidate.prepared.map(({ moduleId, persona, variablesEnabled }) => ({
30126
+ moduleId,
30127
+ persona,
30128
+ variablesEnabled
30129
+ })));
29616
30130
  let plan = plans[index];
29617
30131
  while (true) try {
29618
- await mount(candidate.agent, candidate.prepared, plan, duplicateDedupeIdentities(candidate.prepared), stamp, content);
30132
+ await mount(candidate.agent, candidate.prepared, plan, duplicateDedupeIdentities(candidate.prepared), stamp, content, personaSignature);
29619
30133
  break;
29620
30134
  } catch (error) {
29621
30135
  const sourceIds = error !== null && typeof error === "object" ? "sourceModuleIds" in error && Array.isArray(error.sourceModuleIds) ? error.sourceModuleIds : "sourceModuleId" in error ? [error.sourceModuleId] : [] : [];
@@ -29623,7 +30137,7 @@ function createAgentAssembly(ctx, options) {
29623
30137
  if (failed.length === 0) throw error;
29624
30138
  for (const item of failed) {
29625
30139
  failures.push(new Error(`模块 ${item.moduleId}(Agent ${candidate.agent.id})发布失败:${failureReason(error)}`, { cause: error }));
29626
- reject(candidate.agent, item);
30140
+ reject(candidate.agent, item, error);
29627
30141
  }
29628
30142
  candidate.prepared = candidate.prepared.flatMap((item) => {
29629
30143
  if (!failed.includes(item)) return [item];
@@ -29632,18 +30146,24 @@ function createAgentAssembly(ctx, options) {
29632
30146
  });
29633
30147
  plan = planFor(candidate.agent, candidate.prepared);
29634
30148
  }
30149
+ await new Promise((resolve) => setImmediate(resolve));
29635
30150
  }
29636
30151
  } catch (error) {
29637
30152
  const restoreErrors = [];
29638
30153
  for (const { agent, previous, goodSnapshot } of changed.reverse()) {
29639
30154
  if (!active || disposed.has(agent)) continue;
29640
30155
  try {
29641
- await release(agent.id);
29642
30156
  if (goodSnapshot === void 0) lastGood.delete(agent);
29643
30157
  else lastGood.set(agent, new Map(goodSnapshot));
29644
- if (previous?.agent === agent) await mount(agent, previous.prepared, previous.plan, [], previous.stamp, previous.content);
30158
+ if (previous?.agent === agent) await mount(agent, previous.prepared, previous.plan, [], previous.stamp, previous.content, previous.personaSignature);
30159
+ else await release(agent.id, agent);
29645
30160
  } catch (restoreError) {
29646
30161
  restoreErrors.push(restoreError);
30162
+ try {
30163
+ await release(agent.id, agent);
30164
+ } catch (cleanupError) {
30165
+ restoreErrors.push(cleanupError);
30166
+ }
29647
30167
  }
29648
30168
  }
29649
30169
  if (restoreErrors.length > 0) throw new AggregateError([error, ...restoreErrors], `切换装配失败(${failureReason(error)}),且恢复原有装配也失败(${restoreErrors.map(failureReason).join(";")})`);
@@ -29658,12 +30178,40 @@ function createAgentAssembly(ctx, options) {
29658
30178
  const install = async (agent) => {
29659
30179
  knownAgents.add(agent);
29660
30180
  if (!active || disposed.has(agent) || mounts.get(agent.id)?.agent === agent) return;
29661
- await replace([agent]);
29662
- };
30181
+ publishingContent = contentSignature(options.enabledModules(), options.personaModules?.());
30182
+ try {
30183
+ await replace([agent]);
30184
+ } finally {
30185
+ publishingContent = void 0;
30186
+ }
30187
+ };
30188
+ const publication = createPublicationQueue({
30189
+ warn: (message) => warn(message),
30190
+ publish: (moduleIds, retry) => serial(async () => {
30191
+ if (!active) return;
30192
+ const enabled = options.enabledModules();
30193
+ const personas = options.personaModules?.();
30194
+ const live = [.../* @__PURE__ */ new Set([
30195
+ ...ctx.get("agents")?.list() ?? [],
30196
+ ...knownAgents,
30197
+ ...[...mounts.values()].map((mount) => mount.agent)
30198
+ ])].filter((agent) => !disposed.has(agent));
30199
+ const targets = live.filter((agent) => moduleIds === void 0 || moduleIds.some((id) => enabled.includes(id) || mounts.get(agent.id)?.moduleIds.includes(id) || (personas ?? []).some((item) => item.moduleId === id && item.persona?.[isDelegated(agent.session) ? "subagent" : "main"] !== void 0)));
30200
+ publishingContent = contentSignature(enabled, personas);
30201
+ try {
30202
+ await replace(targets, retry);
30203
+ } finally {
30204
+ publishingContent = void 0;
30205
+ }
30206
+ return targets.length === live.length;
30207
+ })
30208
+ });
29663
30209
  let lastReadError;
29664
- const ensureFresh = (agent) => serial(async () => {
30210
+ const ensureFresh = async (agent) => {
29665
30211
  if (!active || disposed.has(agent) || !knownAgents.has(agent)) return;
29666
30212
  try {
30213
+ await publication.flush(void 0, false);
30214
+ await pending;
29667
30215
  readGlobalLayerSettings(options.moduleRoot);
29668
30216
  const ids = options.enabledModules(), personas = options.personaModules?.();
29669
30217
  watchSources(ids, personas);
@@ -29671,7 +30219,10 @@ function createAgentAssembly(ctx, options) {
29671
30219
  const current = mounts.get(agent.id);
29672
30220
  if (current?.stamp !== stamp) {
29673
30221
  if (current?.content === contentSignature(ids, personas, current?.content)) current.stamp = stamp;
29674
- else await replace([agent]);
30222
+ else {
30223
+ publication.schedule();
30224
+ await publication.flush(void 0, false);
30225
+ }
29675
30226
  }
29676
30227
  lastReadError = void 0;
29677
30228
  } catch (error) {
@@ -29679,7 +30230,7 @@ function createAgentAssembly(ctx, options) {
29679
30230
  if (message !== lastReadError) warn(error);
29680
30231
  lastReadError = message;
29681
30232
  }
29682
- });
30233
+ };
29683
30234
  const listeners = [
29684
30235
  ctx.on("internal/get", (_caller, name, _error, next) => {
29685
30236
  const service = next();
@@ -29714,7 +30265,7 @@ function createAgentAssembly(ctx, options) {
29714
30265
  for (const [file, listener] of watched) unwatchFile(file, listener);
29715
30266
  watched.clear();
29716
30267
  knownAgents.clear();
29717
- return pending.then(async () => {
30268
+ return publication.dispose().then(() => pending).then(async () => {
29718
30269
  for (const id of mounts.keys()) await release(id);
29719
30270
  });
29720
30271
  }, "prompt-tool assembly runtime");
@@ -29728,24 +30279,22 @@ function createAgentAssembly(ctx, options) {
29728
30279
  let previous;
29729
30280
  do {
29730
30281
  await initialization.settled();
30282
+ await publication.settled();
29731
30283
  previous = pending;
29732
30284
  await previous;
29733
30285
  } while (previous !== pending);
29734
30286
  },
29735
- hasMounted: (sessionId) => mounts.has(sessionId),
30287
+ hasMounted: (sessionId) => mounts.get(sessionId)?.committed === true,
29736
30288
  moduleIds: (sessionId) => mounts.get(sessionId)?.moduleIds ?? [],
29737
30289
  ruleDiagnostics: () => active ? ruleDiagnostics.snapshot() : emptyRuleDiagnostics(),
30290
+ schedule: (moduleId) => {
30291
+ if (active) publication.schedule(moduleId);
30292
+ },
30293
+ publicationFailures: () => publication.failures(),
29738
30294
  refresh: async (moduleId) => {
29739
30295
  if (!active) throw new Error("运行时配装已释放");
29740
30296
  try {
29741
- await serial(async () => {
29742
- const enabled = options.enabledModules();
29743
- const targets = [.../* @__PURE__ */ new Set([...ctx.get("agents")?.list() ?? [], ...[...mounts.values()].map((mount) => mount.agent)])].filter((agent) => {
29744
- const current = mounts.get(agent.id);
29745
- return options.personaModules !== void 0 || moduleId === void 0 || current === void 0 || current.moduleIds.includes(moduleId) || enabled.includes(moduleId);
29746
- });
29747
- await replace(targets, moduleId ?? true);
29748
- });
30297
+ await publication.flush(moduleId, true);
29749
30298
  } catch (error) {
29750
30299
  throw new AggregateError([error], `运行时配装更新失败:${failureReason(error)}`);
29751
30300
  }
@@ -29756,6 +30305,7 @@ function createAgentAssembly(ctx, options) {
29756
30305
  for (const [file, listener] of watched) unwatchFile(file, listener);
29757
30306
  watched.clear();
29758
30307
  knownAgents.clear();
30308
+ await publication.dispose();
29759
30309
  await pending;
29760
30310
  for (const id of mounts.keys()) await release(id);
29761
30311
  }
@@ -29879,6 +30429,11 @@ function apply(ctx, configIn) {
29879
30429
  },
29880
30430
  capabilityChanged: (id) => id === void 0 ? assembly?.refresh() : refreshModuleRuntime(id),
29881
30431
  moduleListChanged: () => assembly?.refresh(),
30432
+ schedulePublication: (id, reason) => {
30433
+ if (reason === "installed") skillsRuntime.invalidate();
30434
+ assembly?.schedule(reason === "list" ? void 0 : id);
30435
+ },
30436
+ publicationFailures: () => assembly?.publicationFailures() ?? [],
29882
30437
  ruleDiagnostics: () => assembly?.ruleDiagnostics() ?? emptyRuleDiagnostics(),
29883
30438
  warn: (message) => warn(ctx, message)
29884
30439
  });