dsh-plugin-prompt-tool 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +17 -1
  2. package/engine/actions/catalog.mjs +2 -1
  3. package/engine/actions/index.mjs +5 -3
  4. package/engine/actions/shared.mjs +26 -12
  5. package/engine/actions/tool-visibility.mjs +31 -14
  6. package/engine/compositions/source/local/context-provenance.yml +9 -0
  7. package/engine/compositions/source/local/generation-recovery.yml +10 -0
  8. package/engine/compositions/source/local/retrieval.yml +7 -0
  9. package/engine/compositions/source/local/soul-ledger.yml +3 -0
  10. package/engine/compositions/source/local/soul-maintenance.yml +9 -0
  11. package/engine/conditions/examples.mjs +1 -0
  12. package/engine/conditions/index.mjs +7 -0
  13. package/engine/conditions/manifest.mjs +28 -0
  14. package/engine/context-idle-compaction.mjs +58 -0
  15. package/engine/context-provenance.d.mts +7 -0
  16. package/engine/context-provenance.mjs +156 -0
  17. package/engine/context-pruner.mjs +64 -0
  18. package/engine/context-routing.mjs +45 -0
  19. package/engine/context-summary.mjs +80 -0
  20. package/engine/fillers.mjs +37 -4
  21. package/engine/generation-recovery-coordinator.mjs +40 -0
  22. package/engine/generation-recovery-maintenance.mjs +18 -0
  23. package/engine/generation-recovery.mjs +153 -0
  24. package/engine/host-package.mjs +13 -0
  25. package/engine/layers.d.mts +4 -0
  26. package/engine/layers.mjs +57 -20
  27. package/engine/present-as.mjs +2 -2
  28. package/engine/retrieval-reference.d.mts +5 -0
  29. package/engine/retrieval-reference.mjs +26 -0
  30. package/engine/retrieval.mjs +17 -0
  31. package/engine/rule-engine.mjs +1 -0
  32. package/engine/rule-runtime.d.mts +8 -1
  33. package/engine/rule-runtime.mjs +18 -10
  34. package/engine/rule-spec.mjs +4 -2
  35. package/engine/schema.mjs +1 -1
  36. package/engine/service-names.d.mts +5 -0
  37. package/engine/service-names.mjs +5 -0
  38. package/engine/soul-ledger.mjs +8 -0
  39. package/engine/soul-maintenance.mjs +20 -0
  40. package/lib/client.js +16 -6
  41. package/lib/client.js.map +1 -1
  42. package/lib/index.mjs +1953 -246
  43. package/modules/soul/assets/ledger.md +4 -2
  44. package/modules/soul/assets/manifest.yml +2 -0
  45. package/modules/soul/assets/reference/00-layers.md +27 -27
  46. package/modules/soul/assets/reference/02-echo.md +161 -161
  47. package/modules/soul/assets/reference/05-doctor.md +67 -67
  48. package/modules/soul/assets/reference/10-echo-anatomy.md +109 -109
  49. package/modules/soul/assets/reference/ECHO-/346/235/203/345/250/201/345/217/243/345/276/204.md +202 -202
  50. package/modules/soul/assets/reference/ECHO/345/205/255/345/261/202/344/270/216mnemon/345/215/217/345/220/214/350/257/264/346/230/216/344/271/246.md +229 -229
  51. package/modules/soul/assets/templates/agents-template.md +3 -0
  52. package/modules/soul/assets/templates/self-template.md +19 -0
  53. package/modules/soul/module.yml +71 -13
  54. package/modules/tool-surface/module.yml +8 -5
  55. package/package.json +1 -1
  56. package/skills/dsh-module/reference.md +2 -2
  57. package/templates/80-tool-surface.yml +8 -7
package/engine/layers.mjs CHANGED
@@ -183,7 +183,7 @@ function textLayerGroups(configs) {
183
183
  }
184
184
 
185
185
  /** system-section:注册静态 system prompt 段(支持官方 {{variable}} 渲染与 merged 拼接)。 */
186
- function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered) {
186
+ function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered, sourceError) {
187
187
  const systemPrompt = getService(ctx, 'systemPrompt')
188
188
  if (systemPrompt === undefined || typeof systemPrompt.section !== 'function') {
189
189
  if (configs.length > 0) warnOnce(`${name}: systemPrompt service unavailable — system-section configs skipped`)
@@ -218,6 +218,7 @@ function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered) {
218
218
  keep(systemPrompt.suppressRuntimeContext(), `${name}: suppressRuntimeContext ${base.id}`)
219
219
  }
220
220
  } catch (error) {
221
+ if (sourceError !== undefined) throw sourceError(error, base.sourceModuleId)
221
222
  warnOnce(`${name}: system-section config ${base.id} failed: ${String(error?.message ?? error)}`)
222
223
  }
223
224
  }
@@ -240,7 +241,7 @@ async function resolvedContextText(ctx, config, context, registry, warnOnce) {
240
241
  }
241
242
 
