dsh-plugin-prompt-tool 0.8.0 → 1.0.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.
- package/README.md +58 -50
- package/engine/character-tools.mjs +3 -6
- package/engine/compositions/library/agent-instructions.yml +1 -1
- package/engine/compositions/library/command-goal.yml +1 -1
- package/engine/compositions/library/compaction.yml +1 -1
- package/engine/compositions/library/delegation-ptc.yml +1 -1
- package/engine/compositions/library/delegation.yml +1 -1
- package/engine/compositions/library/persistent-shell.yml +1 -1
- package/engine/compositions/library/planning.yml +1 -1
- package/engine/compositions/library/skill-filesystem-cordis.yml +1 -1
- package/engine/compositions/library/skill-filesystem.yml +1 -1
- package/engine/compositions/library/tool-ask-user.yml +1 -1
- package/engine/compositions/library/tool-bash.yml +1 -1
- package/engine/compositions/library/tool-cordis.yml +1 -1
- package/engine/compositions/library/tool-fs-search.yml +1 -1
- package/engine/compositions/library/tool-fs.yml +1 -1
- package/engine/compositions/library/tool-goal.yml +1 -1
- package/engine/compositions/library/tool-jobs.yml +1 -1
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +1 -1
- package/engine/compositions/library/tool-plugin-manager.yml +1 -1
- package/engine/compositions/library/tool-present.yml +1 -1
- package/engine/compositions/library/tool-presentation.yml +1 -1
- package/engine/compositions/library/tool-pwsh.yml +1 -1
- package/engine/compositions/library/tool-skill.yml +1 -1
- package/engine/compositions/library/tool-todo.yml +1 -1
- package/engine/compositions/library/tool-web.yml +1 -1
- package/engine/compositions/source/local/character-tools.yml +4 -4
- package/engine/compositions/source/local/prompt-config-engine.yml +1 -1
- package/engine/compositions/source/local/session-var-tools.yml +4 -4
- package/engine/compositions/source/local/world-book-tools.yml +4 -4
- package/engine/executor.mjs +39 -26
- package/engine/instruction-hint.mjs +6 -2
- package/engine/layers.mjs +38 -30
- package/engine/order.mjs +17 -0
- package/engine/schema.mjs +16 -6
- package/engine/session-var-tools.mjs +3 -6
- package/engine/st-render.mjs +8 -4
- package/engine/subagent-tool-policy.mjs +25 -10
- package/engine/world-book-tools.mjs +3 -6
- package/lib/client.js +3451 -3519
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +233 -272
- package/lib/index.mjs +1020 -1226
- package/lib/{preset-package-BnJqrU4R.mjs → module-package-Cm3fHHlp.mjs} +997 -594
- package/lib/{paths-CY5psBqE.d.mts → paths-BgL42sSM.d.mts} +191 -149
- package/lib/preset-transfer.d.mts +2 -2
- package/lib/preset-transfer.mjs +2 -2
- package/modules/ponytail/configs/0000-ponytail-rules.yml +102 -0
- package/modules/ponytail/configs/0010-ponytail-level-full.yml +19 -0
- package/modules/ponytail/configs/0020-ponytail-level-lite.yml +19 -0
- package/modules/ponytail/configs/0030-ponytail-level-ultra.yml +19 -0
- package/modules/ponytail/module.yml +172 -0
- package/package.json +46 -80
- package/templates/20-system-section.yml +4 -1
- package/templates/70-subagent-maintenance.yml +1 -1
- package/templates/tools/50-delegate-worldbook.yml +1 -1
- package/preset/pt-cordis/preset.yml +0 -43
- package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +0 -29
- package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +0 -501
- package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +0 -38
- package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +0 -60
- package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +0 -5
- package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +0 -13
- package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +0 -5
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +0 -22
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +0 -3
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +0 -2
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +0 -15
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +0 -8
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +0 -7
- package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +0 -84
- package/preset/pt-custom/preset.yml +0 -15
- package/preset/pt-minimal/preset.yml +0 -27
- package/preset/pt-ptc/preset.yml +0 -45
- package/preset/pt-standard/preset.yml +0 -43
package/engine/executor.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
|
28
28
|
import { wireLayers } from './layers.mjs'
|
|
29
29
|
import { sessionVarsSnapshot } from './session-vars.mjs'
|
|
30
30
|
import { selectStWorldBook } from './st-world-book.mjs'
|
|
31
|
+
import { compareConfigSequence } from './order.mjs'
|
|
31
32
|
|
|
32
33
|
const name = 'prompt-config-engine'
|
|
33
34
|
|
|
@@ -253,28 +254,23 @@ export async function runPreStepBatch(options) {
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
// order
|
|
257
|
-
due.sort((a, b) => a.config
|
|
257
|
+
// 模块来源按序号;独立调用仍按 order,同值保留稳定身份顺序。
|
|
258
|
+
due.sort((a, b) => compareConfigSequence(a.config, b.config))
|
|
258
259
|
|
|
259
|
-
// merged
|
|
260
|
-
//
|
|
260
|
+
// 每个落位内只合并同来源的连续 merged 卡;其他落位不会穿插在本落位的输出中。
|
|
261
|
+
// source 仍用既有 merged:<position>,不因分段或排序改写持久去重身份。
|
|
261
262
|
const orderedGroups = []
|
|
262
|
-
const
|
|
263
|
+
const lastAtPosition = new Map()
|
|
263
264
|
for (const entry of due) {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
265
|
+
const previous = lastAtPosition.get(entry.config.position)
|
|
266
|
+
const base = previous?.[0].config
|
|
267
|
+
if (entry.config.mergeMode === 'merged' && base?.mergeMode === 'merged'
|
|
268
|
+
&& entry.config.sourceModuleId === base.sourceModuleId) previous.push(entry)
|
|
269
|
+
else {
|
|
270
|
+
const group = [entry]
|
|
271
|
+
orderedGroups.push(group)
|
|
272
|
+
lastAtPosition.set(entry.config.position, group)
|
|
270
273
|
}
|
|
271
|
-
let index = groupIndex.get(group)
|
|
272
|
-
if (index === undefined) {
|
|
273
|
-
index = orderedGroups.length
|
|
274
|
-
groupIndex.set(group, index)
|
|
275
|
-
orderedGroups.push([])
|
|
276
|
-
}
|
|
277
|
-
orderedGroups[index].push(entry)
|
|
278
274
|
}
|
|
279
275
|
|
|
280
276
|
const planned = []
|
|
@@ -345,15 +341,10 @@ export async function runPreStepBatch(options) {
|
|
|
345
341
|
* @param options.officialInstructions 本 mount 的组合是否仍挂着官方指令加载行
|
|
346
342
|
* (负责人冲突事实;协调器据此拒绝同时注入文件正文)。
|
|
347
343
|
*/
|
|
348
|
-
|
|
344
|
+
function effectivePromptConfigs(configs) {
|
|
349
345
|
const list = configs.filter((config) => config !== undefined && config !== null)
|
|
350
|
-
const sourceId = typeof options.sourceId === 'string' && options.sourceId.length > 0
|
|
351
|
-
? options.sourceId
|
|
352
|
-
: `preset:${String(list[0]?.id ?? 'unknown')}`
|
|
353
|
-
// 空 mount 同样保留接管监听:装配事实与协调器迟到/HMR 不依赖是否有预设卡。
|
|
354
|
-
// 互斥组:同一 group 且 exclusive=true 时,只保留排序后第一个 enabled 提示词配置。
|
|
355
346
|
const claimedGroups = new Set()
|
|
356
|
-
|
|
347
|
+
return list.filter((config) => {
|
|
357
348
|
if (config.enabled === false) return false
|
|
358
349
|
if (config.group !== undefined && config.exclusive === true) {
|
|
359
350
|
if (claimedGroups.has(config.group)) return false
|
|
@@ -361,6 +352,27 @@ export function applyPromptConfigs(ctx, configs, options = {}) {
|
|
|
361
352
|
}
|
|
362
353
|
return true
|
|
363
354
|
})
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** 多模块仍各自拥有来源/互斥组;只有非 pre-step 层汇总接线,避免模块块状执行。 */
|
|
358
|
+
export function applyPromptConfigSources(ctx, sources, options = {}) {
|
|
359
|
+
const selected = sources.map(source => ({ ...source, configs: effectivePromptConfigs(source.configs) }))
|
|
360
|
+
const releases = selected.map(source => applyPromptConfigs(ctx, source.configs, { ...options, ...source, layers: false }))
|
|
361
|
+
const releaseLayers = wireLayers(ctx, selected.flatMap(source => source.configs)
|
|
362
|
+
.filter(config => config.layer !== 'pre-step'), createWarnOnce(ctx, name))
|
|
363
|
+
return () => {
|
|
364
|
+
for (const release of releases) release()
|
|
365
|
+
releaseLayers()
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function applyPromptConfigs(ctx, configs, options = {}) {
|
|
370
|
+
const list = configs.filter((config) => config !== undefined && config !== null)
|
|
371
|
+
const sourceId = typeof options.sourceId === 'string' && options.sourceId.length > 0
|
|
372
|
+
? options.sourceId
|
|
373
|
+
: `preset:${String(list[0]?.id ?? 'unknown')}`
|
|
374
|
+
// 互斥组仍在本来源内筛选;空来源也保留协调器迟到/HMR 接管。
|
|
375
|
+
const effectiveList = effectivePromptConfigs(list)
|
|
364
376
|
const injectedMemo = new Map()
|
|
365
377
|
const main = createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: false })
|
|
366
378
|
const withSubagents = createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: true })
|
|
@@ -401,7 +413,8 @@ export function applyPromptConfigs(ctx, configs, options = {}) {
|
|
|
401
413
|
|
|
402
414
|
// 非 pre-step 提示词配置接入各自声明的官方层级通道(system-section /
|
|
403
415
|
// runtime-context / agent-request / llm-stream / tool-pipeline)。
|
|
404
|
-
const releaseLayers =
|
|
416
|
+
const releaseLayers = options.layers === false ? () => {}
|
|
417
|
+
: wireLayers(ctx, effectiveList.filter((config) => config.layer !== 'pre-step'), warnOnce)
|
|
405
418
|
|
|
406
419
|
// 协调器已在(正常装配顺序:插件先加载、预设后挂载)时立即登记来源;
|
|
407
420
|
// 迟到/消失由下面的监听器按「先撤旧再启新」处理。
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { randomUUID } from 'node:crypto'
|
|
20
20
|
import { readFileSync } from 'node:fs'
|
|
21
21
|
import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
22
|
-
import { booleanOption, createWarnOnce, parsePromoteOn, validateConfig } from './shared.mjs'
|
|
22
|
+
import { booleanOption, createWarnOnce, getService, parsePromoteOn, validateConfig } from './shared.mjs'
|
|
23
23
|
|
|
24
24
|
export const name = 'instruction-hint'
|
|
25
25
|
|
|
@@ -272,10 +272,14 @@ export function apply(ctx, config) {
|
|
|
272
272
|
if (!promotion.status(agent).promoted) return decision
|
|
273
273
|
if (!Array.isArray(decision.messages)) return decision
|
|
274
274
|
if (agent?.session === undefined) return decision
|
|
275
|
+
const coordinator = getService(ctx, 'promptToolPreStep')
|
|
276
|
+
const messages = typeof coordinator?.filterInstructions === 'function'
|
|
277
|
+
? coordinator.filterInstructions(agent, decision.messages)
|
|
278
|
+
: decision.messages
|
|
275
279
|
// 1 换 1 的转换不能按长度判断(长度相同仍可能已转换),
|
|
276
280
|
// instructionHintMessages 本身保留非目标消息,直接采用结果。
|
|
277
281
|
const hintState = { instructionHinted: hasVisibleInstructionHint(agent.session) }
|
|
278
|
-
return { ...decision, messages: instructionHintMessages(
|
|
282
|
+
return { ...decision, messages: instructionHintMessages(messages, hintState, name) }
|
|
279
283
|
} catch (error) {
|
|
280
284
|
// 转换失败不阻断会话:保留原消息。
|
|
281
285
|
warnOnce(`${name}: instruction hint conversion failed, keeping messages: ${String((error && error.message) || error)}`)
|
package/engine/layers.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { KNOWN_STRATEGIES } from './schema.mjs'
|
|
|
18
18
|
import { interpolateVariables, stripUnresolvedRefs, RUNTIME_FACTS, runtimeFactValue } from './interpolate.mjs'
|
|
19
19
|
import { getSessionVar, sessionVarsSnapshot } from './session-vars.mjs'
|
|
20
20
|
import { conditionHit, lastAssistantText, subagentTextOf, toolArgsText } from './condition.mjs'
|
|
21
|
+
import { compareConfigSequence, compareTextPlacement } from './order.mjs'
|
|
21
22
|
|
|
22
23
|
const name = 'prompt-config-engine'
|
|
23
24
|
// 同一宿主会话共享投递账本,避免预设作用域重挂后重投同一次结束通知。
|
|
@@ -160,28 +161,31 @@ function officialChannelText(text, label, registry, warnOnce) {
|
|
|
160
161
|
return result.text
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
/**
|
|
164
|
+
/** 只合并排序后同来源、同官方档位和位置的连续 merged 卡,不跨过穿插段。 */
|
|
164
165
|
function textLayerGroups(configs) {
|
|
165
|
-
const sorted = [...configs].sort(
|
|
166
|
+
const sorted = [...configs].sort(compareTextPlacement)
|
|
166
167
|
const groups = []
|
|
167
|
-
const index = new Map()
|
|
168
168
|
for (const config of sorted) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (at === undefined) {
|
|
176
|
-
at = groups.length
|
|
177
|
-
index.set(key, at)
|
|
178
|
-
groups.push([])
|
|
179
|
-
}
|
|
180
|
-
groups[at].push(config)
|
|
169
|
+
const previous = groups.at(-1)
|
|
170
|
+
const base = previous?.[0]
|
|
171
|
+
if (config.mergeMode === 'merged' && base?.mergeMode === 'merged'
|
|
172
|
+
&& config.sourceModuleId === base.sourceModuleId
|
|
173
|
+
&& config.position === base.position && config.order === base.order) previous.push(config)
|
|
174
|
+
else groups.push([config])
|
|
181
175
|
}
|
|
182
176
|
return groups
|
|
183
177
|
}
|
|
184
178
|
|
|
179
|
+
/** 官方同 order 按 name 排序:默认名编码安全整数序号,显式名仍由作者拥有。 */
|
|
180
|
+
function textRegistrationName(config, field) {
|
|
181
|
+
const explicit = config.params?.[field]
|
|
182
|
+
if (typeof explicit === 'string' && explicit.length > 0) return explicit
|
|
183
|
+
if (config.sourceModuleId === undefined) return config.id
|
|
184
|
+
// 非负安全整数最多 16 位,跨过四位文件前缀后仍保持数值顺序。
|
|
185
|
+
const sequence = String(config.sequence).padStart(16, '0')
|
|
186
|
+
return `prompt-tool:${sequence}:${config.sourceModuleId}:${config.id}`
|
|
187
|
+
}
|
|
188
|
+
|
|
185
189
|
/** system-section:注册静态 system prompt 段(支持官方 {{variable}} 渲染与 merged 拼接)。 */
|
|
186
190
|
function wireSystemSections(ctx, configs, registry, warnOnce) {
|
|
187
191
|
const disposers = []
|
|
@@ -207,7 +211,7 @@ function wireSystemSections(ctx, configs, registry, warnOnce) {
|
|
|
207
211
|
.join('\n\n')
|
|
208
212
|
: groupText
|
|
209
213
|
keepDisposer(ctx, systemPrompt.section({
|
|
210
|
-
name:
|
|
214
|
+
name: textRegistrationName(base, 'sectionName'),
|
|
211
215
|
order: base.order,
|
|
212
216
|
text,
|
|
213
217
|
...(base.params?.complete === true ? { complete: true } : {}),
|
|
@@ -244,7 +248,7 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
|
244
248
|
const text = dynamic ? render : render()
|
|
245
249
|
if (!dynamic && text.length === 0) continue
|
|
246
250
|
keepDisposer(ctx, systemPrompt.context({
|
|
247
|
-
name:
|
|
251
|
+
name: textRegistrationName(base, 'contextName'),
|
|
248
252
|
order: base.order,
|
|
249
253
|
text,
|
|
250
254
|
}), `${name}: context ${base.id}`)
|
|
@@ -255,7 +259,7 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
|
255
259
|
// 官方 provider 必须同步:先注册可渲染为空的私有变量占位,再在 waterfall 中填充。
|
|
256
260
|
// 标记随官方排序/遮蔽进入本次 assembly,无会话缓存,也不依赖调用方 context 的对象身份。
|
|
257
261
|
const placeholders = configs.filter(needsResolver)
|
|
258
|
-
.sort(
|
|
262
|
+
.sort(compareTextPlacement)
|
|
259
263
|
if (placeholders.length === 0) return disposers
|
|
260
264
|
const slotVariable = `pt_runtime_${newMessageId('context').replace(/[^a-z0-9_]/gi, '_').toLowerCase()}`
|
|
261
265
|
const slotText = `{{${slotVariable}}}`
|
|
@@ -265,7 +269,7 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
|
265
269
|
keepDisposer(ctx, systemPrompt.variable(slotVariable, () => ''), `${name}: runtime-context placeholder`)
|
|
266
270
|
for (const config of placeholders) {
|
|
267
271
|
try {
|
|
268
|
-
const contextName =
|
|
272
|
+
const contextName = textRegistrationName(config, 'contextName')
|
|
269
273
|
keepDisposer(ctx, systemPrompt.context({
|
|
270
274
|
name: contextName,
|
|
271
275
|
order: config.order,
|
|
@@ -337,20 +341,20 @@ export function applyAgentRequestParams(params, base) {
|
|
|
337
341
|
|
|
338
342
|
/** agent-request:对冻结的 LlmCallConfig 做浅合并 / 整体替换 / 按值条件删键。 */
|
|
339
343
|
function wireAgentRequests(ctx, configs, warnOnce) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
+
if (configs.length === 0) return []
|
|
345
|
+
const ordered = configs.some(config => config.sequence !== undefined) ? configs : [...configs].reverse()
|
|
346
|
+
return [ctx.on('agent/request', async (payload, next) => {
|
|
347
|
+
let result = await next()
|
|
348
|
+
// 模块请求在 next 返回后按序合并;无文件来源的独立调用保持原回栈顺序。
|
|
349
|
+
for (const config of ordered) {
|
|
344
350
|
try {
|
|
345
|
-
if (
|
|
346
|
-
return applyAgentRequestParams(config.params, base)
|
|
351
|
+
if (matchesAgentScope(config, payload?.agent)) result = applyAgentRequestParams(config.params, result)
|
|
347
352
|
} catch (error) {
|
|
348
353
|
warnOnce(`${name}: agent-request config ${config.id} failed: ${String(error?.message ?? error)}`)
|
|
349
|
-
return base
|
|
350
354
|
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
355
|
+
}
|
|
356
|
+
return result
|
|
357
|
+
})]
|
|
354
358
|
}
|
|
355
359
|
|
|
356
360
|
/** 把流替换为提示词配置文本的最小合法 chunk 序列。 */
|
|
@@ -518,9 +522,12 @@ export function createTurnStopBudget() {
|
|
|
518
522
|
function wireTurnStops(ctx, configs, warnOnce) {
|
|
519
523
|
const disposers = []
|
|
520
524
|
if (configs.length === 0) return disposers
|
|
521
|
-
const
|
|
525
|
+
const budgets = new Map()
|
|
522
526
|
|
|
523
527
|
for (const config of configs) {
|
|
528
|
+
const source = config.sourceModuleId ?? ''
|
|
529
|
+
if (!budgets.has(source)) budgets.set(source, createTurnStopBudget())
|
|
530
|
+
const budget = budgets.get(source)
|
|
524
531
|
disposers.push(ctx.on('agent/turn-stopping', ({ agent, turn } = {}) => {
|
|
525
532
|
try {
|
|
526
533
|
const session = agent?.session
|
|
@@ -646,6 +653,7 @@ function wireSubagentEvents(ctx, configs, warnOnce) {
|
|
|
646
653
|
* 变量注册走 keepDisposer(随 ctx fiber 释放),不在本函数的回收面内。
|
|
647
654
|
*/
|
|
648
655
|
export function wireLayers(ctx, configs, warnOnce) {
|
|
656
|
+
configs = [...configs].sort(compareConfigSequence)
|
|
649
657
|
// 官方插值两层共享一份变量注册:运行时事实按 assembly 求值,非法名走别名改写。
|
|
650
658
|
const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === 'system-section' || config.layer === 'runtime-context'), warnOnce)
|
|
651
659
|
const registered = [
|
package/engine/order.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** 加载器私有元数据:不进入 YAML、公共配置或客户端载荷。 */
|
|
2
|
+
export const FILE_SEQUENCE = Symbol('prompt-tool:file-sequence')
|
|
3
|
+
|
|
4
|
+
const compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0
|
|
5
|
+
|
|
6
|
+
/** 模块配置按持久序号执行;无物化来源的独立调用保留原 order 语义。 */
|
|
7
|
+
export function compareConfigSequence(a, b) {
|
|
8
|
+
const difference = (a.sequence ?? a.order ?? 0) - (b.sequence ?? b.order ?? 0)
|
|
9
|
+
if (difference !== 0) return difference
|
|
10
|
+
if (a.sourceModuleId === undefined && b.sourceModuleId === undefined) return 0
|
|
11
|
+
return compareText(a.sourceModuleId ?? '', b.sourceModuleId ?? '') || compareText(a.id, b.id)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 官方文本段的定位仍由 order 决定;序号只提供同位置的稳定顺序。 */
|
|
15
|
+
export function compareTextPlacement(a, b) {
|
|
16
|
+
return a.order - b.order || compareConfigSequence(a, b)
|
|
17
|
+
}
|
package/engine/schema.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { sep } from 'node:path'
|
|
|
9
9
|
import { parse as parseYaml } from './vendor/yaml/index.js'
|
|
10
10
|
import { bindResolver } from './strategies.mjs'
|
|
11
11
|
import { attachStRenderers } from './st-render.mjs'
|
|
12
|
+
import { FILE_SEQUENCE, compareConfigSequence } from './order.mjs'
|
|
12
13
|
import { MATCH_LOGIC, createAnchorMatcher } from './anchor-match.mjs'
|
|
13
14
|
|
|
14
15
|
const name = 'prompt-config-engine'
|
|
@@ -126,11 +127,14 @@ export function loadPromptConfigFiles(dirUrl) {
|
|
|
126
127
|
const specs = []
|
|
127
128
|
for (const fileName of promptConfigFileNames(entries)) {
|
|
128
129
|
const raw = readFileSync(new URL(fileName, dirUrl), 'utf8')
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const spec = /\.json$/i.test(fileName) ? JSON.parse(raw) : parsePromptConfigYaml(raw)
|
|
131
|
+
const prefix = /^(\d+)-/.exec(fileName)
|
|
132
|
+
if (prefix !== null && spec !== null && typeof spec === 'object' && !Array.isArray(spec)) {
|
|
133
|
+
const sequence = Number(prefix[1])
|
|
134
|
+
if (!Number.isSafeInteger(sequence)) throw new TypeError(`${name}: unsafe config sequence in ${fileName}`)
|
|
135
|
+
Object.defineProperty(spec, FILE_SEQUENCE, { value: sequence })
|
|
133
136
|
}
|
|
137
|
+
specs.push(spec)
|
|
134
138
|
}
|
|
135
139
|
for (const spec of specs) {
|
|
136
140
|
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) continue
|
|
@@ -632,17 +636,23 @@ export function createPromptConfigs(specs, options = {}) {
|
|
|
632
636
|
templatePatch,
|
|
633
637
|
params,
|
|
634
638
|
}
|
|
639
|
+
const sequence = (Object.hasOwn(options.configOrder ?? {}, spec.id) ? options.configOrder[spec.id] : undefined) ?? spec[FILE_SEQUENCE]
|
|
640
|
+
?? (typeof options.sourceModuleId === 'string' ? index * 10 : undefined)
|
|
641
|
+
if (sequence !== undefined) config.sequence = sequence
|
|
642
|
+
if (typeof options.sourceModuleId === 'string') config.sourceModuleId = options.sourceModuleId
|
|
635
643
|
// 条件判定的匹配器在挂载期编译一次,执行侧(condition.mjs)直接复用:
|
|
636
644
|
// normalizeMatch 已用同一份参数试编译过,这里是同源的第二句(失败会抛出)。
|
|
637
645
|
config.matchScan = match === undefined ? undefined : createAnchorMatcher(match).scan
|
|
638
646
|
config.resolve = bindResolver(config, options.strategyDir)
|
|
639
647
|
return config
|
|
640
648
|
})
|
|
641
|
-
//
|
|
642
|
-
// 稳定升序排在其后。默认 order=0 时等价于文件顺序。
|
|
649
|
+
// 模块/物化来源按持久序号;没有文件来源的独立数组继续采用 anchor + order 契约。
|
|
643
650
|
return attachStRenderers(configs
|
|
644
651
|
.map((config, fileOrder) => ({ config, fileOrder }))
|
|
645
652
|
.sort((a, b) => {
|
|
653
|
+
if (a.config.sequence !== undefined || b.config.sequence !== undefined) {
|
|
654
|
+
return compareConfigSequence(a.config, b.config) || a.fileOrder - b.fileOrder
|
|
655
|
+
}
|
|
646
656
|
const aAnchor = a.config.configKind === 'anchor' ? 0 : 1
|
|
647
657
|
const bAnchor = b.config.configKind === 'anchor' ? 0 : 1
|
|
648
658
|
if (aAnchor !== bAnchor) return aAnchor - bAnchor
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
export const name = "session-var-tools"
|
|
3
3
|
|
|
4
4
|
export function apply(ctx) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
ctx.effect(() => service.mount(ctx), name + ": mount")
|
|
5
|
+
ctx.inject(["pt-session-var-tools"], (scopeCtx) => {
|
|
6
|
+
scopeCtx.effect(() => scopeCtx.get("pt-session-var-tools").mount(scopeCtx), name + ": mount")
|
|
7
|
+
})
|
|
11
8
|
}
|
package/engine/st-render.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { sessionVarsSnapshot } from './session-vars.mjs'
|
|
|
5
5
|
import { sessionEvents, isDelegated, matchesModel } from './shared.mjs'
|
|
6
6
|
import { stripUnresolvedRefs } from './interpolate.mjs'
|
|
7
7
|
import { isSuccessfulCompactionEnd } from './compaction-epoch.mjs'
|
|
8
|
+
import { compareConfigSequence } from './order.mjs'
|
|
8
9
|
|
|
9
10
|
function visible(config, agent) {
|
|
10
11
|
if (config.enabled === false) return false
|
|
@@ -92,10 +93,13 @@ export function attachStRenderers(configs) {
|
|
|
92
93
|
frame.evaluate = evaluate
|
|
93
94
|
if (session) sessions.set(session, frame)
|
|
94
95
|
}
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
// 模块 pre-step 已跨来源按序调度,不能预跑本模块后面的卡而越过另一模块。
|
|
97
|
+
// 其它入口沿用原预求值语义;同帧已求值模板不重放副作用或随机宏。
|
|
98
|
+
if (target.sequence === undefined || eligible === undefined) {
|
|
99
|
+
for (const config of templates.filter(config => approved(config)
|
|
100
|
+
&& config.strategy === 'static' && config.dedupe === 'none' && !frame.text.has(config))
|
|
101
|
+
.sort(compareConfigSequence)) frame.evaluate(config)
|
|
102
|
+
}
|
|
99
103
|
if (token && typeof token === 'object') tokens.set(token, frame)
|
|
100
104
|
if (!frame.text.has(target)) frame.evaluate(target)
|
|
101
105
|
return frame.text.get(target) ?? ''
|
|
@@ -291,22 +291,30 @@ export function apply(ctx, config) {
|
|
|
291
291
|
}
|
|
292
292
|
const compositionScope = scopeOf(ctx)
|
|
293
293
|
if (compositionScope === undefined) throw new Error(`${name}: requires a preset scope`)
|
|
294
|
-
const installs = new
|
|
294
|
+
const installs = new Map()
|
|
295
|
+
const installing = new WeakSet()
|
|
296
|
+
let active = true
|
|
295
297
|
const belongsToComposition = (agent) => scopeChainOf(scopeOf(agent.ctx)).includes(compositionScope)
|
|
296
298
|
const install = (agent) => {
|
|
297
|
-
if (installs.has(agent) || !belongsToComposition(agent)) return
|
|
299
|
+
if (!active || installing.has(agent) || installs.has(agent) || !belongsToComposition(agent)) return
|
|
298
300
|
// 注册走 disposer 契约。这里保留 ctx.effect 而不是 shared.keepDisposer,两条依据:
|
|
299
301
|
// (1) 两个 shadow 必须整组注册:任一个失败时由 Cordis 回滚已注册的那个
|
|
300
302
|
// (keepDisposer 无回滚语义,第二个失败会留下第一个的残留注册);
|
|
301
303
|
// (2) remove(agent) 需要按 agent 定向撤销,而 keepDisposer 返回 void,拿不到句柄。
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
304
|
+
// register/unregister 同步发 tools/change;登记完成前的重入不能再次注册同名工具。
|
|
305
|
+
installing.add(agent)
|
|
306
|
+
try {
|
|
307
|
+
const dispose = agent.ctx.effect(() => {
|
|
308
|
+
const disposers = [
|
|
309
|
+
agent.ctx.tools.register(createShadowTool(ctx, agent.ctx.tools, compositionScope, compiled, 'spawn', source)),
|
|
310
|
+
agent.ctx.tools.register(createShadowTool(ctx, agent.ctx.tools, compositionScope, compiled, 'fork', source)),
|
|
311
|
+
]
|
|
312
|
+
return () => { for (const item of disposers) item() }
|
|
313
|
+
}, `${name}: ${agent.id} shadow`)
|
|
314
|
+
installs.set(agent, dispose)
|
|
315
|
+
} finally {
|
|
316
|
+
installing.delete(agent)
|
|
317
|
+
}
|
|
310
318
|
}
|
|
311
319
|
const remove = (agent) => {
|
|
312
320
|
const dispose = installs.get(agent)
|
|
@@ -323,5 +331,12 @@ export function apply(ctx, config) {
|
|
|
323
331
|
}
|
|
324
332
|
}
|
|
325
333
|
ctx.on('tools/change', reconcile)
|
|
334
|
+
ctx.effect(() => () => {
|
|
335
|
+
// Agent 可以继续存活:模块先停接线,再撤回其持有的 Agent scope 贡献。
|
|
336
|
+
active = false
|
|
337
|
+
const disposers = [...installs.values()]
|
|
338
|
+
installs.clear()
|
|
339
|
+
return Promise.all(disposers.map(dispose => dispose())).then(() => undefined)
|
|
340
|
+
}, `${name}: shadows`)
|
|
326
341
|
reconcile()
|
|
327
342
|
}
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
export const name = "world-book-tools"
|
|
3
3
|
|
|
4
4
|
export function apply(ctx) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
ctx.effect(() => service.mount(ctx), name + ": mount")
|
|
5
|
+
ctx.inject(["pt-world-book-tools"], (scopeCtx) => {
|
|
6
|
+
scopeCtx.effect(() => scopeCtx.get("pt-world-book-tools").mount(scopeCtx), name + ": mount")
|
|
7
|
+
})
|
|
11
8
|
}
|