dsh-plugin-prompt-tool 3.1.3 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/engine/compaction-epoch.d.mts +1 -0
- package/engine/conditions/composite.mjs +6 -1
- package/engine/conditions/evaluation.mjs +22 -3
- package/engine/conditions/examples.mjs +1 -0
- package/engine/conditions/index.mjs +4 -0
- package/engine/conditions/soul.mjs +15 -0
- package/engine/delivery.d.mts +1 -0
- package/engine/executor.mjs +4 -1
- package/engine/fillers.mjs +2 -1
- package/engine/generation-recovery.d.mts +6 -0
- package/engine/generation-recovery.mjs +53 -4
- package/engine/layers.mjs +7 -2
- package/engine/retrieval-reference.d.mts +1 -1
- package/engine/retrieval.mjs +2 -1
- package/engine/rule-runtime.d.mts +4 -0
- package/engine/rule-runtime.mjs +3 -2
- package/engine/rule-spec.mjs +7 -1
- package/engine/soul-maintenance.mjs +5 -0
- package/lib/client.js +2 -0
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +1631 -166
- package/modules/soul/assets/ledger.md +4 -0
- package/modules/soul/assets/retrieval-synonyms.json +8 -0
- package/modules/soul/assets/tools.md +4 -0
- package/modules/soul/module.yml +27 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ persona:
|
|
|
112
112
|
| module-file-tools | module_file_read / module_file_append |
|
|
113
113
|
| module-manifest-tools | module_manifest |
|
|
114
114
|
| module-import-tools | module_import |
|
|
115
|
-
| soul-ledger | soul_ledger |
|
|
115
|
+
| soul-ledger | soul_ledger / soul_state |
|
|
116
116
|
| retrieval | module_search / module_open |
|
|
117
117
|
| tool-config-engine | customTools 中的实际工具 |
|
|
118
118
|
|
|
@@ -134,11 +134,17 @@ outlet/vectorized/triggers 不能等价执行,相关规则禁用并报告;
|
|
|
134
134
|
|
|
135
135
|
Soul 模块包含结构化账本、授权文件/历史检索、每步动态 SELF、上下文压缩与裁剪溯源、生成恢复和维护心跳。SELF 由 `rule-engine` 的内置动态内容源提供(`strategy: placeholder`、`fill: soul-self`),无需模块附带策略 JS。先用上述初始化预览补齐 `SELF.src.md` 骨架;已有模块副本须显式更新,包升级不会覆盖用户定义或私有数据,旧副本仍可使用自身保留的自定义策略。
|
|
136
136
|
|
|
137
|
-
`soul_ledger`
|
|
137
|
+
`soul_ledger` 自动署名真实会话;`lead` 需宿主审批,`promise` 仅当前主导会话追加/收口,`shelved` 可由原作者或当前主导收口。`close` 通过稳定 `targetId` 追加完成/取消及依据,保留历史原行;`list` 的 `pending/open/pending-review` 视图提供版本分页。升级前历史事项标为待核对,不猜测已完成。
|
|
138
|
+
|
|
139
|
+
SELF 每步读取当前骨架、账本投影和身份/时钟,维护关闭或诊断快照陈旧也能呈现;索引只报告最近成功观测,不逐步扫描历史。`soul_state status/mark/check` 提供当前会话状态、带版本的短标记及只读运行诊断。未结提醒默认仅主会话,在集合变化或成功压缩后实际投递一次;候选装配不消费提醒。Soul 显式开启 45/60/80% 水位提示,只提示,不自动压缩。
|
|
140
|
+
|
|
141
|
+
`module_search` 支持全半角/大小写/千分位归一化、Soul 声明的有界近义表及 `mode: timeline` 时间线;命中标注方式,引用交给 `module_open` 重验授权与原文。历史会话仍只读白名单。维护默认每分钟更新索引与诊断快照,等待可取消,`wake` 独立且默认关闭。
|
|
138
142
|
|
|
139
143
|
`generation-recovery` 的通用默认只截断;Soul 显式启用空回合检测和有界恢复,主会话额度 3、子代理 1,每会话独立且重挂不清零。维护压缩和扩大范围默认关闭。配置与边界见 [引擎运行时](docs/engine-reuse.md#重复输出安全截断)、[账本与SELF](docs/architecture-params.md#soul账本与派生快照)。
|
|
140
144
|
|
|
141
|
-
这些运行能力由本插件实现,不需要源项目 `dsh-agent-soul` 的仓库、插件或后台服务;上下文治理仍要求宿主提供[官方能力包](docs/engine-reuse.md#上下文运行时与溯源)
|
|
145
|
+
这些运行能力由本插件实现,不需要源项目 `dsh-agent-soul` 的仓库、插件或后台服务;上下文治理仍要求宿主提供[官方能力包](docs/engine-reuse.md#上下文运行时与溯源)。未提供向量库、规模持久索引、压缩帧直取、附属面板、源安装体系或恢复补额/惩罚。`assets/reference/` 保留源背景文档,其 CLI 不代表本插件接口。
|
|
146
|
+
|
|
147
|
+
已有 Soul 定义通过 `soul_state check` 显式传 `includeUpgrade: true` 查看定向升级候选及冲突;完整 `files` 含必要配置、近义词表和现有可交换资产,再交 `module_import` 模型工具预览确认及重建,该入口保留私有数据、记忆和技能资产。包内种子不覆盖用户副本。包含 `close` 的账本要求支持账本 v2 的程序:旧版会忽略收口且仍能写入,回滚前必须撤写能力及旧 SELF/提醒贡献,不能删除 close 行迁就旧版。
|
|
142
148
|
|
|
143
149
|
上下文能力按包名白名单从 DSH 自带包解析,不增加插件依赖,运行期不校验版本;仅包缺失或未登记时拒绝该能力装配。包代码更新需要用户重启 DSH 加载;加载后修改模块启停/参数沿运行时刷新,不改官方 preset 或 profile patch。
|
|
144
150
|
|
|
@@ -23,3 +23,4 @@ export interface EpochPromotionOptions {
|
|
|
23
23
|
|
|
24
24
|
/** 建一份相位追踪器;`promoteEvents` 是 durable 事件类型集合(见 `shared.mjs#PROMOTE_EVENTS`)。 */
|
|
25
25
|
export function createEpochPromotion(promoteEvents: Iterable<string>, options?: EpochPromotionOptions): PromotionTracker
|
|
26
|
+
export function isSuccessfulCompactionEnd(event: unknown): boolean
|
|
@@ -35,21 +35,26 @@ export function composite(node) {
|
|
|
35
35
|
const children = list.map(composite)
|
|
36
36
|
const predicates = {
|
|
37
37
|
[MATCH_LOGIC.ANY]: (payload) => {
|
|
38
|
+
predicate.soulReminders = []
|
|
38
39
|
let unknown = false
|
|
39
40
|
for (const child of children) {
|
|
40
41
|
const result = child(payload)
|
|
41
42
|
if (result === UNAVAILABLE) unknown = true
|
|
42
|
-
else if (result) return true
|
|
43
|
+
else if (result) { predicate.soulReminders = child.soulReminders ?? []; return true }
|
|
43
44
|
}
|
|
44
45
|
return unknown ? UNAVAILABLE : false
|
|
45
46
|
},
|
|
46
47
|
[MATCH_LOGIC.ALL]: (payload) => {
|
|
48
|
+
predicate.soulReminders = []
|
|
49
|
+
const reminders = []
|
|
47
50
|
let unknown = false
|
|
48
51
|
for (const child of children) {
|
|
49
52
|
const result = child(payload)
|
|
50
53
|
if (result === UNAVAILABLE) unknown = true
|
|
51
54
|
else if (!result) return false
|
|
55
|
+
if (result === true) reminders.push(...(child.soulReminders ?? []))
|
|
52
56
|
}
|
|
57
|
+
if (!unknown) predicate.soulReminders = reminders
|
|
53
58
|
return unknown ? UNAVAILABLE : true
|
|
54
59
|
},
|
|
55
60
|
[MATCH_LOGIC.NOT_ANY]: (payload) => {
|
|
@@ -9,7 +9,7 @@ import { UNAVAILABLE } from './availability.mjs'
|
|
|
9
9
|
* 缺省不传即零开销,也不改变任何判定结果。
|
|
10
10
|
*/
|
|
11
11
|
export function ruleFrame(channel, args, warnOnce = () => {}, ctx, onOutcome) {
|
|
12
|
-
return { channel, args, subject: subjectOf(channel, args, warnOnce, ctx), decisions: new Map(), actionDecisions: new Set(), warnOnce, onOutcome }
|
|
12
|
+
return { channel, args, subject: subjectOf(channel, args, warnOnce, ctx), decisions: new Map(), actionDecisions: new Set(), reminders: new Map(), warnOnce, onOutcome }
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** 条件返回值的三值归一:词表与 bridge 的 `RuleOutcomeKind` 共享,故只此一处定义。 */
|
|
@@ -42,7 +42,7 @@ function decideRule(rule, frame) {
|
|
|
42
42
|
frame.warnOnce(`rule ${rule.id} condition failed: ${String(error?.message ?? error)}`)
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
const decided = { hit, outcome }
|
|
45
|
+
const decided = { hit, outcome, reminders: hit ? rule.when?.soulReminders ?? [] : [] }
|
|
46
46
|
frame.decisions.set(rule, decided)
|
|
47
47
|
return decided
|
|
48
48
|
}
|
|
@@ -70,7 +70,11 @@ export function ruleMatches(rule, frame) {
|
|
|
70
70
|
*/
|
|
71
71
|
export function actionMatches(entry, frame) {
|
|
72
72
|
const branched = entry.actionWhen !== undefined || entry.bypassRuleWhen === true
|
|
73
|
-
if (!branched)
|
|
73
|
+
if (!branched) {
|
|
74
|
+
const hit = ruleMatches(entry.rule, frame)
|
|
75
|
+
frame.reminders.set(entry, hit ? decideRule(entry.rule, frame).reminders ?? [] : [])
|
|
76
|
+
return hit
|
|
77
|
+
}
|
|
74
78
|
// `else`(bypassRuleWhen)自带 not(if),不叠加规则级判定;其余带分支的动作自判
|
|
75
79
|
// 规则级(不发上报),判定仍走同一份帧内缓存,同帧同规则依旧只判一次。
|
|
76
80
|
const ruled = entry.bypassRuleWhen === true ? { hit: true, outcome: 'hit' } : decideRule(entry.rule, frame)
|
|
@@ -92,5 +96,20 @@ export function actionMatches(entry, frame) {
|
|
|
92
96
|
frame.actionDecisions.add(entry)
|
|
93
97
|
report(frame, entry.rule, outcome)
|
|
94
98
|
}
|
|
99
|
+
frame.reminders.set(entry, hit ? [...(ruled.reminders ?? []), ...(entry.actionWhen?.soulReminders ?? [])] : [])
|
|
95
100
|
return hit
|
|
96
101
|
}
|
|
102
|
+
|
|
103
|
+
/** 只关联实际命中路径的正向Soul事实;普通分支没有提醒戳。 */
|
|
104
|
+
export function actionSoulReminder(entry, frame) {
|
|
105
|
+
return frame.reminders.get(entry)?.[0]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function recheckSoulReminder(entry, frame, expected) {
|
|
109
|
+
if (expected === undefined) return true
|
|
110
|
+
const current = ruleFrame(frame.channel, frame.args, frame.warnOnce, undefined)
|
|
111
|
+
if (!actionMatches(entry, current)) return false
|
|
112
|
+
const fact = actionSoulReminder(entry, current)
|
|
113
|
+
return fact?.predicate === expected.predicate && fact.moduleId === expected.moduleId
|
|
114
|
+
&& fact.pendingVersion === expected.pendingVersion && fact.epoch === expected.epoch
|
|
115
|
+
}
|
|
@@ -9,6 +9,7 @@ import { createScopePredicate } from './scope.mjs'
|
|
|
9
9
|
import { createAnchorPredicate } from './anchor.mjs'
|
|
10
10
|
import { createVariablePredicate } from './variable.mjs'
|
|
11
11
|
import { createManifestPredicate } from './manifest.mjs'
|
|
12
|
+
import { createSoulPredicate } from './soul.mjs'
|
|
12
13
|
import { withAvailableInput } from './availability.mjs'
|
|
13
14
|
import { composite } from './composite.mjs'
|
|
14
15
|
import { COMPOSITE_OPERATORS } from './operators.mjs'
|
|
@@ -23,6 +24,7 @@ export * from './scope.mjs'
|
|
|
23
24
|
export * from './anchor.mjs'
|
|
24
25
|
export * from './variable.mjs'
|
|
25
26
|
export * from './manifest.mjs'
|
|
27
|
+
export * from './soul.mjs'
|
|
26
28
|
export { subjectOf, channelTextSubjects, channelFactSubjects, FACT_PREDICATE_SUBJECTS } from './subject.mjs'
|
|
27
29
|
export { composite } from './composite.mjs'
|
|
28
30
|
export const PREDICATE_FACTORIES = Object.freeze({
|
|
@@ -37,6 +39,7 @@ export const PREDICATE_FACTORIES = Object.freeze({
|
|
|
37
39
|
anchor: createAnchorPredicate,
|
|
38
40
|
variable: createVariablePredicate,
|
|
39
41
|
manifest: createManifestPredicate,
|
|
42
|
+
soul: createSoulPredicate,
|
|
40
43
|
})
|
|
41
44
|
export { COMPOSITE_OPERATORS }
|
|
42
45
|
|
|
@@ -102,5 +105,6 @@ export function compileWhen(node, context = {}) {
|
|
|
102
105
|
if (key === 'manifest') {
|
|
103
106
|
return createManifestPredicate(options, { manifestCheck: context.manifestCheck, moduleId: context.moduleId })
|
|
104
107
|
}
|
|
108
|
+
if (key === 'soul') return createSoulPredicate(options, context)
|
|
105
109
|
return withAvailableInput(key, options, factory(options))
|
|
106
110
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { UNAVAILABLE } from './availability.mjs'
|
|
2
|
+
import { agentOf } from './subject.mjs'
|
|
3
|
+
|
|
4
|
+
export function createSoulPredicate(options = {}, context = {}) {
|
|
5
|
+
if (Object.keys(options).some(key => key !== 'pending') || options.pending !== true) throw new TypeError('predicates: soul requires pending: true')
|
|
6
|
+
const predicate = payload => {
|
|
7
|
+
predicate.soulReminders = []
|
|
8
|
+
if (typeof context.soulReminder?.read !== 'function' || !context.moduleId) return UNAVAILABLE
|
|
9
|
+
const fact = context.soulReminder.read(context.moduleId, agentOf(payload))
|
|
10
|
+
if (fact?.due === true) predicate.soulReminders = [{ ...fact, predicate, moduleId: context.moduleId }]
|
|
11
|
+
return typeof fact?.due === 'boolean' ? fact.due : UNAVAILABLE
|
|
12
|
+
}
|
|
13
|
+
predicate.kind = 'soul'
|
|
14
|
+
return predicate
|
|
15
|
+
}
|
package/engine/delivery.d.mts
CHANGED
package/engine/executor.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { wireLayers } from './layers.mjs'
|
|
|
30
30
|
import { renderTemplate } from './template-frame.mjs'
|
|
31
31
|
import { selectContextActivation } from './context-activation.mjs'
|
|
32
32
|
import { compareConfigSequence } from './order.mjs'
|
|
33
|
-
import { ruleFrame, actionMatches } from './conditions/evaluation.mjs'
|
|
33
|
+
import { ruleFrame, actionMatches, actionSoulReminder, recheckSoulReminder } from './conditions/evaluation.mjs'
|
|
34
34
|
import { composePreStepSources, promotionOwnerOf } from './pre-step-source.mjs'
|
|
35
35
|
// 标记键名与「已登记进 config 白名单」同源(schema.mjs),此处不另写字面量。
|
|
36
36
|
import { SESSION_VARIABLES_DISABLED } from './schema.mjs'
|
|
@@ -329,12 +329,15 @@ async function runPromptConfigBatch(options) {
|
|
|
329
329
|
continue
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
const reminder = actionSoulReminder(config, options.ruleFrame)
|
|
332
333
|
const signal = options.payload?.signal
|
|
333
334
|
if (signal?.aborted) continue
|
|
334
335
|
const resolved = await config.resolve({ ctx, agent, session, signal, decision, messages, contextActivationSelected: scope.contextActivation.has(config) })
|
|
335
336
|
if (signal?.aborted) continue
|
|
336
337
|
if (resolved === null || resolved === undefined) continue
|
|
338
|
+
if (!recheckSoulReminder(config, options.ruleFrame, reminder)) continue
|
|
337
339
|
const patched = { ...resolved }
|
|
340
|
+
if (reminder !== undefined) patched.source = { ...patched.source, soulReminder: { pendingVersion: reminder.pendingVersion, epoch: reminder.epoch } }
|
|
338
341
|
// 按来源读取最新持久值;无变量正文不因另一个坏候选的读盘失败撤回。
|
|
339
342
|
const mergedVars = {
|
|
340
343
|
...(config[SESSION_VARIABLES_DISABLED] === true ? {} : config.params?.templateEvaluation !== true && [patched.text, ...config.texts].some(text => typeof text === 'string' && text.includes('{{'))
|
package/engine/fillers.mjs
CHANGED
|
@@ -191,7 +191,8 @@ function createSoulSelfResolver(config) {
|
|
|
191
191
|
return ({ ctx, agent, signal }) => {
|
|
192
192
|
const service = getService(ctx, SOUL_SELF_SERVICE)
|
|
193
193
|
if (typeof service?.render !== 'function') throw new Error('SELF dependency missing')
|
|
194
|
-
|
|
194
|
+
const text = service.render(config.sourceModuleId, agent, signal)
|
|
195
|
+
return typeof text?.then === 'function' ? text.then(value => ({ text: value })) : { text }
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface RecoveryObservation { state: string; observedAt: string }
|
|
2
|
+
export function recoveryObservationOf(agent: object): RecoveryObservation | undefined
|
|
3
|
+
export const name: string
|
|
4
|
+
export const inject: string[]
|
|
5
|
+
export const configContract: { parse(raw: unknown, plugin: string): Record<string, unknown> }
|
|
6
|
+
export function apply(ctx: unknown, raw: unknown, options?: Record<string, unknown>): (() => void) | undefined
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
1
2
|
import { bool, defineConfig, enumOf, int, passthrough } from './fields.mjs'
|
|
2
3
|
import { capabilityAudience } from './agent-capability.mjs'
|
|
3
4
|
import { recoveryCoordinator } from './generation-recovery-coordinator.mjs'
|
|
@@ -6,6 +7,8 @@ import { recoveryMaintenance } from './generation-recovery-maintenance.mjs'
|
|
|
6
7
|
|
|
7
8
|
export const name = 'generation-recovery'
|
|
8
9
|
export const inject = ['llm']
|
|
10
|
+
const observations = new WeakMap()
|
|
11
|
+
export const recoveryObservationOf = agent => observations.get(agent)
|
|
9
12
|
|
|
10
13
|
const recoveryLimit = passthrough((value, plugin, field) => {
|
|
11
14
|
if (value === undefined) return 0
|
|
@@ -19,6 +22,8 @@ export const configContract = defineConfig({
|
|
|
19
22
|
textRepeatChars: int({ min: 3, default: 4096 }),
|
|
20
23
|
reasoningRepeatChars: int({ min: 3, default: 8192 }),
|
|
21
24
|
silentReasoningChars: int({ min: 0, default: 0 }),
|
|
25
|
+
lineRepeatChars: int({ min: 0, default: 0 }),
|
|
26
|
+
crossCallRepeatChars: int({ min: 0, default: 0 }),
|
|
22
27
|
recoverEmpty: bool({ default: false }),
|
|
23
28
|
mainRecoveryLimit: recoveryLimit,
|
|
24
29
|
subagentRecoveryLimit: recoveryLimit,
|
|
@@ -54,20 +59,53 @@ function repeatDetector(threshold, maxCycle) {
|
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
// 只保存行摘要;窗口与单行都有上限,引用与代码围栏不计入材料判据。
|
|
63
|
+
function materialDetector(config, previous) {
|
|
64
|
+
const counts = new Map()
|
|
65
|
+
let line = '', fenced = false, overflow = false, chars = 0, lines = 0, repeated = 0, dominant = 0
|
|
66
|
+
const overlap = [0, 0]
|
|
67
|
+
return {
|
|
68
|
+
push(text) {
|
|
69
|
+
let offset = 0
|
|
70
|
+
for (const char of text) {
|
|
71
|
+
offset += char.length
|
|
72
|
+
if (char !== '\n') { if (line.length < 1024) line += char; else overflow = true; continue }
|
|
73
|
+
const value = line.trim(); line = ''
|
|
74
|
+
if (value.startsWith('```') || value.startsWith('~~~')) { fenced = !fenced; overflow = false; continue }
|
|
75
|
+
if (overflow || fenced || value.startsWith('>') || value.length < 12 || lines >= 128) { overflow = false; continue }
|
|
76
|
+
const key = createHash('sha256').update(value).digest('hex')
|
|
77
|
+
const count = (counts.get(key) ?? 0) + 1
|
|
78
|
+
counts.set(key, count); lines++; chars += value.length
|
|
79
|
+
if (count > 1) repeated += value.length
|
|
80
|
+
dominant = Math.max(dominant, count)
|
|
81
|
+
previous.forEach((sample, index) => { if (sample.has(key)) overlap[index] += value.length })
|
|
82
|
+
if (config.lineRepeatChars > 0 && chars >= config.lineRepeatChars && lines >= 6 && repeated / chars >= 0.8 && dominant / lines >= 0.5) return { offset, cause: 'repeated-lines' }
|
|
83
|
+
if (config.crossCallRepeatChars > 0 && chars >= config.crossCallRepeatChars && lines >= 6 && previous.length === 2 && overlap.every(value => value / chars >= 0.9)) return { offset, cause: 'repeated-material' }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
sample: () => lines >= 6 && chars >= config.crossCallRepeatChars ? new Set(counts.keys()) : undefined,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
57
90
|
export function apply(ctx, raw, options = {}) {
|
|
58
91
|
const { agent } = options
|
|
59
92
|
const config = configContract.parse(raw, name)
|
|
60
93
|
if (!config.enabled || typeof agent?.session?.id !== 'string') return
|
|
61
94
|
if (config.audience !== 'all' && config.audience !== capabilityAudience(agent)) return
|
|
62
95
|
let active = true
|
|
63
|
-
const
|
|
96
|
+
const samples = { text: [], reasoning: [] }
|
|
97
|
+
const clearSamples = () => { samples.text.length = 0; samples.reasoning.length = 0 }
|
|
98
|
+
const observe = state => observations.set(agent, { state, observedAt: new Date().toISOString() })
|
|
99
|
+
const report = message => { observe('reported'); ctx.logger?.warn?.(`${name}: ${message}`) }
|
|
64
100
|
const limit = capabilityAudience(agent) === 'subagent' ? config.subagentRecoveryLimit : config.mainRecoveryLimit
|
|
65
101
|
const coordinator = recoveryCoordinator(ctx, agent, config, { ...options, recover(candidate) {
|
|
102
|
+
observe('candidate')
|
|
66
103
|
if (!limit) return
|
|
67
104
|
if (!options.recoveryState) { report('persistent recovery state unavailable; automatic recovery disabled'); return }
|
|
68
105
|
try {
|
|
69
106
|
const claimed = options.recoveryState.claim(agent.session.id, candidate.execution, limit)
|
|
70
107
|
if (!claimed.claimed) { report(`recovery unavailable (${claimed.used}/${limit})`); return }
|
|
108
|
+
observe('claimed')
|
|
71
109
|
void (async () => {
|
|
72
110
|
try {
|
|
73
111
|
await agent.whenIdle()
|
|
@@ -82,18 +120,21 @@ export function apply(ctx, raw, options = {}) {
|
|
|
82
120
|
const text = '上一轮输出因异常重复或仅有推理而未完成。请停止重复,检查已完成的工作,并给出有效结果或明确说明阻塞原因。'
|
|
83
121
|
+ (maintenance ? `\n维护压缩已完成,实际来源事件:${maintenance.shadowedSeqs.join(',')}。` : '')
|
|
84
122
|
agent.followup(pluginMessage('generation-recovery', text, '恢复异常生成'))
|
|
123
|
+
observe('enqueued')
|
|
85
124
|
} catch (error) { report(String(error?.message ?? error)) }
|
|
86
125
|
})()
|
|
87
126
|
} catch (error) { report(String(error?.message ?? error)) }
|
|
88
127
|
} }, () => active)
|
|
89
|
-
const stop = ctx.effect(() => () => { active = false })
|
|
128
|
+
const stop = ctx.effect(() => () => { active = false; clearSamples() })
|
|
90
129
|
const off = ctx.on('llm/stream', (options, next) => {
|
|
130
|
+
if (options.sessionId === agent.session.id && options.signal?.aborted) clearSamples()
|
|
91
131
|
if (!active || options.signal?.aborted || options.purpose !== undefined || options.sessionId !== agent.session.id) return next()
|
|
92
132
|
return (async function* () {
|
|
93
133
|
const detectors = {
|
|
94
134
|
text: repeatDetector(config.textRepeatChars, config.maxCycleChars),
|
|
95
135
|
reasoning: repeatDetector(config.reasoningRepeatChars, config.maxCycleChars),
|
|
96
136
|
}
|
|
137
|
+
const materials = { text: materialDetector(config, samples.text), reasoning: materialDetector(config, samples.reasoning) }
|
|
97
138
|
const open = new Map()
|
|
98
139
|
const closed = new Set()
|
|
99
140
|
const finalized = new Set()
|
|
@@ -102,7 +143,7 @@ export function apply(ctx, raw, options = {}) {
|
|
|
102
143
|
let reasoningChars = 0
|
|
103
144
|
for await (const chunk of next()) {
|
|
104
145
|
if (finalized.has(chunk.index)) continue
|
|
105
|
-
if (!active || options.signal?.aborted) { yield chunk; continue }
|
|
146
|
+
if (!active || options.signal?.aborted) { clearSamples(); yield chunk; continue }
|
|
106
147
|
if (chunk.type === 'tool-call-delta' || chunk.blockType === 'tool-call' || chunk.block?.type === 'tool-call') hasToolCall = true
|
|
107
148
|
if (chunk.type === 'block-start') open.set(chunk.index, { type: chunk.blockType, text: '' })
|
|
108
149
|
if (chunk.type === 'block-end') { open.delete(chunk.index); closed.add(chunk.index) }
|
|
@@ -114,6 +155,8 @@ export function apply(ctx, raw, options = {}) {
|
|
|
114
155
|
if (kind === 'text' && chunk.text.trim()) visibleText = true
|
|
115
156
|
let cutoff = safe ? detectors[kind](chunk.text) : undefined
|
|
116
157
|
let cause = 'repeated-' + kind
|
|
158
|
+
const material = safe ? materials[kind].push(chunk.text) : undefined
|
|
159
|
+
if (material && (cutoff === undefined || material.offset < cutoff)) { cutoff = material.offset; cause = material.cause }
|
|
117
160
|
if (safe && kind === 'reasoning' && !visibleText && config.silentReasoningChars > 0) {
|
|
118
161
|
const chars = [...chunk.text]
|
|
119
162
|
const remaining = config.silentReasoningChars - reasoningChars
|
|
@@ -147,7 +190,13 @@ export function apply(ctx, raw, options = {}) {
|
|
|
147
190
|
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
148
191
|
return
|
|
149
192
|
}
|
|
193
|
+
if (active && !options.signal?.aborted && !hasToolCall && config.crossCallRepeatChars > 0) {
|
|
194
|
+
for (const kind of ['text', 'reasoning']) {
|
|
195
|
+
const sample = materials[kind].sample()
|
|
196
|
+
if (sample) { samples[kind].push(sample); if (samples[kind].length > 2) samples[kind].shift() }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
150
199
|
})()
|
|
151
200
|
})
|
|
152
|
-
return () => { stop(); off(); coordinator.dispose() }
|
|
201
|
+
return () => { stop(); off(); coordinator.dispose(); observations.delete(agent) }
|
|
153
202
|
}
|
package/engine/layers.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { INJECTION_LAYERS, KNOWN_STRATEGIES, SESSION_VARIABLES_DISABLED } from '
|
|
|
18
18
|
import { interpolateVariables, stripUnresolvedRefs, RUNTIME_FACTS, runtimeFactValue, isReservedInterpolationName } from './interpolate.mjs'
|
|
19
19
|
import { renderTemplate } from './template-frame.mjs'
|
|
20
20
|
import { compareConfigSequence, compareTextPlacement } from './order.mjs'
|
|
21
|
-
import { ruleFrame, actionMatches } from './conditions/evaluation.mjs'
|
|
21
|
+
import { ruleFrame, actionMatches, actionSoulReminder, recheckSoulReminder } from './conditions/evaluation.mjs'
|
|
22
22
|
import { textRegistrationName, identityStampOf, actionStamp, injectedMessage, findActionDelivery } from './delivery.mjs'
|
|
23
23
|
|
|
24
24
|
const name = 'prompt-config-engine'
|
|
@@ -303,10 +303,15 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
|
|
|
303
303
|
for (const config of binding.group) {
|
|
304
304
|
if (!active || context.signal?.aborted || !actionMatches(config, frame) || !matchesAgentScope(config, context.agent)) continue
|
|
305
305
|
try {
|
|
306
|
+
const reminder = actionSoulReminder(config, frame)
|
|
306
307
|
const text = config.layer === 'runtime-context' && (config.strategy === 'placeholder' || !KNOWN_STRATEGIES.has(config.strategy))
|
|
307
308
|
? await resolvedContextText(ctx, config, context, registry.get(config), warnOnce)
|
|
308
309
|
: configText(config, registry, warnOnce, context)
|
|
309
|
-
if (
|
|
310
|
+
if (!recheckSoulReminder(config, frame, reminder)) continue
|
|
311
|
+
if (text.length) {
|
|
312
|
+
const stamp = reminder === undefined ? '' : `\n<!-- soul-reminder:${Buffer.from(JSON.stringify({ moduleId: config.sourceModuleId, sessionId: context.agent.session.id, pendingVersion: reminder.pendingVersion, epoch: reminder.epoch })).toString('base64')} -->`
|
|
313
|
+
blocks.push(text + stamp)
|
|
314
|
+
}
|
|
310
315
|
} catch (error) { warnOnce(`${name}: rule text ${config.id} failed: ${String(error?.message ?? error)}`) }
|
|
311
316
|
}
|
|
312
317
|
if (active && !context.signal?.aborted) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RetrievalEvent = { readonly seq: number; readonly type: string }
|
|
1
|
+
export type RetrievalEvent = { readonly seq: number; readonly type: string; readonly time?: number }
|
|
2
2
|
export type SessionReference = { kind: 'session'; moduleId: string; sessionId: string; seqs: number[]; revision: string; start: number; end: number }
|
|
3
3
|
export const MAX_REFERENCE_CHARS: number
|
|
4
4
|
export function sessionReferenceText(events: readonly RetrievalEvent[], seqs: readonly number[]): string
|
package/engine/retrieval.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bool, defineConfig, stringList } from './fields.mjs'
|
|
1
|
+
import { bool, defineConfig, stringList, text } from './fields.mjs'
|
|
2
2
|
import { toolServiceName } from './service-names.mjs'
|
|
3
3
|
|
|
4
4
|
export const name = 'retrieval'
|
|
@@ -7,6 +7,7 @@ export const configContract = defineConfig({
|
|
|
7
7
|
enabled: bool({ default: false }),
|
|
8
8
|
paths: stringList({ default: () => [], allowEmpty: true, dedupe: true }),
|
|
9
9
|
sessions: stringList({ default: () => [], allowEmpty: true, dedupe: true }),
|
|
10
|
+
synonymsFile: text({ default: undefined, allowEmpty: false }),
|
|
10
11
|
})
|
|
11
12
|
|
|
12
13
|
export function apply(ctx, raw, options) {
|
|
@@ -16,6 +16,9 @@ export interface RuleManifestCheck {
|
|
|
16
16
|
read(moduleId: string): boolean | undefined
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export interface SoulReminderFact { count: number; pendingVersion: string; epoch: number; due: boolean }
|
|
20
|
+
export interface RuleSoulReminder { read(moduleId: string, agent: unknown): SoulReminderFact | undefined }
|
|
21
|
+
|
|
19
22
|
/** 判定上报载荷;仅覆盖插件依赖面(宿主按 `RuleOutcomeReport` 消费它)。 */
|
|
20
23
|
export interface RuleOutcomeReport {
|
|
21
24
|
moduleId?: string
|
|
@@ -37,6 +40,7 @@ export interface MountRuleSourcesOptions {
|
|
|
37
40
|
variableStore?: RuleVariableStore
|
|
38
41
|
/** 清单 checks 事实;缺省=manifest 条件恒不可用。 */
|
|
39
42
|
manifestCheck?: RuleManifestCheck
|
|
43
|
+
soulReminder?: RuleSoulReminder
|
|
40
44
|
/** 官方预设兜底;缺省=没有兜底(预设条件恒不命中)。 */
|
|
41
45
|
standingMountFor?: StandingMountFor
|
|
42
46
|
/** 注入的晋升相位实例对;缺省从协调器或自建池取。 */
|
package/engine/rule-runtime.mjs
CHANGED
|
@@ -82,7 +82,7 @@ function mountSources(ctx, sources, options = {}) {
|
|
|
82
82
|
const bind = (item, on) => prepareAction(item.action, { plugin: options.plugin, warnOnce, promptConfigOptions: item.rule.promptConfigOptions, on })(ctx)
|
|
83
83
|
try {
|
|
84
84
|
for (const source of sources) {
|
|
85
|
-
try { rebindConditions([source], { ctx, standingMountFor: options.standingMountFor, promotionPool: pool, variableStore: options.variableStore, manifestCheck: options.manifestCheck }) }
|
|
85
|
+
try { rebindConditions([source], { ctx, standingMountFor: options.standingMountFor, promotionPool: pool, variableStore: options.variableStore, manifestCheck: options.manifestCheck, soulReminder: options.soulReminder }) }
|
|
86
86
|
catch (error) { throw sourceError(error, source.moduleId) }
|
|
87
87
|
}
|
|
88
88
|
const rules = sources.flatMap(source => source.rules.filter(rule => rule.enabled !== false))
|
|
@@ -115,7 +115,8 @@ function mountSources(ctx, sources, options = {}) {
|
|
|
115
115
|
for (const item of point.items) {
|
|
116
116
|
const { action, rule, moduleId } = item
|
|
117
117
|
if (action.kind === 'inject-text') {
|
|
118
|
-
const config = { ...action.compiledConfig, rule, sourceModuleId: moduleId, variableStore: options.variableStore,
|
|
118
|
+
const config = { ...action.compiledConfig, rule, sourceModuleId: moduleId, variableStore: options.variableStore,
|
|
119
|
+
group: undefined, exclusive: false, enabled: true, actionWhen: action.actionWhen, bypassRuleWhen: action.bypassRuleWhen, runtimeOrder: action.channelOrder, runtimeRuleIndex: item.ruleIndex }
|
|
119
120
|
if (config.layer === 'pre-step') injections.get(moduleId).configs.push(config)
|
|
120
121
|
else textConfigs.push(config)
|
|
121
122
|
continue
|
package/engine/rule-spec.mjs
CHANGED
|
@@ -18,6 +18,8 @@ import { record } from './record.mjs'
|
|
|
18
18
|
/** 规则字段:只有 `if`/`then`/`else`;旧名 `when`/`do` 留在名单里,只为把 unknown fields 换成一句「已退役,改用 X」。 */
|
|
19
19
|
const RULE_FIELDS = new Set(['id', 'name', 'enabled', 'layer', 'group', 'exclusive', 'if', 'then', 'else', 'when', 'do'])
|
|
20
20
|
const nonempty = value => typeof value === 'string' && value.trim().length > 0
|
|
21
|
+
const hasSoulCondition = node => node !== null && typeof node === 'object'
|
|
22
|
+
&& (Object.hasOwn(node, 'soul') || Object.values(node).some(hasSoulCondition))
|
|
21
23
|
|
|
22
24
|
/** `prepend` 退役文案(动作层与 `inject-text.config` 共用):等价档只有「注册到最外层」一档,而 `inject-text` 连它也不接受。 */
|
|
23
25
|
const PREPEND_RETIRED = '`prepend` 已取消 — 它是未文档化的注册后门(executor 直读 config.prepend),作用只有「注册到最外层」一档:非 inject-text 动作改用 `waterfallPosition: outermost`;`inject-text` 不接受该字段,没有等价写法'
|
|
@@ -232,7 +234,7 @@ export function compileRules(specs, options = {}) {
|
|
|
232
234
|
// 条件的绑定**不在编译期**:`preset` 谓词的取法要挂载 scope 的 ctx(`conditions/preset.mjs`),
|
|
233
235
|
// 准备期拿不到。这里显式剥掉挂载期选项——传了也不生效,免得调用方以为已经绑好。
|
|
234
236
|
// variableStore / manifestCheck 与 ctx/standingMountFor 同理:绑定发生在挂载期,准备期传了也不生效。
|
|
235
|
-
const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, ...conditionOptions } = options
|
|
237
|
+
const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, soulReminder: _soulReminder, ...conditionOptions } = options
|
|
236
238
|
const ids = new Set()
|
|
237
239
|
const pendingConfigs = []
|
|
238
240
|
const moduleId = options.moduleId ?? options.sourceModuleId
|
|
@@ -294,6 +296,9 @@ export function compileRules(specs, options = {}) {
|
|
|
294
296
|
const action = structuredClone(source)
|
|
295
297
|
if (action.kind === 'inject-text') {
|
|
296
298
|
action.config = injectionConfigSpec(spec, action, options)
|
|
299
|
+
if ((hasSoulCondition(ruleIf) || conditions.some(hasSoulCondition)) && !['pre-step', 'runtime-context'].includes(action.config.layer)) {
|
|
300
|
+
throw new TypeError('soul reminder conditions support only pre-step or runtime-context delivery')
|
|
301
|
+
}
|
|
297
302
|
}
|
|
298
303
|
const execution = ruleActionExecution(action)
|
|
299
304
|
// 未知键检查必须在 normalizeActionGates **之后**:中性旧 audience/modelScope/promotion
|
|
@@ -413,6 +418,7 @@ function needsMountContext(node) {
|
|
|
413
418
|
if (Object.hasOwn(node, 'variable')) return true
|
|
414
419
|
// manifest 要读挂载 scope 的清单检查服务(准备期没有)。
|
|
415
420
|
if (Object.hasOwn(node, 'manifest')) return true
|
|
421
|
+
if (Object.hasOwn(node, 'soul')) return true
|
|
416
422
|
if (Object.hasOwn(node, 'phase') && node.phase?.subscribe !== false) return true
|
|
417
423
|
if (node.anchor?.after !== undefined) return true
|
|
418
424
|
return Object.values(node).some(needsMountContext)
|
|
@@ -10,6 +10,11 @@ export const configContract = defineConfig({
|
|
|
10
10
|
enabled: bool({ default: false }), intervalMs: bounded(60000, 1000, 2147483647),
|
|
11
11
|
wake: bool({ default: false }), audience: enumOf(['all', 'main', 'subagent'], { default: 'all' }),
|
|
12
12
|
budgetBytes: bounded(12288, 1024, 65536),
|
|
13
|
+
pressureThresholds: passthrough((value, plugin, field) => {
|
|
14
|
+
if (value === undefined) return undefined
|
|
15
|
+
if (!Array.isArray(value) || value.length !== 3 || value.some((ratio, index) => typeof ratio !== 'number' || !Number.isFinite(ratio) || ratio <= 0 || ratio >= 1 || index > 0 && ratio <= value[index - 1])) throw new TypeError(`${plugin}: ${field} must be three increasing ratios between 0 and 1`)
|
|
16
|
+
return [...value]
|
|
17
|
+
}),
|
|
13
18
|
})
|
|
14
19
|
export function apply(ctx, raw, options = {}) {
|
|
15
20
|
const config = configContract.parse(raw, name)
|
package/lib/client.js
CHANGED
|
@@ -744,6 +744,7 @@ window.__ModuleLoader__.load({
|
|
|
744
744
|
manifest: "module_manifest",
|
|
745
745
|
variables: "module_variables",
|
|
746
746
|
soulLedger: "soul_ledger",
|
|
747
|
+
soulState: "soul_state",
|
|
747
748
|
search: "module_search",
|
|
748
749
|
open: "module_open"
|
|
749
750
|
};
|
|
@@ -756,6 +757,7 @@ window.__ModuleLoader__.load({
|
|
|
756
757
|
[MODULE_TOOL_NAMES.manifest]: "module-manifest-tools",
|
|
757
758
|
[MODULE_TOOL_NAMES.variables]: "module-variable-tools",
|
|
758
759
|
[MODULE_TOOL_NAMES.soulLedger]: "soul-ledger",
|
|
760
|
+
[MODULE_TOOL_NAMES.soulState]: "soul-ledger",
|
|
759
761
|
[MODULE_TOOL_NAMES.search]: "retrieval",
|
|
760
762
|
[MODULE_TOOL_NAMES.open]: "retrieval"
|
|
761
763
|
};
|