242
243
  /** 条件化文本保留官方排序占位;仅在本次真实 assembly 填充,不缓存 context 身份。 */
243
- function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, ordered) {
244
+ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, ordered, sourceError) {
244
245
  if (!configs.length) return []
245
246
  const systemPrompt = getService(ctx, 'systemPrompt')
246
247
  if (systemPrompt === undefined) { warnOnce(`${name}: systemPrompt unavailable`); return [] }
@@ -252,8 +253,26 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
252
253
  const section = layer === 'system-section'
253
254
  const base = group[0]
254
255
  const entryName = textRegistrationName(base, section ? 'sectionName' : 'contextName')
255
- keep(systemPrompt[section ? 'section' : 'context']({ name: entryName, order: base.order, text: marker }), `${name}: conditional ${entryName}`)
256
- bindings.push({ group, name: entryName, collection: section ? 'sections' : 'contexts' })
256
+ // runtime-context 在官方 refreshContext 里按注册 provider 重算:provider 必须返回当前文本,
257
+ // 否则 assemble 钩子的填充会在请求前被 marker 覆盖。system-section 不重算,仍用 marker 占位。
258
+ // 文本槽按 Agent 分区:同一挂载服务多个 Agent,交错装配时不能互相读到对方的正文。
259
+ const holders = section ? undefined : new WeakMap()
260
+ const sharedHolder = section ? undefined : { text: '' }
261
+ const holderOf = section ? undefined : (context) => {
262
+ const key = context?.agent
263
+ if (key === undefined || key === null) return sharedHolder
264
+ let holder = holders.get(key)
265
+ if (holder === undefined) { holder = { text: '' }; holders.set(key, holder) }
266
+ return holder
267
+ }
268
+ try {
269
+ keep(systemPrompt[section ? 'section' : 'context']({
270
+ name: entryName,
271
+ order: base.order,
272
+ text: section ? marker : (context) => holderOf(context).text,
273
+ }), `${name}: conditional ${entryName}`)
274
+ } catch (error) { throw sourceError === undefined ? error : sourceError(error, base.sourceModuleId) }
275
+ bindings.push({ group, name: entryName, collection: section ? 'sections' : 'contexts', holderOf })
257
276
  ordered[section ? 'sections' : 'contexts'].push(orderedEntry(entryName, base))
258
277
  }
259
278
  let active = true
@@ -261,9 +280,13 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
261
280
  return [on('system-prompt/assemble', async (assembly, context, next, invocation) => {
262
281
  const frame = invocation ?? ruleFrame('system-prompt/assemble', [assembly, context], warnOnce)
263
282
  for (const binding of bindings) {
264
- const entry = assembly[binding.collection].find(item => item.name === binding.name && item.text === marker)
283
+ const entry = binding.collection === 'sections'
284
+ ? assembly.sections.find(item => item.name === binding.name && item.text === marker)
285
+ : assembly.contexts.find(item => item.name === binding.name)
265
286
  if (entry === undefined) continue
287
+ const holder = binding.holderOf === undefined ? undefined : binding.holderOf(context)
266
288
  entry.text = ''
289
+ if (holder !== undefined) holder.text = ''
267
290
  const blocks = []
268
291
  for (const config of binding.group) {
269
292
  if (!active || context.signal?.aborted || !actionMatches(config, frame) || !matchesAgentScope(config, context.agent)) continue
@@ -274,7 +297,10 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
274
297
  if (text.length) blocks.push(text)
275
298
  } catch (error) { warnOnce(`${name}: rule text ${config.id} failed: ${String(error?.message ?? error)}`) }
276
299
  }
277
- if (active && !context.signal?.aborted) entry.text = blocks.join('\n\n')
300
+ if (active && !context.signal?.aborted) {
301
+ entry.text = blocks.join('\n\n')
302
+ if (holder !== undefined) holder.text = entry.text
303
+ }
278
304
  }
279
305
  return next()
280
306
  })]
@@ -282,7 +308,7 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
282
308
  }
283
309
 
284
310
  /** runtime-context:注册运行时上下文;静态文本支持 merged,动态策略在官方 assembly waterfall 中填充。 */
285
- function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered) {
311
+ function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered, sourceError) {
286
312
  const disposers = []
287
313
  const systemPrompt = getService(ctx, 'systemPrompt')
288
314
  if (systemPrompt === undefined || typeof systemPrompt.context !== 'function') {
@@ -309,44 +335,55 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered
309
335
  }), `${name}: context ${base.id}`)
310
336
  ordered.contexts.push(orderedEntry(entryName, base))
311
337
  } catch (error) {
338
+ if (sourceError !== undefined) throw sourceError(error, base.sourceModuleId)
312
339
  if (base.linkedDelivery) throw error
313
340
  warnOnce(`${name}: runtime-context config ${base.id} failed: ${String(error?.message ?? error)}`)
314
341
  }
315
342
  }
