dsh-plugin-om 0.0.7 → 0.0.9
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 -4
- package/dist/compress.d.ts +0 -7
- package/dist/compress.d.ts.map +1 -1
- package/dist/index.mjs +29 -56
- package/dist/summarize.d.ts +8 -10
- package/dist/summarize.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
dsh plugin --profile <profile> add dsh-plugin-om
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
> 对于 pnpm11+ 添加脚本失败的说明:
|
|
23
|
+
> 插件的依赖 `@huggingface/transformers` 自带原生构建脚本,pnpm 11 默认禁止依赖构建脚本,并会抛出异常。
|
|
24
|
+
> 把 `$DSH_HOME/profiles/<profile>/pnpm-workspace.yaml` 的 `allowBuilds` 改为 `true` 即可
|
|
25
|
+
> 或者使用`dsh plugin --profile <profile> add dsh-plugin-om --allow-build=onnxruntime-node --allow-build=protobufjs --allow-build=sharp`
|
|
26
|
+
|
|
27
|
+
安装完成后,需要重启dsh
|
|
23
28
|
|
|
24
29
|
可以通过`dsh --profile <profile> --dump-config`审查配置是否正确
|
|
25
30
|
|
|
@@ -92,7 +97,7 @@ dsh的"预设"分为两层,`dsh web`等同于`dsh --profile web`,调用的
|
|
|
92
97
|
| `historyMergeRatio` | `0.2` | 反思阈值:摘要 ≥ 窗口 × 该比例触发精简合并 |
|
|
93
98
|
| `compressMaxTokens` | `4096` | 单次摘要(观察/反思调用)生成上限 |
|
|
94
99
|
| `tailMessageCount` | `10` | 尾部保留的不压缩消息条数(不压缩、不被替换、不进摘要日志) |
|
|
95
|
-
| `modelDir` | 打包模型 | recall-semantic 嵌入模型目录(默认插件内 `models/` 目录,仅小文件随包分发;可指向自定义目录)。onnx 缺失且启用语义召回时运行时自动下载到该目录
|
|
100
|
+
| `modelDir` | 打包模型 | recall-semantic 嵌入模型目录(默认插件内 `models/` 目录,仅小文件随包分发;可指向自定义目录)。onnx 缺失且启用语义召回时运行时自动下载到该目录 |
|
|
96
101
|
| `summaryMode` | `fork` | 摘要模式:`fork`(缺省)/ `new` / `disable`(关闭自动压缩);非法值在插件加载时报错(见[摘要模式](#摘要模式)) |
|
|
97
102
|
| `debug` | dev | 压缩流程步骤级(debug)日志开关:`true` 强制开启、`false` 强制关闭;缺省按 `NODE_ENV !== 'production'` 判定(dev/test 输出,生产隐藏)。**失败日志不受此开关影响,始终输出** |
|
|
98
103
|
| `recallEnabled` | `true` | 是否注册 `recall` 工具(`false` 时禁用,不注册) |
|
|
@@ -117,7 +122,7 @@ dsh的"预设"分为两层,`dsh web`等同于`dsh --profile web`,调用的
|
|
|
117
122
|
| `pnpm run download:model` | 手动预下载本地嵌入模型 ONNX(已存在跳过,`--force` 重下;运行时也会按需自动下载) |
|
|
118
123
|
| `pnpm build` | |
|
|
119
124
|
| `pnpm dev` | 自动打包 |
|
|
120
|
-
| `pnpm run release` | CHANGELOG 归档 + 版本号更新 + 打 tag 推送
|
|
125
|
+
| `pnpm run release` | 先跑 check(typecheck+lint+test+build),再 CHANGELOG 归档 + 版本号更新 + 打 tag 推送 |
|
|
121
126
|
|
|
122
127
|
## 调用链和文件地图
|
|
123
128
|
|
|
@@ -148,7 +153,7 @@ src/
|
|
|
148
153
|
├── summarize.ts # 观察/反思 persona + 提示词 + 直连 ctx.llm.stream() 摘要(fork/new 双模式;extractSummaryLog 提取校验;流式 usage 归入主会话)
|
|
149
154
|
├── recall.ts # recall 工具
|
|
150
155
|
├── semantic-recall.ts # recall-semantic 工具(query 语义检索 + 区间限定 + 回退全量 + 匹配说明)
|
|
151
|
-
└── compress.ts # 两级自动压缩(测量 / mid-turn 区间计算 / 配对平衡回退 /
|
|
156
|
+
└── compress.ts # 两级自动压缩(测量 / mid-turn 区间计算 / 配对平衡回退 / source 标记判定摘要消息 / compaction/* 生命周期事件 + checkpoint 替换)
|
|
152
157
|
models/
|
|
153
158
|
└── paraphrase-multilingual-MiniLM-L12-v2/ # 嵌入模型目录(小文件随包分发;onnx 二进制不随包分发、由运行时按需下载到此处,不进 git)
|
|
154
159
|
scripts/ # release-archive.mjs(CHANGELOG 归档)/ download-model.mjs(开发手动预下载 CLI)
|
package/dist/compress.d.ts
CHANGED
|
@@ -27,19 +27,12 @@ export declare function isPairBalancedAfter(session: Session, seq: number): bool
|
|
|
27
27
|
* 观察压缩区间:pre-step 触发时日志 call-result 完备,区间不再受 turn/end 封顶——
|
|
28
28
|
* 头部 → 表层长度-1-tailCount(尾部保留 tailCount 条不压缩),当前 turn 中已完备的
|
|
29
29
|
* 消息同样可压缩;区间终点回退到 tool-call/result 配对平衡点(不切段)。
|
|
30
|
-
* lastEndSeq 仅为中断扫描提供最后一个已结束 turn 的边界(无则 -1)。
|
|
31
30
|
*/
|
|
32
31
|
export declare function computeCompressRange(session: Session, tailCount: number): {
|
|
33
32
|
start: number;
|
|
34
33
|
end: number;
|
|
35
34
|
shadowedSeqs: number[];
|
|
36
|
-
lastEndSeq: number;
|
|
37
35
|
} | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* 中断标记行:范围内 turn/end 以 aborted(含 cause 类型)或 interrupted 结束的轮次
|
|
40
|
-
* (标记用途:让摘要 AI 理解中断原因)。
|
|
41
|
-
*/
|
|
42
|
-
export declare function scanInterruptions(session: Session, fromSeq: number, toSeq: number): string[];
|
|
43
36
|
/**
|
|
44
37
|
* 提取遮蔽区间内最后一次 <om-history> 压缩日志(内文 + seq)。
|
|
45
38
|
* 按表层顺序(shadowedSeqs)扫描:单节点替换(反思)后摘要节点 seq 可能大于
|
package/dist/compress.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EACV,KAAK,EAEL,OAAO,EACP,YAAY,EACZ,OAAO,EAIR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAgB,KAAK,YAAY,EAAsB,MAAM,YAAY,CAAC;AAEjF,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAsBD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,CAAC;AAE3E;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAYzF;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAS7F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAc1E;AAED
|
|
1
|
+
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EACV,KAAK,EAEL,OAAO,EACP,YAAY,EACZ,OAAO,EAIR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAgB,KAAK,YAAY,EAAsB,MAAM,YAAY,CAAC;AAEjF,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAsBD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,CAAC;AAE3E;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAYzF;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAS7F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAc1E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,CAuBpE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAS3C;AAoGD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA+Ff;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA+Hf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAwCf"}
|
package/dist/index.mjs
CHANGED
|
@@ -254,20 +254,18 @@ function makeLogger(ctx, debug) {
|
|
|
254
254
|
//#endregion
|
|
255
255
|
//#region src/summarize.ts
|
|
256
256
|
/** 观察者 persona:只针对未压缩消息产出观察日志,不用工具、不展示思考。 */
|
|
257
|
-
const OBSERVER_PERSONA = "
|
|
257
|
+
const OBSERVER_PERSONA = "把会话中尚未压缩的消息压缩为一份观察日志。不用工具、不展示思考、不评价代码、不输出多余文字。";
|
|
258
258
|
/** 反思者 persona:只精简合并当前摘要,不用工具、不展示思考。 */
|
|
259
|
-
const REFLECTOR_PERSONA = "
|
|
259
|
+
const REFLECTOR_PERSONA = "把当前 <om-history> 压缩日志精简合并为一份更紧凑的日志。不用工具、不展示思考、不输出多余文字。";
|
|
260
260
|
/**
|
|
261
261
|
* 构建观察指令主体:任务声明(fork:停止任务/禁止工具;new:说明总结日志)+ 模式相关
|
|
262
262
|
* 的上下文定位与压缩范围(fork:上方完整会话记录——尾部已在输入中实际截断,提示词不含
|
|
263
263
|
* 尾部规则;new:下方消息即压缩对象)+ 完整消息与 index 定义(三类合并规则、起始编号)+
|
|
264
|
-
* 规则(用户消息完整保留原文 / AI
|
|
265
|
-
* 倾向于新消息 /
|
|
266
|
-
*
|
|
264
|
+
* 规则(用户消息完整保留原文 / AI 消息按模块压缩 / 本指令不入日志 / index/start/end 连续 /
|
|
265
|
+
* 倾向于新消息 / 未完成写进度与下一步)+ 输出格式(合法 XML)+ 追加说明。
|
|
266
|
+
* persona 由调用方拼接到指令开头。
|
|
267
267
|
*/
|
|
268
268
|
function buildObservePrompt(options) {
|
|
269
|
-
/** 中断标记段落(无则标注「无」)。 */
|
|
270
|
-
const interruptionSection = options.interruptions.length > 0 ? options.interruptions : ["(无)"];
|
|
271
269
|
return [
|
|
272
270
|
options.mode === "fork" ? "停止一切现有任务,禁止调用任何工具,改为将过往消息总结为一份日志。" : "将过往消息总结为一份日志。",
|
|
273
271
|
"",
|
|
@@ -278,32 +276,31 @@ function buildObservePrompt(options) {
|
|
|
278
276
|
] : ["下方的消息记录是本次要压缩的全部消息(上一个 <om-history> 块之后的新消息;不含旧压缩日志、不含尾部)。", "你的压缩结果会作为新的 <om-history> 块追加到已有压缩日志之后。"],
|
|
279
277
|
"",
|
|
280
278
|
"【完整消息与 index】",
|
|
281
|
-
"-
|
|
279
|
+
"- 完整消息分三类:用户消息占一条;AI 文本占一条;工具调用及其结果占一条(每个 tool-call 与其 result 各一条,同一条 AI 消息里的文本与工具调用拆开)。每条完整消息有一个 index(从 0 起、按会话顺序递增、全局稳定),index 是该条完整消息的序号。",
|
|
282
280
|
`- 本次要压缩的新消息从 index ${options.startIndex} 开始编号,按日志顺序逐条递增;旧 <${HISTORY_TAG}> 中已有的 index 无需理会,也不要改动旧条目。`,
|
|
283
281
|
"",
|
|
284
282
|
"【规则】",
|
|
283
|
+
"- 当前消息仅作为指令,**不得**进入日志。",
|
|
285
284
|
"- 用户消息完整保留原文,输出为 <user_message index=\"N\"> 条目(N 为该条完整消息的 index,内容为消息原文,不概括、不省略)。",
|
|
286
|
-
"- AI
|
|
287
|
-
"-
|
|
285
|
+
"- 将AI 消息按照内在关联,划分为**连续消息**组成的模块,消息模块的划分方式不受限制,但内部须有足够的**逻辑连贯性**,应当是AI基于某个目的做出了一系列动作或论述",
|
|
286
|
+
"- 模块内的消息聚合输出为<assistant start=\"\" end=\"\">块,start/end表示当前模块首尾消息的index;块内不描述单条消息的细节,只描述当前模块的**目的、行为、结果**;最后一个<assistant>块,额外包含`下一步计划`",
|
|
287
|
+
"- 对于模块涉及到的具体文件,应当出现在<assistant>块中,多个前缀相同的路径要合并简写",
|
|
288
|
+
"- 对于单条重要的AI完整消息,以<assistant index=\"\">单独呈现,其内容不受限制",
|
|
289
|
+
"- 条目按 index 顺序覆盖本次压缩的全部完整消息,index/start/end 必须连续(区间内 index 连续、相邻条目相接),不跳号、不重叠、不遗漏。",
|
|
288
290
|
"- 总结时倾向于新消息,旧消息一句话带过即可;新旧消息冲突时强调新消息,不修改旧日志条目。",
|
|
289
|
-
"- 若【中断标记】非空,在对应位置明确写出中断(例如「被用户打断,因此上一段工作未完成」),帮助后续理解用户为何再次输入消息、为何不延续之前的工作。",
|
|
290
|
-
"- 若当前工作看起来未完成(最后一次工具调用没有结果、或对话被中断/异常结束),在日志末尾说明当前进度与下一步要做什么。",
|
|
291
291
|
"【输出格式】只输出一个 <om-history> 包裹的合法 XML 日志块,不要解释、不要复述规则:",
|
|
292
292
|
`<${HISTORY_TAG}>`,
|
|
293
293
|
"<user_message index=\"(index)\">",
|
|
294
294
|
"(user 消息原文)",
|
|
295
295
|
"</user_message>",
|
|
296
296
|
"<assistant start=\"(起始 index)\" end=\"(结束 index)\">",
|
|
297
|
-
"(
|
|
297
|
+
"(模块的目的、行为与结果摘要)",
|
|
298
298
|
"</assistant>",
|
|
299
299
|
"<assistant index=\"(index)\">",
|
|
300
|
-
"(
|
|
300
|
+
"(单条完整消息的模块摘要)",
|
|
301
301
|
"</assistant>",
|
|
302
302
|
`</${HISTORY_TAG}>`,
|
|
303
303
|
"",
|
|
304
|
-
"【中断标记】",
|
|
305
|
-
...interruptionSection,
|
|
306
|
-
"",
|
|
307
304
|
...options.hasOldHistory ? [`【说明】你的压缩结果会被直接追加到上一次压缩产物(<${HISTORY_TAG}>)的末尾,作为新的 <${HISTORY_TAG}> 块;条目格式与本块一致。`] : [`【说明】你的压缩结果将成为第一条 <${HISTORY_TAG}> 压缩日志。`]
|
|
308
305
|
].join("\n");
|
|
309
306
|
}
|
|
@@ -316,11 +313,14 @@ function buildReflectPrompt(mode) {
|
|
|
316
313
|
...mode === "new" ? ["下方的消息记录包含当前的 <om-history> 压缩日志(最后一次 <om-history> 块)。", "只对这份压缩日志做精简合并;不要涉及日志之外的消息。"] : ["上方的消息记录是主会话的完整历史,其中包含当前的 <om-history> 压缩日志(最后一次 <om-history> 块)。", "只对这份压缩日志做精简合并;不要涉及日志之外的消息。"],
|
|
317
314
|
"",
|
|
318
315
|
"【规则】",
|
|
319
|
-
"-
|
|
320
|
-
"-
|
|
321
|
-
"-
|
|
322
|
-
"-
|
|
323
|
-
"-
|
|
316
|
+
"- 当前消息仅作为指令,**不得**进入日志。",
|
|
317
|
+
"- 完整消息分三类(用户消息 / AI 文本 / 单个工具调用及其结果),各占一个 index(从 0 起、会话内全局稳定),index 是该条完整消息的序号。",
|
|
318
|
+
"- 将AI 消息按照内在关联,划分为**连续消息**组成的模块,消息模块的划分方式不受限制,但内部须有足够的**逻辑连贯性**,应当是AI基于某个目的做出了一系列动作或论述",
|
|
319
|
+
"- 模块内的消息聚合输出为<assistant start=\"\" end=\"\">块,start/end表示当前模块首尾消息的index;块内不描述单条消息的细节,只描述当前模块的**目的、行为、结果**;最后一个<assistant>块,额外包含`下一步计划`",
|
|
320
|
+
"- 对于模块涉及到的具体文件,应当出现在<assistant>块中,多个前缀相同的路径要合并简写",
|
|
321
|
+
"- 对于单条重要的AI完整消息,以<assistant index=\"\">单独呈现,其内容不受限制",
|
|
322
|
+
"- 用户消息保留要点与 index(格式:<user_message index=\"(index)\"> 要点 </user_message>);不重要的内容概括为「(略)」。",
|
|
323
|
+
"- 条目按 index 顺序覆盖日志中的全部完整消息,index/start/end 必须连续(区间内 index 连续、相邻条目相接),不跳号、不重叠、不遗漏;合并时条目的 index 不重新编号。",
|
|
324
324
|
"- 过时事实丢弃,不逐字复制旧文本;新旧条目冲突时保留新条目。",
|
|
325
325
|
"【输出格式】只输出一个 <om-history> 包裹的合法 XML 日志块,不要解释、不要复述规则:",
|
|
326
326
|
`<${HISTORY_TAG}>`,
|
|
@@ -328,10 +328,10 @@ function buildReflectPrompt(mode) {
|
|
|
328
328
|
"(user 消息要点)",
|
|
329
329
|
"</user_message>",
|
|
330
330
|
"<assistant start=\"(起始 index)\" end=\"(结束 index)\">",
|
|
331
|
-
"(
|
|
331
|
+
"(模块的目的、行为与结果摘要)",
|
|
332
332
|
"</assistant>",
|
|
333
333
|
"<assistant index=\"(index)\">",
|
|
334
|
-
"(
|
|
334
|
+
"(单条完整消息的模块摘要)",
|
|
335
335
|
"</assistant>",
|
|
336
336
|
`</${HISTORY_TAG}>`,
|
|
337
337
|
"",
|
|
@@ -442,7 +442,7 @@ function buildSummaryOptions(session, instruction, contextText, maxTokens, mode,
|
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
444
|
/** 产出日志后插入首个 <om-history> 后的格式说明(XML 注释,避免被误读为日志条目)。 */
|
|
445
|
-
const HISTORY_FORMAT_NOTE = "<!--
|
|
445
|
+
const HISTORY_FORMAT_NOTE = "<!-- 完整消息分三类(用户消息 / AI 文本 / 单个工具调用及其结果),各占一个 index(从 0 起、会话内全局稳定);<user_message index=\"N\"> 与 <assistant index=\"N\"> 表示单条完整消息,<assistant start=\"A\" end=\"B\"> 表示多条完整消息,start/end是首尾消息的index -->";
|
|
446
446
|
/**
|
|
447
447
|
* 从 AI 摘要输出中提取合法日志(不信任 AI 的总结结果):
|
|
448
448
|
* - 取首个 <om-history> 到最后一个 </om-history>(含两个首尾)切为日志;
|
|
@@ -617,7 +617,6 @@ function isPairBalancedAfter(session, seq) {
|
|
|
617
617
|
* 观察压缩区间:pre-step 触发时日志 call-result 完备,区间不再受 turn/end 封顶——
|
|
618
618
|
* 头部 → 表层长度-1-tailCount(尾部保留 tailCount 条不压缩),当前 turn 中已完备的
|
|
619
619
|
* 消息同样可压缩;区间终点回退到 tool-call/result 配对平衡点(不切段)。
|
|
620
|
-
* lastEndSeq 仅为中断扫描提供最后一个已结束 turn 的边界(无则 -1)。
|
|
621
620
|
*/
|
|
622
621
|
function computeCompressRange(session, tailCount) {
|
|
623
622
|
/** 当前表层节点(按日志顺序)。 */
|
|
@@ -642,33 +641,10 @@ function computeCompressRange(session, tailCount) {
|
|
|
642
641
|
return {
|
|
643
642
|
start,
|
|
644
643
|
end,
|
|
645
|
-
shadowedSeqs: surface.slice(0, endIdx + 1)
|
|
646
|
-
lastEndSeq: session.events.findLast((event) => event.type === "turn/end")?.seq ?? -1
|
|
644
|
+
shadowedSeqs: surface.slice(0, endIdx + 1)
|
|
647
645
|
};
|
|
648
646
|
}
|
|
649
647
|
/**
|
|
650
|
-
* 中断标记行:范围内 turn/end 以 aborted(含 cause 类型)或 interrupted 结束的轮次
|
|
651
|
-
* (标记用途:让摘要 AI 理解中断原因)。
|
|
652
|
-
*/
|
|
653
|
-
function scanInterruptions(session, fromSeq, toSeq) {
|
|
654
|
-
/** 标记行缓冲区。 */
|
|
655
|
-
const marks = [];
|
|
656
|
-
for (let seq = fromSeq + 1; seq <= toSeq; seq += 1) {
|
|
657
|
-
/** 当前待检查事件。 */
|
|
658
|
-
const event = session.events[seq];
|
|
659
|
-
if (event?.type !== "turn/end") continue;
|
|
660
|
-
/** 结束原因(判别 kind)。 */
|
|
661
|
-
const reason = event.data.reason;
|
|
662
|
-
if (!reason || typeof reason !== "object") continue;
|
|
663
|
-
if (reason.kind === "aborted") {
|
|
664
|
-
/** 取消来源(user/parent/hook/disposed;未知标记 unknown)。 */
|
|
665
|
-
const cause = reason.reason?.kind ?? "unknown";
|
|
666
|
-
marks.push(`[interrupted] turn ${String(event.data.turn)} 被中断(aborted,原因 ${cause})`);
|
|
667
|
-
} else if (reason.kind === "interrupted") marks.push(`[interrupted] turn ${String(event.data.turn)} 因崩溃恢复中断(interrupted)`);
|
|
668
|
-
}
|
|
669
|
-
return marks;
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
648
|
* 提取遮蔽区间内最后一次 <om-history> 压缩日志(内文 + seq)。
|
|
673
649
|
* 按表层顺序(shadowedSeqs)扫描:单节点替换(反思)后摘要节点 seq 可能大于
|
|
674
650
|
* 被压缩消息的 seq,按 seq 区间扫描会漏(start > end)。
|
|
@@ -863,18 +839,15 @@ async function observePass(ctx, agent, config, window, tailCount, target, signal
|
|
|
863
839
|
logger.step(`观察:压缩区间 [${range.start}..${range.end}],遮蔽 ${range.shadowedSeqs.length} 个表层节点`);
|
|
864
840
|
/** 区间内旧摘要(追加基准;无则首次压缩)。 */
|
|
865
841
|
const history = extractHistoryText(session, range.shadowedSeqs);
|
|
866
|
-
/** 中断标记行。 */
|
|
867
|
-
const interruptions = scanInterruptions(session, Math.min(...range.shadowedSeqs), range.lastEndSeq);
|
|
868
842
|
const actualTailCount = [...session.surface.nodes].length - range.shadowedSeqs.length;
|
|
869
843
|
/** 遮蔽 seq 集合(计算新消息起始 index)。 */
|
|
870
844
|
const shadowedSet = new Set(range.shadowedSeqs);
|
|
871
845
|
/** 压缩区间内第一个完整消息的 index(新消息起始编号;区间内无完整消息则 0)。 */
|
|
872
846
|
const startIndex = indexCompleteMessages(session).find((cm) => cm.seqs.every((seq) => shadowedSet.has(seq)))?.index ?? 0;
|
|
873
|
-
logger.step(`观察:实际保留尾部 ${actualTailCount}
|
|
847
|
+
logger.step(`观察:实际保留尾部 ${actualTailCount} 条(不压缩、不进日志),新消息起始 index ${startIndex}`);
|
|
874
848
|
/** 观察指令(persona + 规则主体)。 */
|
|
875
849
|
const prompt = buildObservePrompt({
|
|
876
850
|
startIndex,
|
|
877
|
-
interruptions,
|
|
878
851
|
hasOldHistory: history !== void 0,
|
|
879
852
|
mode: config.summaryMode
|
|
880
853
|
});
|
|
@@ -1135,7 +1108,7 @@ function ensureModelReady(modelDir = BUNDLED_MODEL_DIR, warn = () => {}, fetchIm
|
|
|
1135
1108
|
fetchImpl,
|
|
1136
1109
|
log: () => {}
|
|
1137
1110
|
}).then(() => {}).catch((err) => {
|
|
1138
|
-
warn(
|
|
1111
|
+
warn(`[download-model] 下载失败:${err instanceof Error ? err.message : String(err)}`);
|
|
1139
1112
|
}).finally(() => {
|
|
1140
1113
|
inflightDownloads.delete(modelDir);
|
|
1141
1114
|
});
|
|
@@ -1635,7 +1608,7 @@ function apply(ctx, config) {
|
|
|
1635
1608
|
logger.step(`apply 启动:thresholdRatio=${String(resolved.thresholdRatio)} historyMergeRatio=${String(resolved.historyMergeRatio)} compressMaxTokens=${String(resolved.compressMaxTokens)} tailMessageCount=${String(resolved.tailMessageCount)} summaryMode=${resolved.summaryMode} debug=${String(resolved.debug)}`);
|
|
1636
1609
|
if (resolved.recallEnabled) ctx.tools.register(buildRecallTool(() => ctx.get("toolResultPruner")));
|
|
1637
1610
|
if (resolved.semanticRecallEnabled) {
|
|
1638
|
-
const warnModel = (message) => ctx.logger.warn(
|
|
1611
|
+
const warnModel = (message) => ctx.logger.warn(`dsh-plugin-om: ${message}`);
|
|
1639
1612
|
ensureModelReady(resolved.modelDir, warnModel);
|
|
1640
1613
|
ctx.tools.register(buildSemanticRecallTool({
|
|
1641
1614
|
getPruner: () => ctx.get("toolResultPruner"),
|
package/dist/summarize.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - disable:关闭自动压缩(compress.ts 早退,不发起摘要调用)。
|
|
10
10
|
*
|
|
11
11
|
* 提示词不内嵌消息全文(fork 模式完整历史随请求传入;new 模式由 compress.ts 渲染
|
|
12
|
-
* 区间传入);新消息起始 index
|
|
12
|
+
* 区间传入);新消息起始 index 由插件从日志计算后内嵌——AI 按「完整消息」的
|
|
13
13
|
* index 编号(三类定义见 log-index.ts),recall 用同一套编号定位。token usage 从流式
|
|
14
14
|
* 响应的 usage chunk 提取,归入主会话记录。仅主会话生效(index.ts 守卫)。
|
|
15
15
|
*/
|
|
@@ -17,15 +17,13 @@ import type { SummaryMode } from './config.ts';
|
|
|
17
17
|
import type { Agent, Context, Session, TokenUsage } from './types.ts';
|
|
18
18
|
import { type RoutedTarget } from './utils.ts';
|
|
19
19
|
/** 观察者 persona:只针对未压缩消息产出观察日志,不用工具、不展示思考。 */
|
|
20
|
-
export declare const OBSERVER_PERSONA = "\
|
|
20
|
+
export declare const OBSERVER_PERSONA = "\u628A\u4F1A\u8BDD\u4E2D\u5C1A\u672A\u538B\u7F29\u7684\u6D88\u606F\u538B\u7F29\u4E3A\u4E00\u4EFD\u89C2\u5BDF\u65E5\u5FD7\u3002\u4E0D\u7528\u5DE5\u5177\u3001\u4E0D\u5C55\u793A\u601D\u8003\u3001\u4E0D\u8BC4\u4EF7\u4EE3\u7801\u3001\u4E0D\u8F93\u51FA\u591A\u4F59\u6587\u5B57\u3002";
|
|
21
21
|
/** 反思者 persona:只精简合并当前摘要,不用工具、不展示思考。 */
|
|
22
|
-
export declare const REFLECTOR_PERSONA = "\
|
|
23
|
-
/** 观察提示词参数(起始 index
|
|
22
|
+
export declare const REFLECTOR_PERSONA = "\u628A\u5F53\u524D <om-history> \u538B\u7F29\u65E5\u5FD7\u7CBE\u7B80\u5408\u5E76\u4E3A\u4E00\u4EFD\u66F4\u7D27\u51D1\u7684\u65E5\u5FD7\u3002\u4E0D\u7528\u5DE5\u5177\u3001\u4E0D\u5C55\u793A\u601D\u8003\u3001\u4E0D\u8F93\u51FA\u591A\u4F59\u6587\u5B57\u3002";
|
|
23
|
+
/** 观察提示词参数(起始 index 由 compress.ts 从日志计算后传入)。 */
|
|
24
24
|
export type ObservePromptOptions = {
|
|
25
25
|
/** 本次要压缩的新消息起始完整消息 index(AI 从该序号开始编号)。 */
|
|
26
26
|
startIndex: number;
|
|
27
|
-
/** 中断标记行(aborted/interrupted 轮次)。 */
|
|
28
|
-
interruptions: string[];
|
|
29
27
|
/** 是否存在旧摘要(决定「追加到上次产物末尾」的表述)。 */
|
|
30
28
|
hasOldHistory: boolean;
|
|
31
29
|
/** 摘要模式:fork=完整历史随请求传入;new=被压缩消息渲染为输入。 */
|
|
@@ -35,9 +33,9 @@ export type ObservePromptOptions = {
|
|
|
35
33
|
* 构建观察指令主体:任务声明(fork:停止任务/禁止工具;new:说明总结日志)+ 模式相关
|
|
36
34
|
* 的上下文定位与压缩范围(fork:上方完整会话记录——尾部已在输入中实际截断,提示词不含
|
|
37
35
|
* 尾部规则;new:下方消息即压缩对象)+ 完整消息与 index 定义(三类合并规则、起始编号)+
|
|
38
|
-
* 规则(用户消息完整保留原文 / AI
|
|
39
|
-
* 倾向于新消息 /
|
|
40
|
-
*
|
|
36
|
+
* 规则(用户消息完整保留原文 / AI 消息按模块压缩 / 本指令不入日志 / index/start/end 连续 /
|
|
37
|
+
* 倾向于新消息 / 未完成写进度与下一步)+ 输出格式(合法 XML)+ 追加说明。
|
|
38
|
+
* persona 由调用方拼接到指令开头。
|
|
41
39
|
*/
|
|
42
40
|
export declare function buildObservePrompt(options: ObservePromptOptions): string;
|
|
43
41
|
/** 构建反思指令主体:任务声明(fork:停止任务/禁止工具;new:说明总结日志)+ 精简合并
|
|
@@ -61,7 +59,7 @@ export declare function renderMessages(session: Session, seqs: readonly number[]
|
|
|
61
59
|
/** 日志最小有效长度:<om-history> 中间内容小于该长度视为不合法(C 段校验)。 */
|
|
62
60
|
export declare const MIN_HISTORY_LENGTH = 10;
|
|
63
61
|
/** 产出日志后插入首个 <om-history> 后的格式说明(XML 注释,避免被误读为日志条目)。 */
|
|
64
|
-
export declare const HISTORY_FORMAT_NOTE = "<!-- \
|
|
62
|
+
export declare const HISTORY_FORMAT_NOTE = "<!-- \u5B8C\u6574\u6D88\u606F\u5206\u4E09\u7C7B\uFF08\u7528\u6237\u6D88\u606F / AI \u6587\u672C / \u5355\u4E2A\u5DE5\u5177\u8C03\u7528\u53CA\u5176\u7ED3\u679C\uFF09\uFF0C\u5404\u5360\u4E00\u4E2A index\uFF08\u4ECE 0 \u8D77\u3001\u4F1A\u8BDD\u5185\u5168\u5C40\u7A33\u5B9A\uFF09\uFF1B<user_message index=\"N\"> \u4E0E <assistant index=\"N\"> \u8868\u793A\u5355\u6761\u5B8C\u6574\u6D88\u606F\uFF0C<assistant start=\"A\" end=\"B\"> \u8868\u793A\u591A\u6761\u5B8C\u6574\u6D88\u606F\uFF0Cstart/end\u662F\u9996\u5C3E\u6D88\u606F\u7684index -->";
|
|
65
63
|
/**
|
|
66
64
|
* 从 AI 摘要输出中提取合法日志(不信任 AI 的总结结果):
|
|
67
65
|
* - 取首个 <om-history> 到最后一个 </om-history>(含两个首尾)切为日志;
|
package/dist/summarize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../src/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,YAAY,CAAC;AAErD,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../src/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,YAAY,CAAC;AAErD,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,yRACqB,CAAC;AAEnD,wCAAwC;AACxC,eAAO,MAAM,iBAAiB,mQAC4B,CAAC;AAE3D,gDAAgD;AAChD,MAAM,MAAM,oBAAoB,GAAG;IACjC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAuDxE;AAED;qEACqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA+C5D;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB,CAAC;AA4CF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAehF;AA4DD,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,4hBAC+J,CAAC;AAEhM;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB5D;AAED,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAKtC;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA+EvC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-om",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"build": "tsdown && tsc -p tsconfig.build.json",
|
|
43
43
|
"download:model": "node scripts/download-model.mjs",
|
|
44
44
|
"check": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
|
|
45
|
-
"release": "pnpm format && npm version patch && node scripts/release-archive.mjs && git push && git push --tags",
|
|
45
|
+
"release": "pnpm check && pnpm format && npm version patch && node scripts/release-archive.mjs && git push && git push --tags",
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
47
47
|
"test": "vitest run",
|
|
48
48
|
"build:types": "tsc -p tsconfig.build.json",
|