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 CHANGED
@@ -19,7 +19,12 @@
19
19
  dsh plugin --profile <profile> add dsh-plugin-om
20
20
  ```
21
21
 
22
- 需要重启dsh
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 区间计算 / 配对平衡回退 / 中断扫描 / source 标记判定摘要消息 / compaction/* 生命周期事件 + checkpoint 替换)
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)
@@ -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 可能大于
@@ -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;;;;;GAKG;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,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAyBxF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB5F;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,CAwIf;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"}
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 = "你是 dsh-plugin-om 的上下文观察者(Observer,机制参考 Mastra Observational Memory):把会话中尚未压缩的消息压缩为一份观察日志。不用工具、不展示思考、不评价代码、不输出多余文字。";
257
+ const OBSERVER_PERSONA = "把会话中尚未压缩的消息压缩为一份观察日志。不用工具、不展示思考、不评价代码、不输出多余文字。";
258
258
  /** 反思者 persona:只精简合并当前摘要,不用工具、不展示思考。 */
259
- const REFLECTOR_PERSONA = "你是 dsh-plugin-om 的上下文反思者(Reflector,机制参考 Mastra Observational Memory):把当前 <om-history> 压缩日志精简合并为一份更紧凑的日志。不用工具、不展示思考、不输出多余文字。";
259
+ const REFLECTOR_PERSONA = "把当前 <om-history> 压缩日志精简合并为一份更紧凑的日志。不用工具、不展示思考、不输出多余文字。";
260
260
  /**
261
261
  * 构建观察指令主体:任务声明(fork:停止任务/禁止工具;new:说明总结日志)+ 模式相关
262
262
  * 的上下文定位与压缩范围(fork:上方完整会话记录——尾部已在输入中实际截断,提示词不含
263
263
  * 尾部规则;new:下方消息即压缩对象)+ 完整消息与 index 定义(三类合并规则、起始编号)+
264
- * 规则(用户消息完整保留原文 / AI 消息模块化压缩——工具调用按目的聚合——不限于 run_code /
265
- * 倾向于新消息 / 中断标注 / 未完成写进度与下一步)+ 输出格式(合法 XML)+ 中断标记 +
266
- * 追加说明。persona 由调用方拼接到指令开头。
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
- "- 完整消息是压缩日志与 recall 共用的定位单位,每一条占一个 index(从 0 起、按会话顺序递增、全局稳定)。完整消息分三类:用户消息占一条;AI 文本占一条;工具调用及其结果占一条(每个 tool-call 与其 result 各一条,同一条 AI 消息里的文本与工具调用拆开)。",
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 消息划分模块压缩(按模块聚合 assistant 的方式):所有工具调用(run_code 与其他工具同等对待,不限于 run_code)按调用目的聚合为一行 toolcall index:<该条完整消息的 index> purpose:<聚合目的> summary:<行为与结果摘要>;相邻的 AI 侧完整消息(文本与工具调用)聚合为一个 <assistant> 模块,start/end 标注模块覆盖的 index 区间(start 为首条 index、end 为末条 index);重要工具调用(关键决策、写文件等)单独用 <assistant index=\"N\"> 呈现。工具组内部细节(参数、完整输出)不保留,需要原文时用 recall 按 index 回看(start/end 定位)。",
287
- "- 条目须按 index 顺序覆盖本次压缩的全部完整消息,不遗漏。",
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
- "(压缩模块:toolcall 聚合行等,按模块聚合 assistant 的方式)",
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
- "- 完整消息是压缩日志的定位单位(用户消息 / AI 文本 / 工具调用及其结果各占一个 index,从 0 起、全局稳定);合并时单个完整消息的 index 不重新编号,被删除的条目 index 空缺。",
320
- "- 用户消息保留要点与 index(格式:<user_message index=\"(index)\"> 要点 </user_message>);可省略的条目删除。",
321
- "- toolcall 条目按调用目的进一步聚合,保留 index;不重要的条目 summary 写「(略)」。",
322
- "- 相邻的 assistant 条目可合并为一个 <assistant> 模块,start/end 随合并后的区间变化。",
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
- "(压缩模块:toolcall 聚合行等,按模块聚合 assistant 的方式)",
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 = "<!-- 本日志以「完整消息」为定位单位:完整消息分三类(用户消息 / AI 文本 / 单个工具调用及其结果),各占一个 index(从 0 起、会话内全局稳定);<user_message index=\"N\"> 与 <assistant index=\"N\"> 定位单条完整消息,<assistant start=\"A\" end=\"B\"> 定位连续区间(含两端)。需要原文时用 recall(start, end) 按 index 回看。 -->";
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} 条(不压缩、不进日志),中断标记 ${interruptions.length} 条,新消息起始 index ${startIndex}`);
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("[download-model] 下载失败:" + (err instanceof Error ? err.message : String(err)));
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("dsh-plugin-om: " + message);
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"),
@@ -9,7 +9,7 @@
9
9
  * - disable:关闭自动压缩(compress.ts 早退,不发起摘要调用)。
10
10
  *
11
11
  * 提示词不内嵌消息全文(fork 模式完整历史随请求传入;new 模式由 compress.ts 渲染
12
- * 区间传入);新消息起始 index 与中断标记由插件从日志计算后内嵌——AI 按「完整消息」的
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 = "\u4F60\u662F dsh-plugin-om \u7684\u4E0A\u4E0B\u6587\u89C2\u5BDF\u8005\uFF08Observer\uFF0C\u673A\u5236\u53C2\u8003 Mastra Observational Memory\uFF09\uFF1A\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";
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 = "\u4F60\u662F dsh-plugin-om \u7684\u4E0A\u4E0B\u6587\u53CD\u601D\u8005\uFF08Reflector\uFF0C\u673A\u5236\u53C2\u8003 Mastra Observational Memory\uFF09\uFF1A\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 从日志计算后传入)。 */
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 消息模块化压缩——工具调用按目的聚合——不限于 run_code /
39
- * 倾向于新消息 / 中断标注 / 未完成写进度与下一步)+ 输出格式(合法 XML)+ 中断标记 +
40
- * 追加说明。persona 由调用方拼接到指令开头。
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 = "<!-- \u672C\u65E5\u5FD7\u4EE5\u300C\u5B8C\u6574\u6D88\u606F\u300D\u4E3A\u5B9A\u4F4D\u5355\u4F4D\uFF1A\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\"> \u5B9A\u4F4D\u5355\u6761\u5B8C\u6574\u6D88\u606F\uFF0C<assistant start=\"A\" end=\"B\"> \u5B9A\u4F4D\u8FDE\u7EED\u533A\u95F4\uFF08\u542B\u4E24\u7AEF\uFF09\u3002\u9700\u8981\u539F\u6587\u65F6\u7528 recall(start, end) \u6309 index \u56DE\u770B\u3002 -->";
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>(含两个首尾)切为日志;
@@ -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,kbACyF,CAAC;AAEvH,wCAAwC;AACxC,eAAO,MAAM,iBAAiB,6ZACiG,CAAC;AAEhI,oDAAoD;AACpD,MAAM,MAAM,oBAAoB,GAAG;IACjC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,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,CA0DxE;AAED;qEACqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA4C5D;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,mrBACmM,CAAC;AAEpO;;;;;;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"}
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.7",
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",