316
- // 官方 provider 必须同步:先注册可渲染为空的私有变量占位,再在 waterfall 中填充。
317
- // 标记随官方排序/遮蔽进入本次 assembly,无会话缓存,也不依赖调用方 context 的对象身份。
343
+ // 官方 provider 必须同步:注册函数 provider 读取 holder,再在 waterfall 中填充。
344
+ // 官方 refreshContext 会用注册 provider 重算 contexts;返回 holder 当前值,填充才不会被 reset。
318
345
  const placeholders = configs.filter(needsResolver)
319
346
  .sort(compareTextPlacement)
320
347
  if (placeholders.length === 0) return disposers
321
- const slotVariable = `pt_runtime_${newMessageId('context').replace(/[^a-z0-9_]/gi, '_').toLowerCase()}`
322
- const slotText = `{{${slotVariable}}}`
323
348
  const registered = new Map()
324
349
  let active = true
325
350
  keep(() => { active = false }, `${name}: runtime-context lifecycle`)
326
- keep(systemPrompt.variable(slotVariable, () => ''), `${name}: runtime-context placeholder`)
327
351
  for (const config of placeholders) {
328
352
  try {
329
353
  const contextName = textRegistrationName(config, 'contextName')
354
+ // 文本槽按 Agent 分区:交错装配时 A 的 refresh 不得读到 B 的正文。
355
+ const holders = new WeakMap()
356
+ const sharedHolder = { text: '' }
357
+ const holderOf = (context) => {
358
+ const key = context?.agent
359
+ if (key === undefined || key === null) return sharedHolder
360
+ let holder = holders.get(key)
361
+ if (holder === undefined) { holder = { text: '' }; holders.set(key, holder) }
362
+ return holder
363
+ }
330
364
  keep(systemPrompt.context({
331
365
  name: contextName,
332
366
  order: config.order,
333
- text: slotText,
367
+ text: (context) => holderOf(context).text,
334
368
  }), `${name}: context ${config.id}`)
335
- registered.set(contextName, config)
369
+ registered.set(contextName, { config, holderOf })
336
370
  ordered.contexts.push(orderedEntry(contextName, config))
337
371
  } catch (error) {
372
+ if (sourceError !== undefined) throw sourceError(error, config.sourceModuleId)
338
373
  warnOnce(`${name}: runtime-context placeholder ${config.id} failed: ${String(error?.message ?? error)}`)
339
374
  }
340
375
  }
341
376
  disposers.push(on('system-prompt/assemble', async (assembly, context, next) => {
342
- const entries = assembly.contexts.filter(entry => registered.has(entry.name) && entry.text === slotText)
377
+ const entries = assembly.contexts.filter(entry => registered.has(entry.name))
343
378
  for (const entry of entries) {
344
- const config = registered.get(entry.name)
379
+ const { config, holderOf } = registered.get(entry.name)
380
+ const holder = holderOf(context)
345
381
  entry.text = ''
382
+ holder.text = ''
346
383
  if (!active || context.signal?.aborted) continue
347
384
  try {
348
385
  const text = await resolvedContextText(ctx, config, context, registry.get(config), warnOnce)
349
- if (active && !context.signal?.aborted) entry.text = text
386
+ if (active && !context.signal?.aborted) { entry.text = text; holder.text = text }
350
387
  } catch (error) {
351
388
  warnOnce(`${name}: runtime-context ${config.id} resolve failed: ${String(error?.message ?? error)}`)
352
389
  }
@@ -681,9 +718,9 @@ export function wireLayers(ctx, configs, warnOnce, options = {}) {
681
718
  && (typeof config.rule?.when === 'function' || typeof config.actionWhen === 'function'
682
719
  || (config.rule !== undefined || config.params?.templateEvaluation === true) && config.params?.complete !== true && config.params?.suppressRuntimeContext !== true))
683
720
  const regular = configs.filter(config => !conditional.includes(config))
684
- wireSystemSections(ctx, regular.filter((config) => config.layer === 'system-section'), registry, warnOnce, keep, ordered)
685
- wireRuntimeContexts(ctx, regular.filter((config) => config.layer === 'runtime-context'), registry, warnOnce, keep, on, ordered)
686
- wireRuleTextContributions(ctx, conditional, registry, warnOnce, keep, on, ordered)
721
+ wireSystemSections(ctx, regular.filter((config) => config.layer === 'system-section'), registry, warnOnce, keep, ordered, options.sourceError)
722
+ wireRuntimeContexts(ctx, regular.filter((config) => config.layer === 'runtime-context'), registry, warnOnce, keep, on, ordered, options.sourceError)
723
+ wireRuleTextContributions(ctx, conditional, registry, warnOnce, keep, on, ordered, options.sourceError)
687
724
  wireTurnStops(ctx, configs.filter((config) => config.layer === 'turn-stop'), warnOnce, on, options.turnStopBudgets)
688
725
  wireSubagentEvents(ctx, configs.filter((config) => config.layer === 'subagent-start' || config.layer === 'subagent-end'), warnOnce, on)
689
726
  // 官方 order 只提供初始槽位;configOrder 覆盖段/上下文的先后(平台段与其他插件段不动)。
@@ -13,8 +13,8 @@
13
13
  * declared for this scope`)——本行不捕获、不降级,让它响亮失败(装配失败优于静默换形态)。
14
14
  *
15
15
  * `ptc` 需要 `ctx.ptcRuntime`(宿主 base bundle 的 `ptc-runtime` 行提供):用 `ctx.inject`
16
- * 等它就绪再声明;等不到本行停在 pending,由预设注册表指名报告**不可用的行**——刻意不静默
17
- * 退回 native,否则「开了 PTC」的部署会悄悄以 native 跑。`native` 不需要运行时,直接声明。
16
+ * 等它就绪再声明;等不到本行停在 pending。受管装配观察初始化结果,声明失败时按来源
17
+ * 回退或撤销模块;本行不自行改成 native。`native` 不需要运行时,直接声明。
18
18
  *
19
19
  * disposer:`presentAs` 本身就是 effect(它用调用方的 ctx 注册、并把那个 disposer 还回来),
20
20
  * 本行不再包一层,随 fiber 释放即撤销。
@@ -0,0 +1,5 @@
1
+ export type RetrievalEvent = { readonly seq: number; readonly type: string }
2
+ export type SessionReference = { kind: 'session'; moduleId: string; sessionId: string; seqs: number[]; revision: string; start: number; end: number }
3
+ export const MAX_REFERENCE_CHARS: number
4
+ export function sessionReferenceText(events: readonly RetrievalEvent[], seqs: readonly number[]): string
5
+ export function makeSessionReference(moduleId: string, sessionId: string, events: readonly RetrievalEvent[], seqs: readonly number[], range?: { start?: number; end?: number }): SessionReference
@@ -0,0 +1,26 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ export const MAX_REFERENCE_CHARS = 8192
4
+
5
+ /** seq 是持久日志身份;不排序为数值区间,也不以相同正文替代。 */
6
+ export function sessionReferenceText(events, seqs) {
7
+ if (!Array.isArray(seqs) || seqs.length === 0 || seqs.length > 2048 || new Set(seqs).size !== seqs.length || seqs.some(seq => !Number.isSafeInteger(seq) || seq < 0)) throw new Error('检索引用事件身份无效')
8
+ const bySeq = new Map(events.map(event => [event.seq, event]))
9
+ let bytes = 0
10
+ return seqs.map(seq => {
11
+ const event = bySeq.get(seq)
12
+ if (!event) throw new Error('检索引用已失效')
13
+ const text = JSON.stringify(event)
14
+ bytes += Buffer.byteLength(text)
15
+ if (bytes > 64 * 1024 * 1024) throw new Error('检索引用来源超过64 MiB上限')
16
+ return text
17
+ }).join('\n')
18
+ }
19
+
20
+ export function makeSessionReference(moduleId, sessionId, events, seqs, range = {}) {
21
+ if (typeof moduleId !== 'string' || !moduleId || typeof sessionId !== 'string' || !sessionId) throw new Error('检索引用缺少真实来源身份')
22
+ const text = sessionReferenceText(events, seqs)
23
+ const start = range.start ?? 0, end = range.end ?? Math.min(text.length, start + MAX_REFERENCE_CHARS)
24
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end <= start || end > text.length || end - start > MAX_REFERENCE_CHARS) throw new Error('检索引用范围无效')
25
+ return { kind: 'session', moduleId, sessionId, seqs: [...seqs], revision: createHash('sha256').update(text).digest('hex'), start, end }
26
+ }
@@ -0,0 +1,17 @@
1
+ import { bool, defineConfig, stringList } from './fields.mjs'
2
+ import { toolServiceName } from './service-names.mjs'
3
+
4
+ export const name = 'retrieval'
5
+ export const inject = ['tools']
6
+ export const configContract = defineConfig({
7
+ enabled: bool({ default: false }),
8
+ paths: stringList({ default: () => [], allowEmpty: true, dedupe: true }),
9
+ sessions: stringList({ default: () => [], allowEmpty: true, dedupe: true }),
10
+ })
11
+
12
+ export function apply(ctx, raw, options) {
13
+ const config = configContract.parse(raw, name)
14
+ if (!config.enabled) return
15
+ const fiber = ctx.inject([toolServiceName(name)], scopeCtx => scopeCtx.effect(() => scopeCtx.get(toolServiceName(name)).mount(scopeCtx, config, options)))
16
+ return () => { void fiber.dispose() }
17
+ }
@@ -37,5 +37,6 @@ export async function apply(ctx, config) {
37
37
  const moduleId = typeof packet.moduleId === 'string' ? packet.moduleId : basename(dirname(file))
38
38
  const rules = compileRules(packet.rules, { moduleId, variables: packet.variables, variablesEnabled: packet.variablesEnabled, configOrder: packet.configOrder, promptConfigOptions: { templateBaseUrl, templateModuleRoot, strategyDir } })
39
39
  // 条件在挂载缝绑定(preset 需要挂载 scope 的 ctx);官方模块导出只作可选兜底。
40
+ // 独立装配路径没有插件侧服务,manifest 条件在这个入口恒不可用(见 docs/engine-reuse.md)。
40
41
  return mountRuleSources(ctx, [{ moduleId, rules }], { standingMountFor: await loadStandingMountFor() })
41
42
  }
@@ -11,6 +11,11 @@ export interface RuleVariableStore {
11
11
  publish(moduleId: string): void
12
12
  }
13
13
 
14
+ /** 清单检查事实通道(宿主读取器);引擎只调用 read,undefined 表示不可用。 */
15
+ export interface RuleManifestCheck {
16
+ read(moduleId: string): boolean | undefined
17
+ }
18
+
14
19
  /** 判定上报载荷;仅覆盖插件依赖面(宿主按 `RuleOutcomeReport` 消费它)。 */
15
20
  export interface RuleOutcomeReport {
16
21
  moduleId?: string
@@ -30,6 +35,8 @@ export interface MountRuleSourcesOptions {
30
35
  plugin?: string
31
36
  warnOnce?: WarnOnce
32
37
  variableStore?: RuleVariableStore
38
+ /** 清单 checks 事实;缺省=manifest 条件恒不可用。 */
39
+ manifestCheck?: RuleManifestCheck
33
40
  /** 官方预设兜底;缺省=没有兜底(预设条件恒不命中)。 */
34
41
  standingMountFor?: StandingMountFor
35
42
  /** 注入的晋升相位实例对;缺省从协调器或自建池取。 */
@@ -38,7 +45,7 @@ export interface MountRuleSourcesOptions {
38
45
  onOutcome?(report: RuleOutcomeReport): void
39
46
  }
40
47
 
41
- /** 所有规则来源的唯一装配入口;返回释放本次挂载的 disposer。 */
48
+ /** 所有规则来源的唯一装配入口;返回 disposer。可归因挂载错误携带 sourceModuleId,原错误保留在 cause。 */
42
49
  export function mountRuleSources(
43
50
  ctx: unknown,
44
51
  sources: ReadonlyArray<RuleSourceLike>,
@@ -16,6 +16,8 @@ const noNext = new Set(['agent/inbox/inserted', 'agent/turn-stopping', 'subagent
16
16
  const compare = (a, b) => a.action.channelOrder - b.action.channelOrder
17
17
  || a.moduleId.localeCompare(b.moduleId) || a.ruleIndex - b.ruleIndex || a.action.actionIndex - b.action.actionIndex
18
18
 
19
+ const sourceError = (error, moduleId) => Object.assign(new Error(String(error?.message ?? error), { cause: error }), { sourceModuleId: moduleId })
20
+
19
21
  /** 所有来源只进入这个装配入口;受众限定固定效果要求 ctx 属于 options.agent 的 scope。 */
20
22
  export function mountRuleSources(ctx, sources, options = {}) {
21
23
  validateDeliverySources(sources)
@@ -32,7 +34,6 @@ export function mountRuleSources(ctx, sources, options = {}) {
32
34
  // 条件绑定**在这里**发生:`compileRules` 编译期没有挂载 scope 的 ctx(preset 条件在那边恒
33
35
  // UNAVAILABLE 且静默)。必须早于事件订阅与动作注册——动作级 `actionWhen.observe` 是订阅通道,
34
36
  // 绑晚了那一类谓词会漏事件。`standingMountFor` 是可选兜底(官方包缺席=没有兜底,不是错误)。
35
- rebindConditions(sources, { ctx, standingMountFor: options.standingMountFor, promotionPool: pool, variableStore: options.variableStore })
36
37
  const warnOnce = options.warnOnce ?? createWarnOnce(ctx, options.plugin ?? 'rule-engine')
37
38
  // 非 pre-step 动作仍共享一次 ruleFrame;规则实例没有来源字段(RuleDefinition 不暴露 moduleId),
38
39
  // 因此保留一份局部 rule→module 映射。它不是 pre-step 来源事实的重建,不参与 pre-step 的 source sink。
@@ -50,19 +51,23 @@ export function mountRuleSources(ctx, sources, options = {}) {
50
51
  // 因此按 moduleId 分档;来源 id 缺失时落空串兜底键(与 wireTurnStops 同源)。
51
52
  const turnStopBudgets = new Map()
52
53
  const injections = new Map(sources.map(source => [source.moduleId, { moduleId: source.moduleId, sourceId: moduleSourceId(source.moduleId), configs: [], ruleActions: [] }]))
53
- const rules = sources.flatMap(source => source.rules.filter(rule => rule.enabled !== false))
54
54
  // 动作级分支条件(actionWhen)也要喂 session/event:否则 else / 嵌套 if 里的 phase / count
55
55
  // 谓词永远收不到事件,状态停在冷扫那一刻(规则级 when 已由 rules 覆盖,动作级单独补齐)。
56
- const observer = wireTriggerObservers(ctx, [
57
- ...rules,
58
- ...rules.flatMap(rule => (rule.actions ?? [])
59
- .filter(action => typeof action.actionWhen?.observe === 'function')
60
- .map(action => ({ id: action.id, when: action.actionWhen }))),
61
- ], { plugin: options.plugin ?? 'rule-engine', warnOnce })
62
- if (observer) releases.push(observer)
63
56
  let active = true
64
57
  const bind = (item, on) => prepareAction(item.action, { plugin: options.plugin, warnOnce, promptConfigOptions: item.rule.promptConfigOptions, on })(ctx)
65
58
  try {
59
+ for (const source of sources) {
60
+ try { rebindConditions([source], { ctx, standingMountFor: options.standingMountFor, promotionPool: pool, variableStore: options.variableStore, manifestCheck: options.manifestCheck }) }
61
+ catch (error) { throw sourceError(error, source.moduleId) }
62
+ }
63
+ const rules = sources.flatMap(source => source.rules.filter(rule => rule.enabled !== false))
64
+ const observer = wireTriggerObservers(ctx, [
65
+ ...rules,
66
+ ...rules.flatMap(rule => (rule.actions ?? [])
67
+ .filter(action => typeof action.actionWhen?.observe === 'function')
68
+ .map(action => ({ id: action.id, when: action.actionWhen }))),
69
+ ], { plugin: options.plugin ?? 'rule-engine', warnOnce })
70
+ if (observer) releases.push(observer)
66
71
  for (const source of sources) for (const [ruleIndex, rule] of source.rules.entries()) {
67
72
  if (rule.enabled === false) continue
68
73
  for (const action of rule.actions) {
@@ -82,6 +87,7 @@ export function mountRuleSources(ctx, sources, options = {}) {
82
87
  releases.push(wireLayers(ctx, textConfigs, warnOnce, {
83
88
  agent: options.agent,
84
89
  turnStopBudgets,
90
+ sourceError,
85
91
  on: (channel, handler) => {
86
92
  if (channel !== point.channel) throw new TypeError(`rule action registered outside ${point.channel}: ${channel}`)
87
93
  handlers.push({ handler }); return () => {}
@@ -109,7 +115,9 @@ export function mountRuleSources(ctx, sources, options = {}) {
109
115
  // 这里再返回一个真 disposer 会与它重复释放同一批 handler。
110
116
  return () => {}
111
117
  }
112
- const dispose = bind(item, on)
118
+ let dispose
119
+ try { dispose = bind(item, on) }
120
+ catch (error) { throw sourceError(error, moduleId) }
113
121
  // 未命中通知:条件为假时动作不会进入 handler,撤销型动作(tool-visibility 的执行闸)
114
122
  // 只能靠这条回调收手——否则第一轮为真后永久留闸。
115
123
  if (typeof dispose?.miss === 'function') {
@@ -231,8 +231,8 @@ export function compileRules(specs, options = {}) {
231
231
  if (!Array.isArray(specs)) throw new TypeError('rules must be an array')
232
232
  // 条件的绑定**不在编译期**:`preset` 谓词的取法要挂载 scope 的 ctx(`conditions/preset.mjs`),
233
233
  // 准备期拿不到。这里显式剥掉挂载期选项——传了也不生效,免得调用方以为已经绑好。
234
- // variableStore 与 ctx/standingMountFor 同理:绑定发生在挂载期,准备期传了也不生效。
235
- const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, ...conditionOptions } = options
234
+ // variableStore / manifestCheck 与 ctx/standingMountFor 同理:绑定发生在挂载期,准备期传了也不生效。
235
+ const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, ...conditionOptions } = options
236
236
  const ids = new Set()
237
237
  const pendingConfigs = []
238
238
  const moduleId = options.moduleId ?? options.sourceModuleId
@@ -411,6 +411,8 @@ function needsMountContext(node) {
411
411
  if (Object.hasOwn(node, 'preset')) return true
412
412
  // variable 要读挂载 scope 的变量适配器(准备期没有)。
413
413
  if (Object.hasOwn(node, 'variable')) return true
414
+ // manifest 要读挂载 scope 的清单检查服务(准备期没有)。
415
+ if (Object.hasOwn(node, 'manifest')) return true
414
416
  if (Object.hasOwn(node, 'phase') && node.phase?.subscribe !== false) return true
415
417
  if (node.anchor?.after !== undefined) return true
416
418
  return Object.values(node).some(needsMountContext)
package/engine/schema.mjs CHANGED
@@ -198,7 +198,7 @@ export const KNOWN_MODEL_SCOPES = new Set(['all', 'pro', 'flash'])
198
198
  * 自定义策略或模板 patch 的角色仍由 executor 在出口校验,避免写出非法事件。
199
199
  */
200
200
  export const KNOWN_ROLES = new Set(['user'])
201
- export const KNOWN_FILLS = new Set(['instruction-hint', 'env-facts', 'skill-catalog', 'tool-catalog'])
201
+ export const KNOWN_FILLS = new Set(['instruction-hint', 'env-facts', 'skill-catalog', 'tool-catalog', 'soul-self'])
202
202
 
203
203
  /** 层能力矩阵:每个字段只在对应注入层生效。客户端表单据此动态渲染。 */
204
204
  export const LAYER_FIELD_POLICIES = Object.fromEntries(
@@ -5,6 +5,11 @@ export const PRE_STEP_COORDINATOR_SERVICE: string
5
5
 
6
6
  /** 发现引擎注册表服务名:宿主 root provide,adapter 行按 agent 注册,动作按 agent 解析。 */
7
7
  export const DISCOVERY_SERVICE: string
8
+ export const SOUL_SELF_SERVICE: string
9
+ export const SOUL_MAINTENANCE_SERVICE: string
10
+
11
+ /** 模块清单 checks 事实:宿主提供,宿主装配读取后经挂载 options 传给谓词。 */
12
+ export const MANIFEST_CHECK_SERVICE: 'promptToolManifestCheck'
8
13
 
9
14
  /** 四个模块工具外壳的服务名:`pt-<引擎模块 id>`。前缀是本函数的内部细节。 */
10
15
  export function toolServiceName(engineModuleId: string): string
@@ -3,6 +3,11 @@ export const PRE_STEP_COORDINATOR_SERVICE = 'promptToolPreStep'
3
3
 
4
4
  /** 发现引擎注册表:宿主 root provide,adapter 行按 agent 注册,动作按 agent 解析。 */
5
5
  export const DISCOVERY_SERVICE = 'promptToolDiscovery'
6
+ export const SOUL_SELF_SERVICE = 'promptToolSoulSelf'
7
+ export const SOUL_MAINTENANCE_SERVICE = 'promptToolSoulMaintenance'
8
+
9
+ /** 模块清单 checks 事实:宿主提供,宿主装配读取后经挂载 options 传给谓词。 */
10
+ export const MANIFEST_CHECK_SERVICE = 'promptToolManifestCheck'
6
11
 
7
12
  /** 五个模块工具外壳的服务名:`pt-<引擎模块 id>`。前缀是本函数的内部细节。 */
8
13
  export function toolServiceName(engineModuleId) {
@@ -0,0 +1,8 @@
1
+ import { toolServiceName } from './service-names.mjs'
2
+
3
+ export const name = 'soul-ledger'
4
+ export function apply(ctx) {
5
+ ctx.inject([toolServiceName(name)], scopeCtx => {
6
+ scopeCtx.effect(() => scopeCtx.get(toolServiceName(name)).mount(scopeCtx), name + ': mount')
7
+ })
8
+ }
@@ -0,0 +1,20 @@
1
+ import { bool, defineConfig, enumOf, passthrough } from './fields.mjs'
2
+ import { SOUL_MAINTENANCE_SERVICE } from './service-names.mjs'
3
+
4
+ export const name = 'soul-maintenance'
5
+ const bounded = (fallback, min, max) => passthrough((value = fallback, plugin, field) => {
6
+ if (!Number.isSafeInteger(value) || value < min || value > max) throw new TypeError(`${plugin}: ${field} must be an integer from ${min} to ${max}`)
7
+ return value
8
+ })
9
+ export const configContract = defineConfig({
10
+ enabled: bool({ default: false }), intervalMs: bounded(60000, 1000, 2147483647),
11
+ wake: bool({ default: false }), audience: enumOf(['all', 'main', 'subagent'], { default: 'all' }),
12
+ budgetBytes: bounded(12288, 1024, 65536),
13
+ })
14
+ export function apply(ctx, raw, options = {}) {
15
+ const config = configContract.parse(raw, name)
16
+ if (!config.enabled) return
17
+ ctx.inject([SOUL_MAINTENANCE_SERVICE], scopeCtx => {
18
+ scopeCtx.effect(() => scopeCtx.get(SOUL_MAINTENANCE_SERVICE).mount(scopeCtx, config, options), name + ': mount')
19
+ })
20
+ }
package/lib/client.js CHANGED
@@ -742,7 +742,10 @@ window.__ModuleLoader__.load({
742
742
  fileRead: "module_file_read",
743
743
  fileAppend: "module_file_append",
744
744
  manifest: "module_manifest",
745
- variables: "module_variables"
745
+ variables: "module_variables",
746
+ soulLedger: "soul_ledger",
747
+ search: "module_search",
748
+ open: "module_open"
746
749
  };
747
750
  const MODULE_TOOL_CAPABILITIES = {
748
751
  [MODULE_TOOL_NAMES.import]: "module-import-tools",
@@ -751,7 +754,10 @@ window.__ModuleLoader__.load({
751
754
  [MODULE_TOOL_NAMES.fileRead]: "module-file-tools",
752
755
  [MODULE_TOOL_NAMES.fileAppend]: "module-file-tools",
753
756
  [MODULE_TOOL_NAMES.manifest]: "module-manifest-tools",
754
- [MODULE_TOOL_NAMES.variables]: "module-variable-tools"
757
+ [MODULE_TOOL_NAMES.variables]: "module-variable-tools",
758
+ [MODULE_TOOL_NAMES.soulLedger]: "soul-ledger",
759
+ [MODULE_TOOL_NAMES.search]: "retrieval",
760
+ [MODULE_TOOL_NAMES.open]: "retrieval"
755
761
  };
756
762
  /** 旧公开类型名只作输入兼容。 */
757
763
  /**
@@ -10469,6 +10475,7 @@ window.__ModuleLoader__.load({
10469
10475
  "triggers.label.variable": "变量状态",
10470
10476
  "triggers.label.op": "比较方式",
10471
10477
  "triggers.label.value": "比较值",
10478
+ "triggers.label.manifest": "清单检查",
10472
10479
  "triggers.label.after": "限定在动作投递之后",
10473
10480
  "triggers.label.afterAction": "起点动作标识",
10474
10481
  "triggers.option.userMessage": "用户消息",
@@ -10494,6 +10501,7 @@ window.__ModuleLoader__.load({
10494
10501
  "triggers.backToSettings": "返回本层参数",
10495
10502
  "triggers.label.text": "文本",
10496
10503
  "triggers.label.phase": "阶段",
10504
+ "triggers.label.ok": "检查通过",
10497
10505
  "triggers.label.source": "消息来源",
10498
10506
  "triggers.label.count": "事件计数",
10499
10507
  "triggers.label.names": "名称名单",
@@ -10549,7 +10557,7 @@ window.__ModuleLoader__.load({
10549
10557
  "triggers.label.allowFrom": "按需解锁",
10550
10558
  "triggers.label.tool": "发现工具",
10551
10559
  "triggers.label.key": "键名",
10552
- "triggers.label.requireMatch": "名单工具缺失时保留完整目录",
10560
+ "triggers.label.requireMatch": "名单工具全部缺失时保留完整目录",
10553
10561
  "triggers.label.keep": "仅保留",
10554
10562
  "triggers.label.remove": "移除",
10555
10563
  "triggers.label.clear": "清空全部",
@@ -10693,6 +10701,7 @@ window.__ModuleLoader__.load({
10693
10701
  "triggers.label.variable": "Variable state",
10694
10702
  "triggers.label.op": "Comparison",
10695
10703
  "triggers.label.value": "Comparison value",
10704
+ "triggers.label.manifest": "Manifest check",
10696
10705
  "triggers.label.after": "Observe after action delivery",
10697
10706
  "triggers.label.afterAction": "Starting action ID",
10698
10707
  "triggers.option.userMessage": "User message",
@@ -10718,6 +10727,7 @@ window.__ModuleLoader__.load({
10718
10727
  "triggers.backToSettings": "Back to layer parameters",
10719
10728
  "triggers.label.text": "Text",
10720
10729
  "triggers.label.phase": "Phase",
10730
+ "triggers.label.ok": "Checks passed",
10721
10731
  "triggers.label.source": "Message source",
10722
10732
  "triggers.label.count": "Event count",
10723
10733
  "triggers.label.names": "Name list",
@@ -10773,7 +10783,7 @@ window.__ModuleLoader__.load({
10773
10783
  "triggers.label.allowFrom": "On-demand unlocking",
10774
10784
  "triggers.label.tool": "Discovery tool",
10775
10785
  "triggers.label.key": "Key",
10776
- "triggers.label.requireMatch": "Keep the full catalog if a listed tool is missing",
10786
+ "triggers.label.requireMatch": "Keep the full catalog only if no listed tool exists",
10777
10787
  "triggers.label.keep": "Keep only",
10778
10788
  "triggers.label.remove": "Remove",
10779
10789
  "triggers.label.clear": "Clear all",
@@ -18457,7 +18467,7 @@ window.__ModuleLoader__.load({
18457
18467
  "rules.surface.dynamic": "按需解锁",
18458
18468
  "rules.surface.discoveryTool": "发现工具",
18459
18469
  "rules.surface.argumentKey": "名单参数",
18460
- "rules.surface.requireMatch": "工具缺失时保留全部",
18470
+ "rules.surface.requireMatch": "工具全部缺失时保留全部",
18461
18471
  "rules.surface.textMode": "过滤方式",
18462
18472
  "rules.surface.unrestricted": "不过滤",
18463
18473
  "rules.surface.keep": "仅保留",
@@ -18582,7 +18592,7 @@ window.__ModuleLoader__.load({
18582
18592
  "rules.surface.dynamic": "Unlock on demand",
18583
18593
  "rules.surface.discoveryTool": "Discovery tool",
18584
18594
  "rules.surface.argumentKey": "List argument",
18585
- "rules.surface.requireMatch": "Keep all if any tool is missing",
18595
+ "rules.surface.requireMatch": "Keep all only if no tool exists",
18586
18596
  "rules.surface.textMode": "Filter",
18587
18597
  "rules.surface.unrestricted": "No filter",
18588
18598
  "rules.surface.keep": "Keep only",