dsh-plugin-om 0.0.22 → 0.0.23

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
@@ -7,12 +7,13 @@
7
7
  ## 功能
8
8
 
9
9
  - **自动压缩**(仅主会话,`agent/pre-step` 时阻塞串行执行):
10
- - **观察**:净压力(上下文压力 − 已压缩 `<history>` 块 token 估算合计)≥ `observeThresholdTokens` 时,摘要未压缩消息并追加为新 `<history>` 块,精确替换被压缩的消息区间(旧块保留)
10
+ - **观察**:净压力(上下文压力 − 已压缩 `<history>` 块 token 估算合计 − 系统提示词 token 估算 − 工具定义 token 估算)≥ `observeThresholdTokens` 时,摘要未压缩消息并追加为新 `<history>` 块,精确替换被压缩的消息区间(旧块保留)
11
11
  - **反思**:全部 `<history>` 块 token 合计 ≥ `reflectThresholdTokens` 时,把多个摘要块合并为一条更紧凑的摘要
12
12
  - **输出容错**:摘要输出按首个 `<history>` 开标签到最后一个 `</history>` 定位;整块 XML 非法时按条目标签模糊提取并重建为合法块,再校验无 reasoning 与 index 连续
13
+ - **失败中断**:摘要尝试全部耗尽后拒绝当前 step(当前 turn 以 blocked 结束,不再继续 AI 会话;signal 中止除外),最后一次尝试的实际报错写入日志与 `compaction/end` error
13
14
  - **recall 工具**:按完整消息 index 区间回看原始会话(含被压缩内容;命中消息中的图片附件随结果保留)
14
15
  - **recall-semantic 工具**:本地嵌入模型(paraphrase-multilingual-MiniLM-L12-v2)按语义检索全部完整消息(只匹配文本,纯图片消息不进候选池)
15
- - **压缩卡片**:浏览器客户端在消息列表渲染折叠式「已压缩」卡片与「正在压缩上下文(观察/反思)…」提示行
16
+ - **压缩卡片**:浏览器客户端在消息列表渲染折叠式「已压缩」卡片、「正在压缩上下文(观察/反思)…」提示行与可展开的「上下文压缩失败」错误行
16
17
 
17
18
  ## 安装与启用
18
19
 
@@ -32,7 +33,7 @@ dsh plugin --profile <profile> add dsh-plugin-om
32
33
  ```yaml
33
34
  - id: dsh-plugin-om
34
35
  config:
35
- observeThresholdTokens: 35000
36
+ observeThresholdTokens: 45000
36
37
  ```
37
38
 
38
39
  ### 开发插件
@@ -53,15 +54,15 @@ preset-agent 自带 `compaction-basic` 压缩,也会压缩上下文,其压
53
54
 
54
55
  | 键 | 默认 | 含义 |
55
56
  | --- | --- | --- |
56
- | `observeThresholdTokens` | `35000` | 观察阈值(tokens):净压力 该值时触发观察压缩 |
57
- | `reflectThresholdTokens` | `40000` | 反思阈值(tokens):全部 `<history>` 块 token 合计 ≥ 该值时触发合并 |
58
- | `compressMaxTokens` | `5000` | 单次摘要生成上限 |
57
+ | `observeThresholdTokens` | `45000` | 观察阈值(tokens):净压力(上下文压力 已压缩块 − 系统提示词估算 − 工具定义估算)≥ 该值时触发观察压缩 |
58
+ | `reflectThresholdTokens` | `120000` | 反思阈值(tokens):全部 `<history>` 块 token 合计 ≥ 该值时触发合并 |
59
+ | `compressMaxTokens` | 不设置 | 单次摘要生成上限(不设置时由模型适配器默认值决定) |
59
60
  | `rateLimitWaitMs` | `60000` | 遇 429 限流后下一次摘要请求前的等待毫秒数(全局冷却期;`0` 不限流) |
60
61
  | `tailMessageCount` | `5` | 尾部保留的不压缩消息条数 |
61
62
  | `compressRetryCount` | `5` | 摘要失败后的最大重试次数(不含首次) |
62
63
  | `modelDir` | 共享目录 | recall-semantic 嵌入模型目录(默认 `$DSH_HOME/plugin-data/dsh-plugin-om/models/<id>`,onnx 缺失且启用语义召回时运行时自动下载) |
63
64
  | `omEnabled` | `true` | 是否启用自动压缩(`false` 时关闭,recall 工具不受影响) |
64
- | `debug` | dev | 步骤级日志开关(缺省按 `NODE_ENV !== 'production'` 判定;失败日志始终输出) |
65
+ | `debug` | dev | 步骤级日志开关(缺省按 `NODE_ENV !== 'production'` 判定;每次压缩尝试的结果/报错与失败日志始终输出) |
65
66
  | `recallEnabled` | `true` | 是否注册 `recall` 工具 |
66
67
  | `semanticRecallEnabled` | `true` | 是否注册 `recall-semantic` 工具(`false` 时不注册、不触发模型下载) |
67
68
 
@@ -98,11 +99,11 @@ src/
98
99
  ├── embedding.ts # 本地 ONNX 嵌入(懒加载 / 批量 / 运行时按需下载编排)
99
100
  ├── model-download.ts # 模型下载原语(URL / 跳过判定 / 原子落盘)
100
101
  ├── summarize.ts # 共享压缩提示词 + <history> 块渲染与输出校验 + 摘要调用
101
- ├── compress.ts # 两级自动压缩(观察/反思)与 compaction 生命周期事件
102
+ ├── compress.ts # 两级自动压缩(观察/反思)、失败中断传播与 compaction 生命周期事件
102
103
  └── client/ # 浏览器客户端 bundle(压缩卡片)
103
104
  ├── index.ts # 客户端入口:注册卡片定义与渲染器
104
105
  ├── definition.ts # 压缩卡片业务定义(认领生命周期事件与替换检查点)
105
- ├── OmCompactionCard.tsx # 折叠卡片渲染器(统计标题 + summary 展开)
106
+ ├── OmCompactionCard.tsx # 折叠卡片渲染器(统计标题 + summary/失败报错展开)
106
107
  ├── format.ts # 统计数字紧凑格式化(k/w/M)
107
108
  └── locales.ts # om-compaction 文案字典(zh/en)
108
109
  models/ # 嵌入模型目录(小文件随包分发;onnx 运行时按需下载,不进 git)
@@ -3,6 +3,7 @@
3
3
  * 复用平台原语 DisclosureRow;行内统计与展开体样式按宿主 ContextInjectionRow 的规则
4
4
  * 注入 <style>(宿主该卡片样式是编译期哈希的 CSS Module,插件 bundle 无法引用其类名)。
5
5
  * 卡片报告模型从何处起不再看到那段历史;summary 留在加载窗口外时行不可展开。
6
+ * 压缩失败节点渲染为可展开的错误行:折叠行显示错误摘要,展开体显示完整报错。
6
7
  */
7
8
  import type { ChatNode } from '@deepseek-ai/dsh-client-ui-conversation/client';
8
9
  import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
@@ -1 +1 @@
1
- {"version":3,"file":"OmCompactionCard.d.ts","sourceRoot":"","sources":["../../src/client/OmCompactionCard.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAKpE,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACzC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,CAAC;CAC1C;AAgCD,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,oDAA+C,qBAAqB,iCAsF/F,CAAC"}
1
+ {"version":3,"file":"OmCompactionCard.d.ts","sourceRoot":"","sources":["../../src/client/OmCompactionCard.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAKpE,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACzC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,CAAC;CAC1C;AAgCD,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,oDAA+C,qBAAqB,iCA2H/F,CAAC"}
@@ -37,12 +37,14 @@ export interface OmCompactionChatData {
37
37
  readonly summaryTokenCount: number | null;
38
38
  /** 摘要调用重试次数,载荷缺失或非法时为 null。 */
39
39
  readonly retryCount: number | null;
40
+ /** 压缩失败的错误文案(来自 compaction/end 载荷 error);非失败节点为 null。 */
41
+ readonly error: string | null;
40
42
  }
41
43
  /** 压缩生命周期关联状态:summary / checkpoint / end 事件证据。 */
42
44
  interface OmCompactionState {
43
45
  readonly summary?: ConversationMatch;
44
46
  readonly checkpoint?: ConversationMatch;
45
- /** compaction/end(成功与失败都会到达;无 checkpoint 的 end 表示压缩失败,撤回进行中提示行)。 */
47
+ /** compaction/end(成功与失败都会到达;无 checkpoint 的 end 表示压缩失败或中止)。 */
46
48
  readonly end?: ConversationMatch;
47
49
  }
48
50
  /**
@@ -52,9 +54,10 @@ interface OmCompactionState {
52
54
  export declare function checkpointCompactionId(event: SessionEvent): string | undefined;
53
55
  /**
54
56
  * 插件压缩生命周期定义:compaction/start 开启上下文,summary / 替换检查点 / end
55
- * 作为 update 折叠证据。有检查点时产出摘要卡片;无检查点的 end hidden 撤回
56
- * 进行中提示行(已物化节点以 hidden visibility 保留同一 key);仅 start 时渲染
57
- * 「正在压缩上下文…」提示行。
57
+ * 作为 update 折叠证据。有检查点时产出摘要卡片;end 已到且无检查点时,error
58
+ * 非中止报错则产出可见失败行,error 为中止标识或缺失则以 hidden 撤回进行中提示行
59
+ * (已物化节点以 hidden visibility 保留同一 key);仅 start 时渲染「正在压缩上下文…」
60
+ * 提示行。
58
61
  */
59
62
  export declare const omCompactionDefinition: ConversationNodeDefinition<OmCompactionState>;
60
63
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/client/definition.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAEjB,0BAA0B,EAC3B,MAAM,wCAAwC,CAAC;AAGhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAInE,uCAAuC;AACvC,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,OAAO,QAAQ,gDAAgD,CAAC;IAC9D,UAAU,eAAe;QACvB,sCAAsC;QACtC,eAAe,EAAE,oBAAoB,CAAC;KACvC;CACF;AAED,wBAAwB;AACxB,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7C,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gDAAgD;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,4CAA4C;IAC5C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,8BAA8B;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,6CAA6C;IAC7C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,kDAAkD;AAClD,UAAU,iBAAiB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC;IACxC,oEAAoE;IACpE,QAAQ,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAgBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAe9E;AAwGD;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,0BAA0B,CAAC,iBAAiB,CAsDhF,CAAC"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/client/definition.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAEjB,0BAA0B,EAC3B,MAAM,wCAAwC,CAAC;AAGhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAInE,uCAAuC;AACvC,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,OAAO,QAAQ,gDAAgD,CAAC;IAC9D,UAAU,eAAe;QACvB,sCAAsC;QACtC,eAAe,EAAE,oBAAoB,CAAC;KACvC;CACF;AAED,wBAAwB;AACxB,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7C,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gDAAgD;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,4CAA4C;IAC5C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,8BAA8B;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,6CAA6C;IAC7C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,kDAAkD;AAClD,UAAU,iBAAiB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC;IACxC,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAgBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAe9E;AAuID;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,0BAA0B,CAAC,iBAAiB,CAqEhF,CAAC"}
@@ -14,6 +14,9 @@ export declare const zh: {
14
14
  readonly 'compaction.running': "正在压缩上下文…";
15
15
  readonly 'compaction.running.observe': "正在压缩上下文(观察)…";
16
16
  readonly 'compaction.running.reflect': "正在压缩上下文(反思)…";
17
+ readonly 'compaction.failed': "上下文压缩失败";
18
+ readonly 'compaction.failed.observe': "上下文压缩失败(观察)";
19
+ readonly 'compaction.failed.reflect': "上下文压缩失败(反思)";
17
20
  readonly 'compaction.retries': "重试 {count} 次";
18
21
  };
19
22
  /** English dictionary(键集与 zh 一致)。 */
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iDAAiD;AACjD,eAAO,MAAM,EAAE,kBAAkB,CAAC;AAElC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;CAWL,CAAC;AAEX,qCAAqC;AACrC,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,OAAO,EAAE,EAAE,MAAM,CAW9C,CAAC;AAEF,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,mDAAmD;QACnD,eAAe,EAAE,MAAM,OAAO,EAAE,CAAC;KAClC;CACF"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iDAAiD;AACjD,eAAO,MAAM,EAAE,kBAAkB,CAAC;AAElC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;CAcL,CAAC;AAEX,qCAAqC;AACrC,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,OAAO,EAAE,EAAE,MAAM,CAc9C,CAAC;AAEF,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,mDAAmD;QACnD,eAAe,EAAE,MAAM,OAAO,EAAE,CAAC;KAClC;CACF"}
package/dist/client.js CHANGED
@@ -51,6 +51,7 @@ window.__ModuleLoader__.load({
51
51
  return {
52
52
  running: false,
53
53
  phase: null,
54
+ error: null,
54
55
  summary,
55
56
  summaryEventSeq: summaryMatch?.event.seq ?? null,
56
57
  shadowedItemCount,
@@ -61,12 +62,38 @@ window.__ModuleLoader__.load({
61
62
  retryCount
62
63
  };
63
64
  }
65
+ /** 从 compaction/start 载荷读取压缩 pass(observe/reflect;缺失或非法时回落 null)。 */
66
+ function startPhase(start) {
67
+ const data = start?.event.data;
68
+ return data?.phase === "observe" || data?.phase === "reflect" ? data.phase : null;
69
+ }
64
70
  /** 压缩进行中提示行的载荷(统计未就绪,全部为 null)。 */
65
71
  function runningData(start) {
66
- const data = start?.event.data;
67
72
  return {
68
73
  running: true,
69
- phase: data?.phase === "observe" || data?.phase === "reflect" ? data.phase : null,
74
+ phase: startPhase(start),
75
+ error: null,
76
+ summary: null,
77
+ summaryEventSeq: null,
78
+ shadowedItemCount: null,
79
+ shadowedTokenCount: null,
80
+ shadowedCharCount: null,
81
+ summaryCharCount: null,
82
+ summaryTokenCount: null,
83
+ retryCount: null
84
+ };
85
+ }
86
+ /**
87
+ * 压缩失败节点的载荷:end 已到且带非中止 error、无检查点时使用。
88
+ * error 取 compaction/end 载荷中的实际报错文案(非字符串或为空时回落 null);
89
+ * 摘要与统计字段全部为 null。
90
+ */
91
+ function failureData(start, end) {
92
+ const data = end.event.data;
93
+ return {
94
+ running: false,
95
+ phase: startPhase(start),
96
+ error: typeof data?.error === "string" && data.error !== "" ? data.error : null,
70
97
  summary: null,
71
98
  summaryEventSeq: null,
72
99
  shadowedItemCount: null,
@@ -103,9 +130,10 @@ window.__ModuleLoader__.load({
103
130
  }
104
131
  /**
105
132
  * 插件压缩生命周期定义:compaction/start 开启上下文,summary / 替换检查点 / end
106
- * 作为 update 折叠证据。有检查点时产出摘要卡片;无检查点的 end hidden 撤回
107
- * 进行中提示行(已物化节点以 hidden visibility 保留同一 key);仅 start 时渲染
108
- * 「正在压缩上下文…」提示行。
133
+ * 作为 update 折叠证据。有检查点时产出摘要卡片;end 已到且无检查点时,error
134
+ * 非中止报错则产出可见失败行,error 为中止标识或缺失则以 hidden 撤回进行中提示行
135
+ * (已物化节点以 hidden visibility 保留同一 key);仅 start 时渲染「正在压缩上下文…」
136
+ * 提示行。
109
137
  */
110
138
  const omCompactionDefinition = {
111
139
  kind: COMPACTION_CARD_KIND,
@@ -150,6 +178,8 @@ window.__ModuleLoader__.load({
150
178
  }
151
179
  if (state.end !== void 0) {
152
180
  const anchorSeq = context.start?.event.seq ?? state.end.event.seq;
181
+ const endData = state.end.event.data;
182
+ if (typeof endData?.error === "string" && endData.error !== "" && !endData.error.startsWith("压缩已中止(signal aborted)")) return chatNode(context, COMPACTION_CARD_KIND, anchorSeq, failureData(context.start, state.end));
153
183
  return chatNode(context, COMPACTION_CARD_KIND, anchorSeq, runningData(context.start), "hidden");
154
184
  }
155
185
  if (context.start !== void 0) return chatNode(context, COMPACTION_CARD_KIND, context.start.event.seq, runningData(context.start));
@@ -174,6 +204,9 @@ window.__ModuleLoader__.load({
174
204
  "compaction.running": "正在压缩上下文…",
175
205
  "compaction.running.observe": "正在压缩上下文(观察)…",
176
206
  "compaction.running.reflect": "正在压缩上下文(反思)…",
207
+ "compaction.failed": "上下文压缩失败",
208
+ "compaction.failed.observe": "上下文压缩失败(观察)",
209
+ "compaction.failed.reflect": "上下文压缩失败(反思)",
177
210
  "compaction.retries": "重试 {count} 次"
178
211
  };
179
212
  /** English dictionary(键集与 zh 一致)。 */
@@ -186,6 +219,9 @@ window.__ModuleLoader__.load({
186
219
  "compaction.running": "Compressing context…",
187
220
  "compaction.running.observe": "Compressing context (observation)…",
188
221
  "compaction.running.reflect": "Compressing context (reflection)…",
222
+ "compaction.failed": "Context compaction failed",
223
+ "compaction.failed.observe": "Context compaction failed (observation)",
224
+ "compaction.failed.reflect": "Context compaction failed (reflection)",
189
225
  "compaction.retries": "Retried {count} times"
190
226
  };
191
227
  //#endregion
@@ -248,6 +284,38 @@ window.__ModuleLoader__.load({
248
284
  })
249
285
  });
250
286
  }
287
+ if (data.error !== null) {
288
+ const failedLabel = data.phase === "observe" ? t("compaction.failed.observe") : data.phase === "reflect" ? t("compaction.failed.reflect") : t("compaction.failed");
289
+ const errorSummary = data.error.length > 80 ? `${data.error.slice(0, 80)}…` : data.error;
290
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
291
+ style: styles.row,
292
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
293
+ className: css.root,
294
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
295
+ chevronClassName: css.chevron,
296
+ title: failedLabel,
297
+ collapsedContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
298
+ className: css.sep,
299
+ "aria-hidden": true
300
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
301
+ className: css.summary,
302
+ children: errorSummary
303
+ })] }),
304
+ keepContentWhenOpen: true,
305
+ expandable: true,
306
+ expandOnRowClick: true,
307
+ open: expanded,
308
+ onToggle: () => {
309
+ setExpanded((value) => !value);
310
+ },
311
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
312
+ className: css.body,
313
+ style: styles.body,
314
+ children: data.error
315
+ })
316
+ })
317
+ });
318
+ }
251
319
  const expandable = data.summary !== null;
252
320
  const open = expandable && expanded;
253
321
  const stats = data.shadowedItemCount !== null && data.shadowedCharCount !== null && data.shadowedTokenCount !== null && data.summaryCharCount !== null && data.summaryTokenCount !== null ? t("compaction.stats", {
@@ -293,7 +361,7 @@ window.__ModuleLoader__.load({
293
361
  });
294
362
  const styles = {
295
363
  row: {},
296
- /** 摘要为 <history> 文本,保留换行。 */
364
+ /** 摘要与失败报错为多行文本,保留换行。 */
297
365
  body: { whiteSpace: "pre-wrap" }
298
366
  };
299
367
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["memo","useState","DisclosureRow","IconBrowseOutline16"],"sources":["../src/constants.ts","../src/client/definition.ts","../src/client/locales.ts","../src/client/format.ts","../src/client/OmCompactionCard.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * 共享常量:插件级魔法字符串,集中定义避免散落各模块。\n * 导出 PLUGIN_LABEL / HISTORY_TAG / HISTORY_TIP / COMPLETE_MESSAGE_DEFINITION /\n * COMPACT_CHECKPOINT_PLUGIN / isPluginOwnedSource。\n */\n\n/** 插件标识:压缩消息 source.plugin 取值与日志前缀。 */\nexport const PLUGIN_LABEL = 'dsh-plugin-om';\n\n/** 压缩日志标签名:<history>...</history> 包裹观察/反思日志块。 */\nexport const HISTORY_TAG = 'history';\n\n/** 压缩日志块开标签的 tip 属性(对 AI 的提醒:本块是历史压缩产物,不要复述)。 */\nexport const HISTORY_TIP = '当前块是历史消息的压缩产物,不要复述';\n\n/**\n * 「完整消息」定义(提示词 / 工具描述 / 块顶注释共用)。\n * 完整消息是摘要日志与 recall 共用的定位单位;首条 index 为 0,按会话顺序递增、全局稳定。\n */\nexport const COMPLETE_MESSAGE_DEFINITION =\n '`完整消息`被定义为`用户消息`,`系统消息`,`模型输出文本`和`具有result的toolcall`的集合。首条`完整消息`的index是0,后续的index递增。';\n\n/** 旧日志压缩消息的宿主 checkpoint 标记 plugin 名(历史兼容识别用)。 */\nexport const COMPACT_CHECKPOINT_PLUGIN = 'compact';\n\n/** 判定 user/message 的 source 是否为本插件自产(压缩日志消息;含旧宿主 checkpoint 标记兼容)。这类消息不占完整消息 index。 */\nexport function isPluginOwnedSource(\n source: { kind?: string; plugin?: string } | undefined,\n): boolean {\n if (source?.kind !== 'plugin') return false;\n return source.plugin === PLUGIN_LABEL || source.plugin === COMPACT_CHECKPOINT_PLUGIN;\n}\n","/**\n * 压缩卡片业务定义:认领插件自产的压缩生命周期事件(compaction/start|summary|end)\n * 与替换检查点(source.plugin = 'dsh-plugin-om'),聚合出可渲染的摘要卡片节点。\n * 导出 COMPACTION_CARD_KIND / OmCompactionChatData / checkpointCompactionId /\n * omCompactionDefinition。宿主的 'compact' 检查点由宿主自己渲染,本定义不认领\n * (双方共存无视觉冲突:宿主无检查点证据时不产出节点)。\n */\n\nimport type {\n ConversationMatch,\n ConversationNodeContext,\n ConversationNodeDefinition,\n} from '@deepseek-ai/dsh-client-runtime/client';\nimport type { ChatNode, ChatNodeDataMap } from '@deepseek-ai/dsh-client-ui-conversation/client';\nimport type {} from '@deepseek-ai/dsh-compaction/types';\nimport type { SessionEvent } from '@deepseek-ai/dsh-session/types';\nimport { PLUGIN_LABEL } from '../constants.ts';\nimport type { CompactionSummaryPayload } from '../types.ts';\n\n/** 压缩卡片渲染器分发键(合并进 ChatNodeDataMap)。 */\nexport const COMPACTION_CARD_KIND = 'om-compaction';\n\ndeclare module '@deepseek-ai/dsh-client-ui-conversation/client' {\n interface ChatNodeDataMap {\n /** dsh-plugin-om 压缩替换检查点渲染的摘要卡片数据。 */\n 'om-compaction': OmCompactionChatData;\n }\n}\n\n/** 压缩卡片载荷:摘要文本与遮蔽统计。 */\nexport interface OmCompactionChatData {\n /** 压缩进行中(start 已到、checkpoint/end 未到):渲染为「正在压缩上下文…」提示行。 */\n readonly running: boolean;\n /** 压缩 pass(观察/反思;来自 start 事件载荷扩展,缺失或非运行态为 null)。 */\n readonly phase: 'observe' | 'reflect' | null;\n /** compaction/summary 的文本摘要;窗口裁剪把该事件留在窗外时为 null(卡片不可展开)。 */\n readonly summary: string | null;\n /** 已载入的 compaction/summary 事件 seq,窗外时为 null。 */\n readonly summaryEventSeq: number | null;\n /** 被遮蔽的表层节点数,summary 事件缺失或载荷非法时为 null。 */\n readonly shadowedItemCount: number | null;\n /** 遮蔽的 token 数,summary 事件缺失或载荷非法时为 null。 */\n readonly shadowedTokenCount: number | null;\n /** 压缩前的字符数,载荷缺失或非法时为 null。 */\n readonly shadowedCharCount: number | null;\n /** 压缩后的字符数(摘要文本长度),summary 不可用或非法时为 null。 */\n readonly summaryCharCount: number | null;\n /** 压缩后的估算 token 数(4 字符 ≈ 1 token),summary 不可用或非法时为 null。 */\n readonly summaryTokenCount: number | null;\n /** 摘要调用重试次数,载荷缺失或非法时为 null。 */\n readonly retryCount: number | null;\n}\n\n/** 压缩生命周期关联状态:summary / checkpoint / end 事件证据。 */\ninterface OmCompactionState {\n readonly summary?: ConversationMatch;\n readonly checkpoint?: ConversationMatch;\n /** compaction/end(成功与失败都会到达;无 checkpoint 的 end 表示压缩失败,撤回进行中提示行)。 */\n readonly end?: ConversationMatch;\n}\n\n/**\n * replace-surface 判定:surfaceOp 为 { op: 'replace' } 对象(与宿主 runtime 的\n * isReplacementSurfaceEvent 同义;宿主该助手位于浏览器 bundle,node 端测试无法求值,\n * 故本地实现同一形状检查)。\n */\nfunction isReplacementSurface(event: SessionEvent): boolean {\n const surfaceOp = (event as { surfaceOp?: unknown }).surfaceOp;\n return (\n typeof surfaceOp === 'object' &&\n surfaceOp !== null &&\n (surfaceOp as { op?: unknown }).op === 'replace'\n );\n}\n\n/**\n * 从压缩替换检查点(user/message + replace surface + source.plugin 为本插件)读取\n * 关联 compactionId;其余事件返回 undefined。\n */\nexport function checkpointCompactionId(event: SessionEvent): string | undefined {\n if (event.type !== 'user/message' || !isReplacementSurface(event)) return undefined;\n const data = event.data as unknown as {\n source?: { kind?: unknown; plugin?: unknown; compactionId?: unknown };\n };\n const source = data.source;\n if (\n source === undefined ||\n source.kind !== 'plugin' ||\n source.plugin !== PLUGIN_LABEL ||\n typeof source.compactionId !== 'string'\n ) {\n return undefined;\n }\n return source.compactionId;\n}\n\n/** 从 compaction/summary 载荷提取摘要文本与遮蔽统计(载荷非法时各字段回落 null)。 */\nfunction compactSummaryData(summaryMatch: ConversationMatch | undefined): OmCompactionChatData {\n let summary: string | null = null;\n let shadowedItemCount: number | null = null;\n let shadowedTokenCount: number | null = null;\n let shadowedCharCount: number | null = null;\n let retryCount: number | null = null;\n if (summaryMatch?.event.type === 'compaction/summary') {\n // 宿主载荷类型是 union 且不含插件扩展字段,读取处收窄为插件扩展类型\n const data = summaryMatch.event.data as CompactionSummaryPayload;\n if (Array.isArray(data.summary)) {\n const text = data.summary.map((block) => (block.type === 'text' ? block.text : '')).join('');\n summary = text.trim() === '' ? null : text;\n }\n shadowedItemCount =\n Array.isArray(data.shadowedSeqs) &&\n data.shadowedSeqs.every((seq) => Number.isSafeInteger(seq) && seq >= 0)\n ? data.shadowedSeqs.length\n : null;\n shadowedTokenCount =\n Number.isSafeInteger(data.shadowedTokenCount) && data.shadowedTokenCount >= 0\n ? data.shadowedTokenCount\n : null;\n shadowedCharCount =\n data.shadowedCharCount !== undefined &&\n Number.isSafeInteger(data.shadowedCharCount) &&\n data.shadowedCharCount >= 0\n ? data.shadowedCharCount\n : null;\n retryCount =\n data.attemptCount !== undefined &&\n Number.isSafeInteger(data.attemptCount) &&\n data.attemptCount >= 0\n ? data.attemptCount\n : null;\n }\n return {\n running: false,\n phase: null,\n summary,\n summaryEventSeq: summaryMatch?.event.seq ?? null,\n shadowedItemCount,\n shadowedTokenCount,\n shadowedCharCount,\n summaryCharCount: summary === null ? null : summary.length,\n summaryTokenCount: summary === null ? null : Math.ceil(summary.length / 4),\n retryCount,\n };\n}\n\n/** 压缩进行中提示行的载荷(统计未就绪,全部为 null)。 */\nfunction runningData(start: ConversationMatch | undefined): OmCompactionChatData {\n const data = start?.event.data as { phase?: unknown } | undefined;\n const phase = data?.phase === 'observe' || data?.phase === 'reflect' ? data.phase : null;\n return {\n running: true,\n phase,\n summary: null,\n summaryEventSeq: null,\n shadowedItemCount: null,\n shadowedTokenCount: null,\n shadowedCharCount: null,\n summaryCharCount: null,\n summaryTokenCount: null,\n retryCount: null,\n };\n}\n\n/** 窗口只载入部分事件时的回落证据扫描(summary / 检查点 / end 各自独立)。 */\nfunction fallbackState(context: ConversationNodeContext<OmCompactionState>): OmCompactionState {\n const summary = context.matches.find((match) => match.event.type === 'compaction/summary');\n const checkpoint = context.matches.find(\n (match) => checkpointCompactionId(match.event) !== undefined,\n );\n const end = context.matches.find((match) => match.event.type === 'compaction/end');\n return {\n ...(summary === undefined ? {} : { summary }),\n ...(checkpoint === undefined ? {} : { checkpoint }),\n ...(end === undefined ? {} : { end }),\n };\n}\n\n/** 构建最终 Chat 目标节点(与 ui-conversation 的 chatNode 助手同构)。 */\nfunction chatNode<Kind extends keyof ChatNodeDataMap & string>(\n context: ConversationNodeContext,\n kind: Kind,\n anchorSeq: number,\n data: ChatNodeDataMap[Kind],\n visibility: 'visible' | 'hidden' = 'visible',\n): ChatNode<Kind> {\n return {\n key: context.key,\n kind,\n id: context.id,\n target: 'chat',\n anchorSeq,\n location: context.start?.location ?? context.matches[0]?.location ?? { kind: 'unresolved' },\n visibility,\n data,\n };\n}\n\n/**\n * 插件压缩生命周期定义:compaction/start 开启上下文,summary / 替换检查点 / end\n * 作为 update 折叠证据。有检查点时产出摘要卡片;无检查点的 end 以 hidden 撤回\n * 进行中提示行(已物化节点以 hidden visibility 保留同一 key);仅 start 时渲染\n * 「正在压缩上下文…」提示行。\n */\nexport const omCompactionDefinition: ConversationNodeDefinition<OmCompactionState> = {\n kind: COMPACTION_CARD_KIND,\n target: 'chat',\n match: (event) => {\n const compactionId = checkpointCompactionId(event);\n if (compactionId !== undefined) return { id: compactionId, role: 'update' };\n if (\n event.type === 'compaction/start' ||\n event.type === 'compaction/summary' ||\n event.type === 'compaction/end'\n ) {\n const id: unknown = event.data.compactionId;\n if (typeof id !== 'string' || id === '') return null;\n return { id, role: event.type === 'compaction/start' ? 'start' : 'update' };\n }\n return null;\n },\n start: () => ({}),\n update: (context, match) => {\n if (match.event.type === 'compaction/summary') return { ...context.state, summary: match };\n if (match.event.type === 'compaction/end') return { ...context.state, end: match };\n if (checkpointCompactionId(match.event) !== undefined)\n return { ...context.state, checkpoint: match };\n return context.state;\n },\n buildViewNode: (context) => {\n const state = context.state ?? fallbackState(context);\n // 替换检查点已到达 → 压缩完成,渲染摘要卡片\n if (state.checkpoint !== undefined) {\n const data = compactSummaryData(state.summary);\n return chatNode(context, COMPACTION_CARD_KIND, state.checkpoint.event.seq, data);\n }\n // end 已到达但无检查点 → 压缩失败/中止:以 hidden visibility 撤回进行中提示行\n if (state.end !== undefined) {\n const anchorSeq = context.start?.event.seq ?? state.end.event.seq;\n return chatNode(\n context,\n COMPACTION_CARD_KIND,\n anchorSeq,\n runningData(context.start),\n 'hidden',\n );\n }\n // start 已到、checkpoint/end 未到 → 压缩进行中,渲染「正在压缩上下文…」提示行\n if (context.start !== undefined) {\n return chatNode(\n context,\n COMPACTION_CARD_KIND,\n context.start.event.seq,\n runningData(context.start),\n );\n }\n return null;\n },\n};\n","/**\n * `om-compaction` namespace 字典:插件压缩卡片的界面文案(zh/en)。\n * 导出 NS / zh / en;zh 为键集事实源,en 键集与 zh 一致。\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'om-compaction';\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n compaction: '上下文已压缩',\n 'compaction.completed': '已压缩 {items} 条历史记录(约 {tokens} tokens)',\n 'compaction.stats':\n '{items} 条 · {beforeChars} 字符({beforeTokens} tokens)→ {afterChars} 字符({afterTokens} tokens)',\n 'compaction.expand': '点击查看压缩摘要',\n 'compaction.unavailable': '压缩摘要不可用',\n 'compaction.running': '正在压缩上下文…',\n 'compaction.running.observe': '正在压缩上下文(观察)…',\n 'compaction.running.reflect': '正在压缩上下文(反思)…',\n 'compaction.retries': '重试 {count} 次',\n} as const;\n\n/** English dictionary(键集与 zh 一致)。 */\nexport const en: Record<keyof typeof zh, string> = {\n compaction: 'Context compacted',\n 'compaction.completed': 'Compacted {items} messages (~{tokens} tokens)',\n 'compaction.stats':\n '{items} msgs · {beforeChars} chars ({beforeTokens} tokens) → {afterChars} chars ({afterTokens} tokens)',\n 'compaction.expand': 'Click to view summary',\n 'compaction.unavailable': 'Summary unavailable',\n 'compaction.running': 'Compressing context…',\n 'compaction.running.observe': 'Compressing context (observation)…',\n 'compaction.running.reflect': 'Compressing context (reflection)…',\n 'compaction.retries': 'Retried {count} times',\n};\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Compaction card copy owned by dsh-plugin-om. */\n 'om-compaction': keyof typeof zh;\n }\n}\n","/**\n * 压缩卡片统计行的紧凑数字格式化。导出 formatCompactCount。\n * <1k 原样;≥1k 用 k;≥1w(万)用 w;≥1M 用 M;保留 1 位小数并去掉末尾 .0。\n */\nexport function formatCompactCount(value: number): string {\n const trim = (n: number): string => {\n const text = String(Math.round(n * 10) / 10);\n return text.endsWith('.0') ? text.slice(0, -2) : text;\n };\n if (value >= 1_000_000) return `${trim(value / 1_000_000)}M`;\n if (value >= 10_000) return `${trim(value / 10_000)}w`;\n if (value >= 1_000) return `${trim(value / 1_000)}k`;\n return String(value);\n}\n","/**\n * 压缩卡片渲染器:与宿主「上下文注入」卡片同款的折叠行。\n * 复用平台原语 DisclosureRow;行内统计与展开体样式按宿主 ContextInjectionRow 的规则\n * 注入 <style>(宿主该卡片样式是编译期哈希的 CSS Module,插件 bundle 无法引用其类名)。\n * 卡片报告模型从何处起不再看到那段历史;summary 留在加载窗口外时行不可展开。\n */\n\nimport type { ChatNode } from '@deepseek-ai/dsh-client-ui-conversation/client';\nimport { DisclosureRow, IconBrowseOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';\nimport type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';\nimport type { CSSProperties } from 'react';\nimport { memo, useState } from 'react';\nimport { formatCompactCount } from './format.ts';\n\n/** Keyed renderer props: 装配好的卡片节点 + 绑定 `om-compaction` 命名空间的 locale 座。 */\nexport interface OmCompactionCardProps {\n readonly node: ChatNode<'om-compaction'>;\n readonly t: TranslateNS<'om-compaction'>;\n}\n\n/** 卡片样式类名(与宿主 ContextInjectionRow.module.css 同款规则,见下方注册)。 */\nconst css = {\n root: 'om-compaction-root',\n chevron: 'om-compaction-chevron',\n sep: 'om-compaction-sep',\n summary: 'om-compaction-summary',\n body: 'om-compaction-body',\n} as const;\n\n/** 宿主 ContextInjectionRow.module.css 的规则(逐字一致,仅类名按插件前缀)。 */\nconst cardCss = `\n.${css.root}{min-width:0}\n.${css.root}[data-open]{padding-bottom:4px}\n.${css.chevron}{color:var(--dsw-alias-label-secondary)}\n.${css.sep}{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}\n.${css.summary}{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}\n.${css.body}{box-sizing:border-box;background:var(--dsw-alias-markdown-code-block);width:calc(100% - 22px);max-height:141px;color:var(--dsw-alias-label-tertiary);font:400 11px/16px var(--ds-font-family-code);border:none;border-radius:8px;margin:4px 0 0 22px;padding:10px 16px 12px 12px;overflow:auto}\n`;\n\n/** 注册卡片样式(无 document 的环境如 node 测试直接跳过)。 */\nif (\n typeof document !== 'undefined' &&\n document.querySelector('style[data-dsh-plugin-om-card]') === null\n) {\n const tag = document.createElement('style');\n tag.dataset.dshPluginOmCard = 'true';\n tag.textContent = cardCss;\n document.head.appendChild(tag);\n}\n\n/** 折叠式压缩卡片(统计标题行 + 代码块样式 summary 展开)。 */\nexport const OmCompactionCard = memo(function OmCompactionCard({ node, t }: OmCompactionCardProps) {\n const [expanded, setExpanded] = useState(false);\n const { data } = node;\n // 压缩进行中:不可展开的「正在压缩上下文…」提示行(按 phase 区分文案)\n if (data.running) {\n const runningLabel =\n data.phase === 'observe'\n ? t('compaction.running.observe')\n : data.phase === 'reflect'\n ? t('compaction.running.reflect')\n : t('compaction.running');\n return (\n <div style={styles.row}>\n <DisclosureRow\n className={css.root}\n icon={<IconBrowseOutline16 size={14} />}\n title={runningLabel}\n open={false}\n expandable={false}\n onToggle={() => {\n /* 进行中提示行不可展开 */\n }}\n />\n </div>\n );\n }\n const expandable = data.summary !== null;\n const open = expandable && expanded;\n // 统计行:优先完整压缩前后统计,载荷字段不全时逐级回落;重试次数仅在有重试时追加\n const stats =\n data.shadowedItemCount !== null &&\n data.shadowedCharCount !== null &&\n data.shadowedTokenCount !== null &&\n data.summaryCharCount !== null &&\n data.summaryTokenCount !== null\n ? t('compaction.stats', {\n items: formatCompactCount(data.shadowedItemCount),\n beforeChars: formatCompactCount(data.shadowedCharCount),\n beforeTokens: formatCompactCount(data.shadowedTokenCount),\n afterChars: formatCompactCount(data.summaryCharCount),\n afterTokens: formatCompactCount(data.summaryTokenCount),\n })\n : data.shadowedItemCount !== null && data.shadowedTokenCount !== null\n ? t('compaction.completed', {\n items: formatCompactCount(data.shadowedItemCount),\n tokens: formatCompactCount(data.shadowedTokenCount),\n })\n : expandable\n ? t('compaction.expand')\n : t('compaction.unavailable');\n const retry =\n data.retryCount !== null && data.retryCount > 0\n ? ` \\u00b7 ${t('compaction.retries', { count: data.retryCount })}`\n : '';\n return (\n <div style={styles.row}>\n <DisclosureRow\n className={css.root}\n icon={<IconBrowseOutline16 size={14} />}\n chevronClassName={css.chevron}\n title={t('compaction')}\n collapsedContent={\n <>\n <span className={css.sep} aria-hidden />\n <span className={css.summary} data-om-compaction-summary>\n {stats}\n {retry}\n </span>\n </>\n }\n keepContentWhenOpen\n expandable={expandable}\n expandOnRowClick\n open={open}\n onToggle={() => {\n setExpanded((value) => !value);\n }}\n >\n {data.summary !== null && (\n <div className={css.body} style={styles.body}>\n {data.summary}\n </div>\n )}\n </DisclosureRow>\n </div>\n );\n});\n\nconst styles: Record<string, CSSProperties> = {\n row: {},\n /** 摘要为 <history> 文本,保留换行。 */\n body: { whiteSpace: 'pre-wrap' },\n};\n","/**\n * 浏览器客户端入口(exports[\"./client\"] → dist/client.js):注册压缩卡片的\n * locale 字典、conversation 业务定义与 keyed 渲染器。宿主 conversation UI 只识别\n * 内置 'compact' 检查点,插件自产的压缩检查点(source.plugin = 'dsh-plugin-om')\n * 由本 bundle 认领并在消息列表渲染「已压缩」卡片。\n */\nimport type { Context } from '@deepseek-ai/cordis';\n// Type-only: 拉取 locale 插件的 Context 合并(ctx.locale)。\nimport type {} from '@deepseek-ai/dsh-client-locale/client';\n// Type-only: 拉取 runtime 的 Context 合并(ctx.conversationEvents)。\nimport type {} from '@deepseek-ai/dsh-client-runtime/client';\n// Type-only: 'conversation.chat.node' 的 SlotMap 行与 ChatNodeDataMap 声明合并。\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client';\nimport { omCompactionDefinition } from './definition.ts';\nimport { en, NS, zh } from './locales.ts';\nimport { OmCompactionCard } from './OmCompactionCard.tsx';\n\n/** Required services: 聊天节点槽、会话事件注册表与 locale 服务。 */\nexport const inject = ['slots', 'conversationEvents', 'locale'];\n\n/** Client plugin body: 注册压缩卡片的业务定义与 keyed 渲染器(随插件卸载移除)。 */\nexport function apply(ctx: Context): void {\n ctx.effect(\n () => ctx.locale.register(NS, { zh, en }),\n 'dsh-plugin-om: compaction card dictionaries',\n );\n ctx.effect(\n () => ctx.conversationEvents.register(omCompactionDefinition),\n 'dsh-plugin-om: compaction definition',\n );\n ctx.slots.inject('conversation.chat.node', () =>\n ctx.slots.register(\n { name: 'conversation.chat.node', key: 'om-compaction', locale: NS },\n OmCompactionCard,\n ),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["memo","useState","DisclosureRow","IconBrowseOutline16"],"sources":["../src/constants.ts","../src/client/definition.ts","../src/client/locales.ts","../src/client/format.ts","../src/client/OmCompactionCard.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * 共享常量:插件级魔法字符串,集中定义避免散落各模块。\n * 导出 PLUGIN_LABEL / HISTORY_TAG / HISTORY_TIP / COMPLETE_MESSAGE_DEFINITION /\n * COMPACT_CHECKPOINT_PLUGIN / COMPACTION_ABORTED_ERROR / isPluginOwnedSource。\n */\n\n/** 插件标识:压缩消息 source.plugin 取值与日志前缀。 */\nexport const PLUGIN_LABEL = 'dsh-plugin-om';\n\n/** 压缩日志标签名:<history>...</history> 包裹观察/反思日志块。 */\nexport const HISTORY_TAG = 'history';\n\n/** 压缩日志块开标签的 tip 属性(对 AI 的提醒:本块是历史压缩产物,不要复述)。 */\nexport const HISTORY_TIP = '当前块是历史消息的压缩产物,不要复述';\n\n/**\n * 「完整消息」定义(提示词 / 工具描述 / 块顶注释共用)。\n * 完整消息是摘要日志与 recall 共用的定位单位;首条 index 为 0,按会话顺序递增、全局稳定。\n */\nexport const COMPLETE_MESSAGE_DEFINITION =\n '`完整消息`被定义为`用户消息`,`系统消息`,`模型输出文本`和`具有result的toolcall`的集合。首条`完整消息`的index是0,后续的index递增。';\n\n/** 旧日志压缩消息的宿主 checkpoint 标记 plugin 名(历史兼容识别用)。 */\nexport const COMPACT_CHECKPOINT_PLUGIN = 'compact';\n\n/**\n * 压缩因 signal 中止而放弃时 compaction/end 的 error 标识(服务端写入、客户端过滤):\n * 中止不是失败,客户端据此隐藏失败行(宿主不变量要求无 summary 的 end 必须带 error)。\n */\nexport const COMPACTION_ABORTED_ERROR = '压缩已中止(signal aborted)';\n\n/** 判定 user/message 的 source 是否为本插件自产(压缩日志消息;含旧宿主 checkpoint 标记兼容)。这类消息不占完整消息 index。 */\nexport function isPluginOwnedSource(\n source: { kind?: string; plugin?: string } | undefined,\n): boolean {\n if (source?.kind !== 'plugin') return false;\n return source.plugin === PLUGIN_LABEL || source.plugin === COMPACT_CHECKPOINT_PLUGIN;\n}\n","/**\n * 压缩卡片业务定义:认领插件自产的压缩生命周期事件(compaction/start|summary|end)\n * 与替换检查点(source.plugin = 'dsh-plugin-om'),聚合出可渲染的摘要卡片节点。\n * 导出 COMPACTION_CARD_KIND / OmCompactionChatData / checkpointCompactionId /\n * omCompactionDefinition。宿主的 'compact' 检查点由宿主自己渲染,本定义不认领\n * (双方共存无视觉冲突:宿主无检查点证据时不产出节点)。\n */\n\nimport type {\n ConversationMatch,\n ConversationNodeContext,\n ConversationNodeDefinition,\n} from '@deepseek-ai/dsh-client-runtime/client';\nimport type { ChatNode, ChatNodeDataMap } from '@deepseek-ai/dsh-client-ui-conversation/client';\nimport type {} from '@deepseek-ai/dsh-compaction/types';\nimport type { SessionEvent } from '@deepseek-ai/dsh-session/types';\nimport { COMPACTION_ABORTED_ERROR, PLUGIN_LABEL } from '../constants.ts';\nimport type { CompactionSummaryPayload } from '../types.ts';\n\n/** 压缩卡片渲染器分发键(合并进 ChatNodeDataMap)。 */\nexport const COMPACTION_CARD_KIND = 'om-compaction';\n\ndeclare module '@deepseek-ai/dsh-client-ui-conversation/client' {\n interface ChatNodeDataMap {\n /** dsh-plugin-om 压缩替换检查点渲染的摘要卡片数据。 */\n 'om-compaction': OmCompactionChatData;\n }\n}\n\n/** 压缩卡片载荷:摘要文本与遮蔽统计。 */\nexport interface OmCompactionChatData {\n /** 压缩进行中(start 已到、checkpoint/end 未到):渲染为「正在压缩上下文…」提示行。 */\n readonly running: boolean;\n /** 压缩 pass(观察/反思;来自 start 事件载荷扩展,缺失或非运行态为 null)。 */\n readonly phase: 'observe' | 'reflect' | null;\n /** compaction/summary 的文本摘要;窗口裁剪把该事件留在窗外时为 null(卡片不可展开)。 */\n readonly summary: string | null;\n /** 已载入的 compaction/summary 事件 seq,窗外时为 null。 */\n readonly summaryEventSeq: number | null;\n /** 被遮蔽的表层节点数,summary 事件缺失或载荷非法时为 null。 */\n readonly shadowedItemCount: number | null;\n /** 遮蔽的 token 数,summary 事件缺失或载荷非法时为 null。 */\n readonly shadowedTokenCount: number | null;\n /** 压缩前的字符数,载荷缺失或非法时为 null。 */\n readonly shadowedCharCount: number | null;\n /** 压缩后的字符数(摘要文本长度),summary 不可用或非法时为 null。 */\n readonly summaryCharCount: number | null;\n /** 压缩后的估算 token 数(4 字符 ≈ 1 token),summary 不可用或非法时为 null。 */\n readonly summaryTokenCount: number | null;\n /** 摘要调用重试次数,载荷缺失或非法时为 null。 */\n readonly retryCount: number | null;\n /** 压缩失败的错误文案(来自 compaction/end 载荷 error);非失败节点为 null。 */\n readonly error: string | null;\n}\n\n/** 压缩生命周期关联状态:summary / checkpoint / end 事件证据。 */\ninterface OmCompactionState {\n readonly summary?: ConversationMatch;\n readonly checkpoint?: ConversationMatch;\n /** compaction/end(成功与失败都会到达;无 checkpoint 的 end 表示压缩失败或中止)。 */\n readonly end?: ConversationMatch;\n}\n\n/**\n * replace-surface 判定:surfaceOp 为 { op: 'replace' } 对象(与宿主 runtime 的\n * isReplacementSurfaceEvent 同义;宿主该助手位于浏览器 bundle,node 端测试无法求值,\n * 故本地实现同一形状检查)。\n */\nfunction isReplacementSurface(event: SessionEvent): boolean {\n const surfaceOp = (event as { surfaceOp?: unknown }).surfaceOp;\n return (\n typeof surfaceOp === 'object' &&\n surfaceOp !== null &&\n (surfaceOp as { op?: unknown }).op === 'replace'\n );\n}\n\n/**\n * 从压缩替换检查点(user/message + replace surface + source.plugin 为本插件)读取\n * 关联 compactionId;其余事件返回 undefined。\n */\nexport function checkpointCompactionId(event: SessionEvent): string | undefined {\n if (event.type !== 'user/message' || !isReplacementSurface(event)) return undefined;\n const data = event.data as unknown as {\n source?: { kind?: unknown; plugin?: unknown; compactionId?: unknown };\n };\n const source = data.source;\n if (\n source === undefined ||\n source.kind !== 'plugin' ||\n source.plugin !== PLUGIN_LABEL ||\n typeof source.compactionId !== 'string'\n ) {\n return undefined;\n }\n return source.compactionId;\n}\n\n/** 从 compaction/summary 载荷提取摘要文本与遮蔽统计(载荷非法时各字段回落 null)。 */\nfunction compactSummaryData(summaryMatch: ConversationMatch | undefined): OmCompactionChatData {\n let summary: string | null = null;\n let shadowedItemCount: number | null = null;\n let shadowedTokenCount: number | null = null;\n let shadowedCharCount: number | null = null;\n let retryCount: number | null = null;\n if (summaryMatch?.event.type === 'compaction/summary') {\n // 宿主载荷类型是 union 且不含插件扩展字段,读取处收窄为插件扩展类型\n const data = summaryMatch.event.data as CompactionSummaryPayload;\n if (Array.isArray(data.summary)) {\n const text = data.summary.map((block) => (block.type === 'text' ? block.text : '')).join('');\n summary = text.trim() === '' ? null : text;\n }\n shadowedItemCount =\n Array.isArray(data.shadowedSeqs) &&\n data.shadowedSeqs.every((seq) => Number.isSafeInteger(seq) && seq >= 0)\n ? data.shadowedSeqs.length\n : null;\n shadowedTokenCount =\n Number.isSafeInteger(data.shadowedTokenCount) && data.shadowedTokenCount >= 0\n ? data.shadowedTokenCount\n : null;\n shadowedCharCount =\n data.shadowedCharCount !== undefined &&\n Number.isSafeInteger(data.shadowedCharCount) &&\n data.shadowedCharCount >= 0\n ? data.shadowedCharCount\n : null;\n retryCount =\n data.attemptCount !== undefined &&\n Number.isSafeInteger(data.attemptCount) &&\n data.attemptCount >= 0\n ? data.attemptCount\n : null;\n }\n return {\n running: false,\n phase: null,\n error: null,\n summary,\n summaryEventSeq: summaryMatch?.event.seq ?? null,\n shadowedItemCount,\n shadowedTokenCount,\n shadowedCharCount,\n summaryCharCount: summary === null ? null : summary.length,\n summaryTokenCount: summary === null ? null : Math.ceil(summary.length / 4),\n retryCount,\n };\n}\n\n/** 从 compaction/start 载荷读取压缩 pass(observe/reflect;缺失或非法时回落 null)。 */\nfunction startPhase(start: ConversationMatch | undefined): 'observe' | 'reflect' | null {\n const data = start?.event.data as { phase?: unknown } | undefined;\n return data?.phase === 'observe' || data?.phase === 'reflect' ? data.phase : null;\n}\n\n/** 压缩进行中提示行的载荷(统计未就绪,全部为 null)。 */\nfunction runningData(start: ConversationMatch | undefined): OmCompactionChatData {\n return {\n running: true,\n phase: startPhase(start),\n error: null,\n summary: null,\n summaryEventSeq: null,\n shadowedItemCount: null,\n shadowedTokenCount: null,\n shadowedCharCount: null,\n summaryCharCount: null,\n summaryTokenCount: null,\n retryCount: null,\n };\n}\n\n/**\n * 压缩失败节点的载荷:end 已到且带非中止 error、无检查点时使用。\n * error 取 compaction/end 载荷中的实际报错文案(非字符串或为空时回落 null);\n * 摘要与统计字段全部为 null。\n */\nfunction failureData(\n start: ConversationMatch | undefined,\n end: ConversationMatch,\n): OmCompactionChatData {\n const data = end.event.data as { error?: unknown } | undefined;\n return {\n running: false,\n phase: startPhase(start),\n error: typeof data?.error === 'string' && data.error !== '' ? data.error : null,\n summary: null,\n summaryEventSeq: null,\n shadowedItemCount: null,\n shadowedTokenCount: null,\n shadowedCharCount: null,\n summaryCharCount: null,\n summaryTokenCount: null,\n retryCount: null,\n };\n}\n\n/** 窗口只载入部分事件时的回落证据扫描(summary / 检查点 / end 各自独立)。 */\nfunction fallbackState(context: ConversationNodeContext<OmCompactionState>): OmCompactionState {\n const summary = context.matches.find((match) => match.event.type === 'compaction/summary');\n const checkpoint = context.matches.find(\n (match) => checkpointCompactionId(match.event) !== undefined,\n );\n const end = context.matches.find((match) => match.event.type === 'compaction/end');\n return {\n ...(summary === undefined ? {} : { summary }),\n ...(checkpoint === undefined ? {} : { checkpoint }),\n ...(end === undefined ? {} : { end }),\n };\n}\n\n/** 构建最终 Chat 目标节点(与 ui-conversation 的 chatNode 助手同构)。 */\nfunction chatNode<Kind extends keyof ChatNodeDataMap & string>(\n context: ConversationNodeContext,\n kind: Kind,\n anchorSeq: number,\n data: ChatNodeDataMap[Kind],\n visibility: 'visible' | 'hidden' = 'visible',\n): ChatNode<Kind> {\n return {\n key: context.key,\n kind,\n id: context.id,\n target: 'chat',\n anchorSeq,\n location: context.start?.location ?? context.matches[0]?.location ?? { kind: 'unresolved' },\n visibility,\n data,\n };\n}\n\n/**\n * 插件压缩生命周期定义:compaction/start 开启上下文,summary / 替换检查点 / end\n * 作为 update 折叠证据。有检查点时产出摘要卡片;end 已到且无检查点时,error 为\n * 非中止报错则产出可见失败行,error 为中止标识或缺失则以 hidden 撤回进行中提示行\n * (已物化节点以 hidden visibility 保留同一 key);仅 start 时渲染「正在压缩上下文…」\n * 提示行。\n */\nexport const omCompactionDefinition: ConversationNodeDefinition<OmCompactionState> = {\n kind: COMPACTION_CARD_KIND,\n target: 'chat',\n match: (event) => {\n const compactionId = checkpointCompactionId(event);\n if (compactionId !== undefined) return { id: compactionId, role: 'update' };\n if (\n event.type === 'compaction/start' ||\n event.type === 'compaction/summary' ||\n event.type === 'compaction/end'\n ) {\n const id: unknown = event.data.compactionId;\n if (typeof id !== 'string' || id === '') return null;\n return { id, role: event.type === 'compaction/start' ? 'start' : 'update' };\n }\n return null;\n },\n start: () => ({}),\n update: (context, match) => {\n if (match.event.type === 'compaction/summary') return { ...context.state, summary: match };\n if (match.event.type === 'compaction/end') return { ...context.state, end: match };\n if (checkpointCompactionId(match.event) !== undefined)\n return { ...context.state, checkpoint: match };\n return context.state;\n },\n buildViewNode: (context) => {\n const state = context.state ?? fallbackState(context);\n // 替换检查点已到达 → 压缩完成,渲染摘要卡片\n if (state.checkpoint !== undefined) {\n const data = compactSummaryData(state.summary);\n return chatNode(context, COMPACTION_CARD_KIND, state.checkpoint.event.seq, data);\n }\n // end 已到达但无检查点:error 为非中止的实际报错时渲染可见失败行;\n // 无 error 或 error 以 COMPACTION_ABORTED_ERROR 开头(用户中止)时,\n // 以 hidden visibility 撤回进行中提示行\n if (state.end !== undefined) {\n const anchorSeq = context.start?.event.seq ?? state.end.event.seq;\n const endData = state.end.event.data as { error?: unknown } | undefined;\n const isFailure =\n typeof endData?.error === 'string' &&\n endData.error !== '' &&\n !endData.error.startsWith(COMPACTION_ABORTED_ERROR);\n if (isFailure) {\n return chatNode(\n context,\n COMPACTION_CARD_KIND,\n anchorSeq,\n failureData(context.start, state.end),\n );\n }\n return chatNode(\n context,\n COMPACTION_CARD_KIND,\n anchorSeq,\n runningData(context.start),\n 'hidden',\n );\n }\n // start 已到、checkpoint/end 未到 → 压缩进行中,渲染「正在压缩上下文…」提示行\n if (context.start !== undefined) {\n return chatNode(\n context,\n COMPACTION_CARD_KIND,\n context.start.event.seq,\n runningData(context.start),\n );\n }\n return null;\n },\n};\n","/**\n * `om-compaction` namespace 字典:插件压缩卡片的界面文案(zh/en)。\n * 导出 NS / zh / en;zh 为键集事实源,en 键集与 zh 一致。\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'om-compaction';\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n compaction: '上下文已压缩',\n 'compaction.completed': '已压缩 {items} 条历史记录(约 {tokens} tokens)',\n 'compaction.stats':\n '{items} 条 · {beforeChars} 字符({beforeTokens} tokens)→ {afterChars} 字符({afterTokens} tokens)',\n 'compaction.expand': '点击查看压缩摘要',\n 'compaction.unavailable': '压缩摘要不可用',\n 'compaction.running': '正在压缩上下文…',\n 'compaction.running.observe': '正在压缩上下文(观察)…',\n 'compaction.running.reflect': '正在压缩上下文(反思)…',\n 'compaction.failed': '上下文压缩失败',\n 'compaction.failed.observe': '上下文压缩失败(观察)',\n 'compaction.failed.reflect': '上下文压缩失败(反思)',\n 'compaction.retries': '重试 {count} 次',\n} as const;\n\n/** English dictionary(键集与 zh 一致)。 */\nexport const en: Record<keyof typeof zh, string> = {\n compaction: 'Context compacted',\n 'compaction.completed': 'Compacted {items} messages (~{tokens} tokens)',\n 'compaction.stats':\n '{items} msgs · {beforeChars} chars ({beforeTokens} tokens) → {afterChars} chars ({afterTokens} tokens)',\n 'compaction.expand': 'Click to view summary',\n 'compaction.unavailable': 'Summary unavailable',\n 'compaction.running': 'Compressing context…',\n 'compaction.running.observe': 'Compressing context (observation)…',\n 'compaction.running.reflect': 'Compressing context (reflection)…',\n 'compaction.failed': 'Context compaction failed',\n 'compaction.failed.observe': 'Context compaction failed (observation)',\n 'compaction.failed.reflect': 'Context compaction failed (reflection)',\n 'compaction.retries': 'Retried {count} times',\n};\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Compaction card copy owned by dsh-plugin-om. */\n 'om-compaction': keyof typeof zh;\n }\n}\n","/**\n * 压缩卡片统计行的紧凑数字格式化。导出 formatCompactCount。\n * <1k 原样;≥1k 用 k;≥1w(万)用 w;≥1M 用 M;保留 1 位小数并去掉末尾 .0。\n */\nexport function formatCompactCount(value: number): string {\n const trim = (n: number): string => {\n const text = String(Math.round(n * 10) / 10);\n return text.endsWith('.0') ? text.slice(0, -2) : text;\n };\n if (value >= 1_000_000) return `${trim(value / 1_000_000)}M`;\n if (value >= 10_000) return `${trim(value / 10_000)}w`;\n if (value >= 1_000) return `${trim(value / 1_000)}k`;\n return String(value);\n}\n","/**\n * 压缩卡片渲染器:与宿主「上下文注入」卡片同款的折叠行。\n * 复用平台原语 DisclosureRow;行内统计与展开体样式按宿主 ContextInjectionRow 的规则\n * 注入 <style>(宿主该卡片样式是编译期哈希的 CSS Module,插件 bundle 无法引用其类名)。\n * 卡片报告模型从何处起不再看到那段历史;summary 留在加载窗口外时行不可展开。\n * 压缩失败节点渲染为可展开的错误行:折叠行显示错误摘要,展开体显示完整报错。\n */\n\nimport type { ChatNode } from '@deepseek-ai/dsh-client-ui-conversation/client';\nimport { DisclosureRow, IconBrowseOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';\nimport type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';\nimport type { CSSProperties } from 'react';\nimport { memo, useState } from 'react';\nimport { formatCompactCount } from './format.ts';\n\n/** Keyed renderer props: 装配好的卡片节点 + 绑定 `om-compaction` 命名空间的 locale 座。 */\nexport interface OmCompactionCardProps {\n readonly node: ChatNode<'om-compaction'>;\n readonly t: TranslateNS<'om-compaction'>;\n}\n\n/** 卡片样式类名(与宿主 ContextInjectionRow.module.css 同款规则,见下方注册)。 */\nconst css = {\n root: 'om-compaction-root',\n chevron: 'om-compaction-chevron',\n sep: 'om-compaction-sep',\n summary: 'om-compaction-summary',\n body: 'om-compaction-body',\n} as const;\n\n/** 宿主 ContextInjectionRow.module.css 的规则(逐字一致,仅类名按插件前缀)。 */\nconst cardCss = `\n.${css.root}{min-width:0}\n.${css.root}[data-open]{padding-bottom:4px}\n.${css.chevron}{color:var(--dsw-alias-label-secondary)}\n.${css.sep}{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}\n.${css.summary}{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}\n.${css.body}{box-sizing:border-box;background:var(--dsw-alias-markdown-code-block);width:calc(100% - 22px);max-height:141px;color:var(--dsw-alias-label-tertiary);font:400 11px/16px var(--ds-font-family-code);border:none;border-radius:8px;margin:4px 0 0 22px;padding:10px 16px 12px 12px;overflow:auto}\n`;\n\n/** 注册卡片样式(无 document 的环境如 node 测试直接跳过)。 */\nif (\n typeof document !== 'undefined' &&\n document.querySelector('style[data-dsh-plugin-om-card]') === null\n) {\n const tag = document.createElement('style');\n tag.dataset.dshPluginOmCard = 'true';\n tag.textContent = cardCss;\n document.head.appendChild(tag);\n}\n\n/** 折叠式压缩卡片(统计标题行 + 代码块样式 summary 展开)。 */\nexport const OmCompactionCard = memo(function OmCompactionCard({ node, t }: OmCompactionCardProps) {\n const [expanded, setExpanded] = useState(false);\n const { data } = node;\n // 压缩进行中:不可展开的「正在压缩上下文…」提示行(按 phase 区分文案)\n if (data.running) {\n const runningLabel =\n data.phase === 'observe'\n ? t('compaction.running.observe')\n : data.phase === 'reflect'\n ? t('compaction.running.reflect')\n : t('compaction.running');\n return (\n <div style={styles.row}>\n <DisclosureRow\n className={css.root}\n icon={<IconBrowseOutline16 size={14} />}\n title={runningLabel}\n open={false}\n expandable={false}\n onToggle={() => {\n /* 进行中提示行不可展开 */\n }}\n />\n </div>\n );\n }\n // 压缩失败:可展开的错误行(按 phase 区分文案),折叠行为错误摘要、展开体为完整报错\n if (data.error !== null) {\n const failedLabel =\n data.phase === 'observe'\n ? t('compaction.failed.observe')\n : data.phase === 'reflect'\n ? t('compaction.failed.reflect')\n : t('compaction.failed');\n const errorSummary = data.error.length > 80 ? `${data.error.slice(0, 80)}…` : data.error;\n return (\n <div style={styles.row}>\n <DisclosureRow\n className={css.root}\n icon={<IconBrowseOutline16 size={14} />}\n chevronClassName={css.chevron}\n title={failedLabel}\n collapsedContent={\n <>\n <span className={css.sep} aria-hidden />\n <span className={css.summary}>{errorSummary}</span>\n </>\n }\n keepContentWhenOpen\n expandable\n expandOnRowClick\n open={expanded}\n onToggle={() => {\n setExpanded((value) => !value);\n }}\n >\n <div className={css.body} style={styles.body}>\n {data.error}\n </div>\n </DisclosureRow>\n </div>\n );\n }\n const expandable = data.summary !== null;\n const open = expandable && expanded;\n // 统计行:优先完整压缩前后统计,载荷字段不全时逐级回落;重试次数仅在有重试时追加\n const stats =\n data.shadowedItemCount !== null &&\n data.shadowedCharCount !== null &&\n data.shadowedTokenCount !== null &&\n data.summaryCharCount !== null &&\n data.summaryTokenCount !== null\n ? t('compaction.stats', {\n items: formatCompactCount(data.shadowedItemCount),\n beforeChars: formatCompactCount(data.shadowedCharCount),\n beforeTokens: formatCompactCount(data.shadowedTokenCount),\n afterChars: formatCompactCount(data.summaryCharCount),\n afterTokens: formatCompactCount(data.summaryTokenCount),\n })\n : data.shadowedItemCount !== null && data.shadowedTokenCount !== null\n ? t('compaction.completed', {\n items: formatCompactCount(data.shadowedItemCount),\n tokens: formatCompactCount(data.shadowedTokenCount),\n })\n : expandable\n ? t('compaction.expand')\n : t('compaction.unavailable');\n const retry =\n data.retryCount !== null && data.retryCount > 0\n ? ` \\u00b7 ${t('compaction.retries', { count: data.retryCount })}`\n : '';\n return (\n <div style={styles.row}>\n <DisclosureRow\n className={css.root}\n icon={<IconBrowseOutline16 size={14} />}\n chevronClassName={css.chevron}\n title={t('compaction')}\n collapsedContent={\n <>\n <span className={css.sep} aria-hidden />\n <span className={css.summary} data-om-compaction-summary>\n {stats}\n {retry}\n </span>\n </>\n }\n keepContentWhenOpen\n expandable={expandable}\n expandOnRowClick\n open={open}\n onToggle={() => {\n setExpanded((value) => !value);\n }}\n >\n {data.summary !== null && (\n <div className={css.body} style={styles.body}>\n {data.summary}\n </div>\n )}\n </DisclosureRow>\n </div>\n );\n});\n\nconst styles: Record<string, CSSProperties> = {\n row: {},\n /** 摘要与失败报错为多行文本,保留换行。 */\n body: { whiteSpace: 'pre-wrap' },\n};\n","/**\n * 浏览器客户端入口(exports[\"./client\"] → dist/client.js):注册压缩卡片的\n * locale 字典、conversation 业务定义与 keyed 渲染器。宿主 conversation UI 只识别\n * 内置 'compact' 检查点,插件自产的压缩检查点(source.plugin = 'dsh-plugin-om')\n * 由本 bundle 认领并在消息列表渲染「已压缩」卡片。\n */\nimport type { Context } from '@deepseek-ai/cordis';\n// Type-only: 拉取 locale 插件的 Context 合并(ctx.locale)。\nimport type {} from '@deepseek-ai/dsh-client-locale/client';\n// Type-only: 拉取 runtime 的 Context 合并(ctx.conversationEvents)。\nimport type {} from '@deepseek-ai/dsh-client-runtime/client';\n// Type-only: 'conversation.chat.node' 的 SlotMap 行与 ChatNodeDataMap 声明合并。\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client';\nimport { omCompactionDefinition } from './definition.ts';\nimport { en, NS, zh } from './locales.ts';\nimport { OmCompactionCard } from './OmCompactionCard.tsx';\n\n/** Required services: 聊天节点槽、会话事件注册表与 locale 服务。 */\nexport const inject = ['slots', 'conversationEvents', 'locale'];\n\n/** Client plugin body: 注册压缩卡片的业务定义与 keyed 渲染器(随插件卸载移除)。 */\nexport function apply(ctx: Context): void {\n ctx.effect(\n () => ctx.locale.register(NS, { zh, en }),\n 'dsh-plugin-om: compaction card dictionaries',\n );\n ctx.effect(\n () => ctx.conversationEvents.register(omCompactionDefinition),\n 'dsh-plugin-om: compaction definition',\n );\n ctx.slots.inject('conversation.chat.node', () =>\n ctx.slots.register(\n { name: 'conversation.chat.node', key: 'om-compaction', locale: NS },\n OmCompactionCard,\n ),\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,13 @@
1
1
  import type { Agent, Context, PluginConfig, Session } from './types.ts';
2
2
  import { type RoutedTarget } from './utils.ts';
3
+ /** 压缩 pass 结果:failed=false 表示无需中断(成功、跳过或非摘要耗尽的局部失败);failed=true 携带最后一次尝试的实际报错与是否因 signal 中止。 */
4
+ export type CompressPassResult = {
5
+ failed: false;
6
+ } | {
7
+ failed: true;
8
+ error: string;
9
+ aborted: boolean;
10
+ };
3
11
  /** 历史文本 token 估算:4 字符 ≈ 1 token(与宿主 dsh-token-meter 启发式一致)。 */
4
12
  export declare function estimateTextTokens(text: string): number;
5
13
  /**
@@ -32,15 +40,21 @@ export declare function historySection(session: Session): {
32
40
  };
33
41
  /**
34
42
  * 反思:全部 <history> 块 token 合计 ≥ reflectThresholdTokens 时,摘要调用把整个块
35
- * 区段合并替换为一条更紧凑的摘要。失败不产生部分替换。
43
+ * 区段合并替换为一条更紧凑的摘要。失败不产生部分替换;摘要尝试全部耗尽返回
44
+ * 失败结果(error = 最后一次尝试的实际报错/具体问题)。
45
+ */
46
+ export declare function reflectPass(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, target: RoutedTarget, signal?: AbortSignal): Promise<CompressPassResult>;
47
+ /**
48
+ * 观察:净压力 tokens(上下文压力 − 已压缩 <history> 块 token 合计 − 系统提示词
49
+ * token 估算 − 工具定义 token 估算)≥ observeThresholdTokens 时,摘要调用把未压缩
50
+ * 消息压缩为观察日志,追加到旧摘要并替换被压缩消息区间。失败不产生部分替换;
51
+ * 摘要尝试全部耗尽返回失败结果(error = 最后一次尝试的实际报错/具体问题)。
36
52
  */
37
- export declare function reflectPass(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, target: RoutedTarget, signal?: AbortSignal): Promise<void>;
53
+ export declare function observePass(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, tailCount: number, target: RoutedTarget, signal?: AbortSignal): Promise<CompressPassResult>;
38
54
  /**
39
- * 观察:净压力 tokens(上下文压力 已压缩 <history> 块 token 合计)≥
40
- * observeThresholdTokens 时,摘要调用把未压缩消息压缩为观察日志,追加到旧摘要并
41
- * 替换被压缩消息区间。失败不产生部分替换。
55
+ * 压力检查 + 两级压缩入口:先反思后观察,pre-step 阻塞串行执行;仅主会话生效。
56
+ * 反思摘要耗尽失败时直接返回失败结果(观察无需继续,本轮将被拒绝);观察失败同样
57
+ * 传播失败结果,由 pre-step 拒绝本 step 中断当前 turn。
42
58
  */
43
- export declare function observePass(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, tailCount: number, target: RoutedTarget, signal?: AbortSignal): Promise<void>;
44
- /** 压力检查 + 两级压缩入口:先反思后观察,pre-step 阻塞串行执行;仅主会话生效。 */
45
- export declare function maybeCompress(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, signal?: AbortSignal): Promise<void>;
59
+ export declare function maybeCompress(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, signal?: AbortSignal): Promise<CompressPassResult>;
46
60
  //# sourceMappingURL=compress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EACV,KAAK,EAIL,OAAO,EACP,YAAY,EACZ,OAAO,EAIR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAgB,KAAK,YAAY,EAAqC,MAAM,YAAY,CAAC;AAEhG,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AA4CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAY1E;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,CAqBpE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG;IAChD,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,CAUA;AAyGD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA8Gf;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA2If;AAED,mDAAmD;AACnD,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,CAmBf"}
1
+ {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,KAAK,EAIL,OAAO,EACP,YAAY,EACZ,OAAO,EAIR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAgB,KAAK,YAAY,EAAqC,MAAM,YAAY,CAAC;AAEhG,gGAAgG;AAChG,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtD,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AA4CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAY1E;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,CAqBpE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG;IAChD,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,CAUA;AA0GD;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,kBAAkB,CAAC,CAiH7B;AAyCD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,kBAAkB,CAAC,CAgJ7B;AAED;;;;GAIG;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,kBAAkB,CAAC,CAwB7B"}
package/dist/config.d.ts CHANGED
@@ -4,8 +4,8 @@ export type PluginConfig = {
4
4
  observeThresholdTokens: number;
5
5
  /** 反思阈值(tokens):全部 <history> 块 token 合计 ≥ 该值时触发精简合并。 */
6
6
  reflectThresholdTokens: number;
7
- /** 单次摘要生成上限(LLM maxTokens)。 */
8
- compressMaxTokens: number;
7
+ /** 单次摘要生成上限(LLM maxTokens);undefined 表示不设置,由模型适配器默认值决定。 */
8
+ compressMaxTokens: number | undefined;
9
9
  /** 遇 429 限流后下一次摘要请求前的等待毫秒数(全局冷却期)。 */
10
10
  rateLimitWaitMs: number;
11
11
  /** 尾部保留的不压缩消息条数。 */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG;IACzB,oCAAoC;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wDAAwD;IACxD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,+BAA+B;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,0BAA0B;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,KAAK,EAAE,OAAO,CAAC;IACf,sBAAsB;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,+CAA+C;IAC/C,qBAAqB,EAAE,OAAO,CAAC;IAC/B,kBAAkB;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,YAAY,CAYhD,CAAC;AAqCH,wCAAwC;AACxC,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,CAoBnE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG;IACzB,oCAAoC;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wDAAwD;IACxD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,sCAAsC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,0BAA0B;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,KAAK,EAAE,OAAO,CAAC;IACf,sBAAsB;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,+CAA+C;IAC/C,qBAAqB,EAAE,OAAO,CAAC;IAC/B,kBAAkB;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,YAAY,CAYhD,CAAC;AAqCH,wCAAwC;AACxC,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,CAoBnE"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 共享常量:插件级魔法字符串,集中定义避免散落各模块。
3
3
  * 导出 PLUGIN_LABEL / HISTORY_TAG / HISTORY_TIP / COMPLETE_MESSAGE_DEFINITION /
4
- * COMPACT_CHECKPOINT_PLUGIN / isPluginOwnedSource。
4
+ * COMPACT_CHECKPOINT_PLUGIN / COMPACTION_ABORTED_ERROR / isPluginOwnedSource。
5
5
  */
6
6
  /** 插件标识:压缩消息 source.plugin 取值与日志前缀。 */
7
7
  export declare const PLUGIN_LABEL = "dsh-plugin-om";
@@ -16,6 +16,11 @@ export declare const HISTORY_TIP = "\u5F53\u524D\u5757\u662F\u5386\u53F2\u6D88\u
16
16
  export declare const COMPLETE_MESSAGE_DEFINITION = "`\u5B8C\u6574\u6D88\u606F`\u88AB\u5B9A\u4E49\u4E3A`\u7528\u6237\u6D88\u606F`\uFF0C`\u7CFB\u7EDF\u6D88\u606F`\uFF0C`\u6A21\u578B\u8F93\u51FA\u6587\u672C`\u548C`\u5177\u6709result\u7684toolcall`\u7684\u96C6\u5408\u3002\u9996\u6761`\u5B8C\u6574\u6D88\u606F`\u7684index\u662F0\uFF0C\u540E\u7EED\u7684index\u9012\u589E\u3002";
17
17
  /** 旧日志压缩消息的宿主 checkpoint 标记 plugin 名(历史兼容识别用)。 */
18
18
  export declare const COMPACT_CHECKPOINT_PLUGIN = "compact";
19
+ /**
20
+ * 压缩因 signal 中止而放弃时 compaction/end 的 error 标识(服务端写入、客户端过滤):
21
+ * 中止不是失败,客户端据此隐藏失败行(宿主不变量要求无 summary 的 end 必须带 error)。
22
+ */
23
+ export declare const COMPACTION_ABORTED_ERROR = "\u538B\u7F29\u5DF2\u4E2D\u6B62\uFF08signal aborted\uFF09";
19
24
  /** 判定 user/message 的 source 是否为本插件自产(压缩日志消息;含旧宿主 checkpoint 标记兼容)。这类消息不占完整消息 index。 */
20
25
  export declare function isPluginOwnedSource(source: {
21
26
  kind?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uCAAuC;AACvC,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAE5C,iDAAiD;AACjD,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,iDAAiD;AACjD,eAAO,MAAM,WAAW,iHAAuB,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,oUACgD,CAAC;AAEzF,kDAAkD;AAClD,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,uFAAuF;AACvF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACrD,OAAO,CAGT"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uCAAuC;AACvC,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAE5C,iDAAiD;AACjD,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,iDAAiD;AACjD,eAAO,MAAM,WAAW,iHAAuB,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,oUACgD,CAAC;AAEzF,kDAAkD;AAClD,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,6DAA0B,CAAC;AAEhE,uFAAuF;AACvF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACrD,OAAO,CAGT"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,6BAA6B;AAC7B,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC,wDAAwD;AACxD,eAAO,MAAM,MAAM,UAA6C,CAAC;AAEjE,yCAAyC;AACzC,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CA8C1D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,6BAA6B;AAC7B,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC,wDAAwD;AACxD,eAAO,MAAM,MAAM,UAA6C,CAAC;AAEjE,yCAAyC;AACzC,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAoD1D"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { scopeOf } from "@deepseek-ai/dsh-scope";
2
+ import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
1
3
  import { copyFileSync, existsSync, mkdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
4
  import { homedir } from "node:os";
3
5
  import path from "node:path";
@@ -11,7 +13,7 @@ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).expor
11
13
  /**
12
14
  * 共享常量:插件级魔法字符串,集中定义避免散落各模块。
13
15
  * 导出 PLUGIN_LABEL / HISTORY_TAG / HISTORY_TIP / COMPLETE_MESSAGE_DEFINITION /
14
- * COMPACT_CHECKPOINT_PLUGIN / isPluginOwnedSource。
16
+ * COMPACT_CHECKPOINT_PLUGIN / COMPACTION_ABORTED_ERROR / isPluginOwnedSource。
15
17
  */
16
18
  /** 插件标识:压缩消息 source.plugin 取值与日志前缀。 */
17
19
  const PLUGIN_LABEL = "dsh-plugin-om";
@@ -24,6 +26,11 @@ const HISTORY_TIP = "当前块是历史消息的压缩产物,不要复述";
24
26
  * 完整消息是摘要日志与 recall 共用的定位单位;首条 index 为 0,按会话顺序递增、全局稳定。
25
27
  */
26
28
  const COMPLETE_MESSAGE_DEFINITION = "`完整消息`被定义为`用户消息`,`系统消息`,`模型输出文本`和`具有result的toolcall`的集合。首条`完整消息`的index是0,后续的index递增。";
29
+ /**
30
+ * 压缩因 signal 中止而放弃时 compaction/end 的 error 标识(服务端写入、客户端过滤):
31
+ * 中止不是失败,客户端据此隐藏失败行(宿主不变量要求无 summary 的 end 必须带 error)。
32
+ */
33
+ const COMPACTION_ABORTED_ERROR = "压缩已中止(signal aborted)";
27
34
  /** 判定 user/message 的 source 是否为本插件自产(压缩日志消息;含旧宿主 checkpoint 标记兼容)。这类消息不占完整消息 index。 */
28
35
  function isPluginOwnedSource(source) {
29
36
  if (source?.kind !== "plugin") return false;
@@ -7511,6 +7518,13 @@ function buildHistoryPrompt() {
7511
7518
  "【数据源】下方的 <history> 消息记录是本次要压缩的全部消息;压缩结果作为一个新的 <history> 块输出。"
7512
7519
  ].join("\n");
7513
7520
  }
7521
+ /**
7522
+ * 静默 DOMParser:非致命解析问题不再走 xmldom 默认的 console.error 输出
7523
+ * (模型输出非法 XML 时避免刷 console),fatalError 仍抛 ParseError、解析语义不变。
7524
+ */
7525
+ function newQuietParser() {
7526
+ return new import_lib.DOMParser({ onError: () => {} });
7527
+ }
7514
7528
  /** 渲染用户消息条目(DOM 元素):文本块原样;图片/文件等非文本块以注释补充。 */
7515
7529
  function renderUserEntry(doc, session, cm) {
7516
7530
  const seq = cm.seqs[0];
@@ -7555,7 +7569,7 @@ function renderMessages(session, seqs) {
7555
7569
  if (reasonings.length > 0) reasoningBySeq.set(seq, reasonings);
7556
7570
  }
7557
7571
  const emittedReasoning = /* @__PURE__ */ new Set();
7558
- const doc = new import_lib.DOMParser().parseFromString(`<${HISTORY_TAG} />`, "text/xml");
7572
+ const doc = newQuietParser().parseFromString(`<${HISTORY_TAG} />`, "text/xml");
7559
7573
  const entries = [];
7560
7574
  for (const cm of indexCompleteMessages(session)) {
7561
7575
  if (!cm.seqs.every((seq) => shadowed.has(seq))) continue;
@@ -7646,7 +7660,7 @@ function buildSummaryOptions(session, instruction, contextText, maxTokens, targe
7646
7660
  return {
7647
7661
  provider: target.provider,
7648
7662
  model: target.model,
7649
- maxTokens,
7663
+ ...maxTokens === void 0 ? {} : { maxTokens },
7650
7664
  sessionId: session.id,
7651
7665
  purpose: "compaction",
7652
7666
  ...signal === void 0 ? {} : { signal },
@@ -7671,7 +7685,7 @@ function intAttr(el, name) {
7671
7685
  function parseHistoryBlock(xml) {
7672
7686
  let doc;
7673
7687
  try {
7674
- doc = new import_lib.DOMParser().parseFromString(xml, "text/xml");
7688
+ doc = newQuietParser().parseFromString(xml, "text/xml");
7675
7689
  } catch {
7676
7690
  return null;
7677
7691
  }
@@ -7830,7 +7844,7 @@ function rebuildHistoryBlock(inner) {
7830
7844
  content: inner.slice(open.contentFrom)
7831
7845
  });
7832
7846
  if (entries.length === 0) return null;
7833
- const doc = new import_lib.DOMParser().parseFromString(`<${HISTORY_TAG} />`, "text/xml");
7847
+ const doc = newQuietParser().parseFromString(`<${HISTORY_TAG} />`, "text/xml");
7834
7848
  const root = doc.documentElement;
7835
7849
  if (!root) return null;
7836
7850
  for (const e of entries) {
@@ -7844,37 +7858,39 @@ function rebuildHistoryBlock(inner) {
7844
7858
  /**
7845
7859
  * 从 AI 摘要输出中提取合法日志(不信任 AI 的总结结果):
7846
7860
  * 取首个 <history> 开标签(允许带属性)到最后一个 </history> 切为候选块,
7847
- * 找不到或内容过短返回 null;候选块经 XML 结构校验,整块非法时按条目模糊
7861
+ * 找不到或内容过短返回具体原因;候选块经 XML 结构校验,整块非法时按条目模糊
7848
7862
  * 提取重建为合法块(不要求模型输出整体合法 XML);随后统一校验:无 reasoning、
7849
7863
  * index 连续且与 expected 覆盖区间一致;通过后统一改写为带 tip 属性的开标签
7850
- * 并在块顶插入格式说明注释。
7864
+ * 并在块顶插入格式说明注释。所有失败原因均为说明性描述,不携带解析器原始报错。
7851
7865
  */
7852
- function extractSummaryLog(raw, expected) {
7866
+ function extractSummaryDetailed(raw, expected) {
7853
7867
  const closeTag = `</${HISTORY_TAG}>`;
7854
7868
  const openMatch = HISTORY_OPEN_TAG_RE.exec(raw);
7855
7869
  const open = openMatch?.index ?? -1;
7856
7870
  const close = raw.lastIndexOf(closeTag);
7857
- if (openMatch === null || close === -1 || close < open) return null;
7871
+ if (openMatch === null || close === -1 || close < open) return { error: "输出中找不到完整的 <history> 块(缺少 <history> 开标签或 </history> 闭标签)" };
7858
7872
  const inner = raw.slice(open + openMatch[0].length, close);
7859
- if (inner.trim().length < 10) return null;
7873
+ if (inner.trim().length < 10) return { error: "输出中 <history> 块内容过短(少于 10 字符)" };
7860
7874
  const block = raw.slice(open, close + closeTag.length);
7861
7875
  const candidate = parseHistoryBlock(block) === null ? rebuildHistoryBlock(inner) : block;
7862
- if (candidate === null) return null;
7876
+ if (candidate === null) return { error: "输出不是合法的 <history> 块(XML 结构非法,按条目模糊提取也未找到有效条目)" };
7863
7877
  const parsed = parseHistoryBlock(candidate);
7864
- if (parsed === null) return null;
7865
- if (parsed.hasReasoning) return null;
7878
+ if (parsed === null) return { error: "输出不是合法的 <history> 块(条目结构非法:属性缺失或包含未定义元素)" };
7879
+ if (parsed.hasReasoning) return { error: "输出包含 <reasoning> 条目(产物不允许携带思考过程)" };
7866
7880
  const span = historyContinuity(parsed.entries);
7867
- if (span === null) return null;
7868
- if (expected?.start !== void 0 && span.start !== expected.start) return null;
7869
- if (expected?.end !== void 0 && span.end !== expected.end) return null;
7881
+ if (span === null) return { error: "输出条目 index/start/end 不连续(跳号、重叠或乱序)" };
7882
+ if (expected?.start !== void 0 && span.start !== expected.start) return { error: `输出覆盖区间从 ${span.start} 开始,与期望起始 ${expected.start} 不一致` };
7883
+ if (expected?.end !== void 0 && span.end !== expected.end) return { error: `输出覆盖区间止于 ${span.end},与期望结束 ${expected.end} 不一致` };
7870
7884
  const candidateInner = candidate.slice(candidate.indexOf(">") + 1, candidate.length - closeTag.length).trim();
7871
- return `<${HISTORY_TAG} tip="${HISTORY_TIP}">\n${HISTORY_FORMAT_NOTE}\n${candidateInner}\n</${HISTORY_TAG}>`;
7885
+ return { log: `<${HISTORY_TAG} tip="${HISTORY_TIP}">\n${HISTORY_FORMAT_NOTE}\n${candidateInner}\n</${HISTORY_TAG}>` };
7872
7886
  }
7873
7887
  /**
7874
7888
  * 直连 LLM 执行一次摘要(观察或反思),返回文本与可选 token usage。
7875
- * 失败(抛异常 / 空输出 / 非 stop 结束 / 校验不通过)均记录日志并重试,最多尝试
7876
- * options.maxAttempts 次;全部失败返回 null(不产生任何日志变更)。每次请求发出前
7877
- * 先过全局限流等待门。
7889
+ * 失败(抛异常 / 空输出 / 非 stop 结束 / 校验不通过)均记录日志并重试,每次尝试的
7890
+ * 结果或报错始终写入日志(成功 info / 失败 warn,不受 debug 影响);全部尝试耗尽
7891
+ * 返回失败结果(携带最后一次尝试的实际报错/具体问题,不产生任何日志变更)。
7892
+ * signal 中止(含限流等待被中止)立即放弃并标记 aborted。每次请求发出前先过全局
7893
+ * 限流等待门。
7878
7894
  */
7879
7895
  async function runSummarySubagent(ctx, agent, instruction, contextText, maxTokens, target, debug, signal, options) {
7880
7896
  const session = agent.session;
@@ -7883,30 +7899,44 @@ async function runSummarySubagent(ctx, agent, instruction, contextText, maxToken
7883
7899
  let lastFailure = {};
7884
7900
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7885
7901
  if (signal?.aborted) {
7886
- logger.warn(`摘要调用中止(第 ${attempt}/${maxAttempts} 次尝试前 signal 已中止),放弃本次摘要`);
7887
- return null;
7902
+ logger.warn(`摘要调用中止(第 ${attempt}/${maxAttempts} 次尝试前 signal 已中止),放弃本次压缩`);
7903
+ return {
7904
+ ok: false,
7905
+ error: COMPACTION_ABORTED_ERROR,
7906
+ aborted: true
7907
+ };
7888
7908
  }
7889
7909
  const rateLimitWaitMs = options?.rateLimitWaitMs ?? 6e4;
7890
7910
  if (!await gateRateLimit(rateLimitWaitMs, signal)) {
7891
- logger.warn(`摘要调用中止(第 ${attempt}/${maxAttempts} 次尝试前限流等待被 signal 中止),放弃本次摘要`);
7892
- return null;
7911
+ logger.warn(`摘要调用中止(第 ${attempt}/${maxAttempts} 次尝试前限流等待被 signal 中止),放弃本次压缩`);
7912
+ return {
7913
+ ok: false,
7914
+ error: COMPACTION_ABORTED_ERROR,
7915
+ aborted: true
7916
+ };
7893
7917
  }
7894
- logger.step(`摘要调用开始(第 ${attempt}/${maxAttempts} 次,provider ${target.provider},model ${target.model},maxTokens ${maxTokens})`);
7918
+ logger.step(`摘要调用开始(第 ${attempt}/${maxAttempts} 次,provider ${target.provider},model ${target.model},maxTokens ${maxTokens === void 0 ? "未设置" : String(maxTokens)})`);
7895
7919
  try {
7896
7920
  const requestOptions = buildSummaryOptions(session, instruction, contextText, maxTokens, target, signal);
7897
7921
  const collector = new StreamCollector();
7898
7922
  for await (const chunk of ctx.llm.stream(requestOptions)) collector.push(chunk);
7899
- const text = extractSummaryLog(collector.text, options?.expected);
7923
+ const extracted = extractSummaryDetailed(collector.text, options?.expected);
7900
7924
  const finish = collector.finish;
7901
- if (finish.kind !== "stop" || text === null) {
7902
- const reason = finish.kind === "stop" ? collector.text.trim() === "" ? "无输出" : "缺少合法 <history> 块(含 reasoning 或 index 不连续)" : String(finish.kind);
7903
- lastFailure = { finish: reason };
7904
- logger.warn(`摘要未完成(第 ${attempt}/${maxAttempts} 次,${reason})` + (attempt < maxAttempts ? ",将重试" : ",重试耗尽,忽略本次摘要"));
7925
+ if (finish.kind !== "stop") {
7926
+ lastFailure = { reason: `摘要流以 ${String(finish.kind)} 结束(非正常完成)` };
7927
+ logger.warn(`摘要未完成(第 ${attempt}/${maxAttempts} 次,${lastFailure.reason})` + (attempt < maxAttempts ? ",将重试" : ",重试耗尽,放弃本次压缩"));
7928
+ continue;
7929
+ }
7930
+ if ("error" in extracted) {
7931
+ lastFailure = { reason: extracted.error };
7932
+ logger.warn(`摘要输出未通过校验(第 ${attempt}/${maxAttempts} 次,${extracted.error})` + (attempt < maxAttempts ? ",将重试" : ",重试耗尽,放弃本次压缩"));
7905
7933
  continue;
7906
7934
  }
7935
+ const text = extracted.log;
7907
7936
  const usage = collector.usage;
7908
- logger.step(`摘要调用成功(第 ${attempt}/${maxAttempts} 次,输出 ${text.length} 字符` + (usage === void 0 ? "" : `,input ${String(usage.inputTokens ?? "?")} / output ${String(usage.outputTokens ?? "?")} tokens`) + ")");
7937
+ logger.info(`摘要调用成功(第 ${attempt}/${maxAttempts} 次,输出 ${text.length} 字符` + (usage === void 0 ? "" : `,input ${String(usage.inputTokens ?? "?")} / output ${String(usage.outputTokens ?? "?")} tokens`) + ")");
7909
7938
  return {
7939
+ ok: true,
7910
7940
  text,
7911
7941
  attemptCount: attempt,
7912
7942
  ...usage === void 0 ? {} : { usage }
@@ -7918,11 +7948,16 @@ async function runSummarySubagent(ctx, agent, instruction, contextText, maxToken
7918
7948
  logger.warn(`摘要调用触发限流(429,第 ${attempt}/${maxAttempts} 次),下一次请求前至少等待 ${rateLimitWaitMs}ms`);
7919
7949
  }
7920
7950
  lastFailure = { error: message };
7921
- logger.warn(`摘要调用失败(第 ${attempt}/${maxAttempts} 次,${message})` + (attempt < maxAttempts ? ",将重试" : ",重试耗尽,忽略本次摘要"));
7951
+ logger.warn(`摘要调用失败(第 ${attempt}/${maxAttempts} 次,${message})` + (attempt < maxAttempts ? ",将重试" : ",重试耗尽,放弃本次压缩"));
7922
7952
  }
7923
7953
  }
7924
- logger.warn(`摘要调用最终失败(已尝试 ${maxAttempts} 次` + (lastFailure.error !== void 0 ? `,最后错误:${lastFailure.error}` : "") + (lastFailure.finish !== void 0 ? `,最后结果:${lastFailure.finish}` : "") + "),忽略本次摘要");
7925
- return null;
7954
+ const lastError = lastFailure.error ?? lastFailure.reason ?? "未知原因";
7955
+ logger.warn(`摘要调用最终失败(已尝试 ${maxAttempts} 次,最后错误:${lastError}),拒绝放行本轮 step`);
7956
+ return {
7957
+ ok: false,
7958
+ error: lastError,
7959
+ aborted: false
7960
+ };
7926
7961
  }
7927
7962
  //#endregion
7928
7963
  //#region src/compress.ts
@@ -7932,12 +7967,15 @@ async function runSummarySubagent(ctx, agent, instruction, contextText, maxToken
7932
7967
  * reflectPass / observePass / maybeCompress。
7933
7968
  *
7934
7969
  * - 反思:全部 <history> 块 token 合计 ≥ reflectThresholdTokens 时,摘要合并为一条
7935
- * - 观察:净压力(上下文压力 − 已压缩块 token 合计)≥ observeThresholdTokens 时,
7970
+ * - 观察:净压力(上下文压力 − 已压缩块 token 合计 系统提示词 token 估算 − 工具定义
7971
+ * token 估算)≥ observeThresholdTokens 时,
7936
7972
  * 摘要未压缩消息为新 <history> 块并精确替换被压缩区间(旧块保留)
7937
7973
  * - 两级在 pre-step 阻塞串行执行(先反思后观察);仅主会话生效;omEnabled=false 关闭
7938
7974
  * - 压缩边界:最后一个合法 <history> 块之后的消息视为未压缩,其前不重复压缩
7975
+ * - 摘要尝试全部耗尽时 pass 返回失败结果(携带最后一次尝试的实际报错),压缩流程
7976
+ * 向上传播,pre-step 据此拒绝本 step 中断当前 turn;signal 中止标记 aborted(不中断)
7939
7977
  * - 提交走宿主 compaction/* 生命周期事件(start 带 phase → summary → 替换消息 → end),
7940
- * 失败补 end(error);替换消息 source 标记插件标识供 UI 认领
7978
+ * 失败补 end(error,实际报错);替换消息 source 标记插件标识供 UI 认领
7941
7979
  */
7942
7980
  /** 历史文本 token 估算:4 字符 ≈ 1 token(与宿主 dsh-token-meter 启发式一致)。 */
7943
7981
  function estimateTextTokens(text) {
@@ -8084,7 +8122,7 @@ function appendCompactionSummary(session, data) {
8084
8122
  shadowedCharCount: data.shadowedCharCount,
8085
8123
  provider: data.provider,
8086
8124
  model: data.model,
8087
- maxTokens: data.maxTokens,
8125
+ ...data.maxTokens === void 0 ? {} : { maxTokens: data.maxTokens },
8088
8126
  attemptCount: data.attemptCount,
8089
8127
  ...data.usage === void 0 ? {} : { usage: data.usage }
8090
8128
  };
@@ -8120,7 +8158,8 @@ function appendHistoryMessage(session, content, sourceEventSeqs, surfaceOp, comp
8120
8158
  }
8121
8159
  /**
8122
8160
  * 反思:全部 <history> 块 token 合计 ≥ reflectThresholdTokens 时,摘要调用把整个块
8123
- * 区段合并替换为一条更紧凑的摘要。失败不产生部分替换。
8161
+ * 区段合并替换为一条更紧凑的摘要。失败不产生部分替换;摘要尝试全部耗尽返回
8162
+ * 失败结果(error = 最后一次尝试的实际报错/具体问题)。
8124
8163
  */
8125
8164
  async function reflectPass(ctx, agent, config, target, signal) {
8126
8165
  const session = agent.session;
@@ -8129,21 +8168,21 @@ async function reflectPass(ctx, agent, config, target, signal) {
8129
8168
  const { blocks } = historySection(session);
8130
8169
  if (blocks.length === 0) {
8131
8170
  logger.step("反思:无 <history> 压缩日志,跳过");
8132
- return;
8171
+ return { failed: false };
8133
8172
  }
8134
8173
  const threshold = config.reflectThresholdTokens;
8135
8174
  const tokens = blocks.reduce((total, block) => total + estimateTextTokens(block.text), 0);
8136
8175
  const shadowedCharCount = blocks.reduce((total, block) => total + historyInnerText(block.text).length, 0);
8137
8176
  if (tokens < threshold) {
8138
8177
  logger.step(`反思:摘要 ${tokens} tokens < 阈值 ${threshold},跳过`);
8139
- return;
8178
+ return { failed: false };
8140
8179
  }
8141
8180
  logger.step(`反思:摘要 ${tokens} tokens ≥ 阈值 ${threshold},触发精简合并(${blocks.length} 个块)`);
8142
8181
  const first = blocks[0];
8143
8182
  const last = blocks[blocks.length - 1];
8144
8183
  if (first === void 0 || last === void 0) {
8145
8184
  logger.step("反思:块区段缺失,跳过");
8146
- return;
8185
+ return { failed: false };
8147
8186
  }
8148
8187
  const blockSeqs = blocks.map((block) => block.seq);
8149
8188
  const instruction = buildHistoryPrompt();
@@ -8159,7 +8198,7 @@ async function reflectPass(ctx, agent, config, target, signal) {
8159
8198
  } catch (error) {
8160
8199
  const message = error instanceof Error ? error.message : String(error);
8161
8200
  logger.warn(`反思压缩启动失败: ${message}`);
8162
- return;
8201
+ return { failed: false };
8163
8202
  }
8164
8203
  const summaryResult = await runSummarySubagent(ctx, agent, instruction, contextText, config.compressMaxTokens, target, config.debug, signal, {
8165
8204
  maxAttempts: config.compressRetryCount + 1,
@@ -8169,12 +8208,16 @@ async function reflectPass(ctx, agent, config, target, signal) {
8169
8208
  },
8170
8209
  rateLimitWaitMs: config.rateLimitWaitMs
8171
8210
  });
8172
- if (summaryResult === null || summaryResult.text.trim().length === 0) {
8173
- logger.step("反思:摘要调用失败/无输出,追加 compaction/end(error)");
8211
+ if (!summaryResult.ok) {
8212
+ logger.warn(`反思:摘要调用失败(${summaryResult.error}),追加 compaction/end(error)`);
8174
8213
  try {
8175
- appendCompactionEnd(session, lifecycle, "摘要调用失败/无输出");
8214
+ appendCompactionEnd(session, lifecycle, summaryResult.error);
8176
8215
  } catch {}
8177
- return;
8216
+ return {
8217
+ failed: true,
8218
+ error: summaryResult.error,
8219
+ aborted: summaryResult.aborted
8220
+ };
8178
8221
  }
8179
8222
  const report = summaryResult.text;
8180
8223
  try {
@@ -8204,18 +8247,53 @@ async function reflectPass(ctx, agent, config, target, signal) {
8204
8247
  logger.step("反思提交:追加 compaction/end");
8205
8248
  appendCompactionEnd(session, lifecycle);
8206
8249
  logger.info(`反思完成(摘要 ${tokens} tokens ≥ 阈值 ${threshold},合并 ${blocks.length} 个块为一条)`);
8250
+ return { failed: false };
8207
8251
  } catch (error) {
8208
8252
  const message = error instanceof Error ? error.message : String(error);
8209
8253
  logger.warn(`反思提交失败: ${message}`);
8210
8254
  try {
8211
8255
  appendCompactionEnd(session, lifecycle, message);
8212
8256
  } catch {}
8257
+ return { failed: false };
8213
8258
  }
8214
8259
  }
8215
8260
  /**
8216
- * 观察:净压力 tokens(上下文压力 已压缩 <history> 块 token 合计)≥
8217
- * observeThresholdTokens 时,摘要调用把未压缩消息压缩为观察日志,追加到旧摘要并
8218
- * 替换被压缩消息区间。失败不产生部分替换。
8261
+ * 估算系统提示词 tokens:按 agent 作用域组装并渲染系统提示词,按长度/4 启发式计。
8262
+ * 宿主未提供 systemPrompt 服务(无 assemble)或组装/渲染失败时按 0 计——只影响
8263
+ * 观察触发时机(偏早触发),不产生错误。
8264
+ */
8265
+ async function estimateSystemPromptTokens(ctx, agent, logger, signal) {
8266
+ const systemPrompt = ctx.systemPrompt;
8267
+ if (typeof systemPrompt?.assemble !== "function") return 0;
8268
+ try {
8269
+ const agentCtx = agent.ctx;
8270
+ const scope = agentCtx === void 0 ? void 0 : scopeOf(agentCtx);
8271
+ const assembleContext = { agent };
8272
+ if (scope !== void 0) assembleContext.scope = scope;
8273
+ if (signal !== void 0) assembleContext.signal = signal;
8274
+ const assembly = await systemPrompt.assemble(assembleContext);
8275
+ return estimateTextTokens(renderPrompt(assembly));
8276
+ } catch (error) {
8277
+ const message = error instanceof Error ? error.message : String(error);
8278
+ logger.warn(`系统提示词 tokens 估算失败,按 0 计: ${message}`);
8279
+ return 0;
8280
+ }
8281
+ }
8282
+ /**
8283
+ * 估算请求工具定义 tokens:按会话请求头 tools(assembled tool schemas)JSON 序列化
8284
+ * 长度/4 启发式计(与宿主 dsh-token-meter 对工具 schema 的启发式一致)。会话请求头
8285
+ * 缺失或无 tools 时按 0 计——只影响观察触发时机(偏早触发),不产生错误。
8286
+ */
8287
+ function estimateToolsTokens(session) {
8288
+ const header = session.requestHeader();
8289
+ if (header?.tools === void 0 || header.tools.length === 0) return 0;
8290
+ return estimateTextTokens(JSON.stringify(header.tools));
8291
+ }
8292
+ /**
8293
+ * 观察:净压力 tokens(上下文压力 − 已压缩 <history> 块 token 合计 − 系统提示词
8294
+ * token 估算 − 工具定义 token 估算)≥ observeThresholdTokens 时,摘要调用把未压缩
8295
+ * 消息压缩为观察日志,追加到旧摘要并替换被压缩消息区间。失败不产生部分替换;
8296
+ * 摘要尝试全部耗尽返回失败结果(error = 最后一次尝试的实际报错/具体问题)。
8219
8297
  */
8220
8298
  async function observePass(ctx, agent, config, tailCount, target, signal) {
8221
8299
  const session = agent.session;
@@ -8225,29 +8303,31 @@ async function observePass(ctx, agent, config, tailCount, target, signal) {
8225
8303
  const pressureTokens = ctx.tokenMeter.measure(session).totalTokens;
8226
8304
  const { blocks } = historySection(session);
8227
8305
  const historyTokens = blocks.reduce((total, block) => total + estimateTextTokens(block.text), 0);
8228
- const netTokens = pressureTokens - historyTokens;
8306
+ const systemTokens = await estimateSystemPromptTokens(ctx, agent, logger, signal);
8307
+ const toolsTokens = estimateToolsTokens(session);
8308
+ const netTokens = pressureTokens - historyTokens - systemTokens - toolsTokens;
8229
8309
  if (netTokens < threshold) {
8230
- logger.step(`观察:净压力 ${netTokens} tokens(上下文压力 ${pressureTokens} − 已压缩块 ${historyTokens})< 阈值 ${threshold},跳过`);
8231
- return;
8310
+ logger.step(`观察:净压力 ${netTokens} tokens(上下文压力 ${pressureTokens} − 已压缩块 ${historyTokens} − 系统提示词 ${systemTokens} − 工具定义 ${toolsTokens})< 阈值 ${threshold},跳过`);
8311
+ return { failed: false };
8232
8312
  }
8233
- logger.step(`观察:净压力 ${netTokens} tokens(上下文压力 ${pressureTokens} − 已压缩块 ${historyTokens})≥ 阈值 ${threshold},触发压缩`);
8313
+ logger.step(`观察:净压力 ${netTokens} tokens(上下文压力 ${pressureTokens} − 已压缩块 ${historyTokens} − 系统提示词 ${systemTokens} − 工具定义 ${toolsTokens})≥ 阈值 ${threshold},触发压缩`);
8234
8314
  const range = computeCompressRange(session, tailCount);
8235
8315
  if (!range) {
8236
8316
  logger.step("观察:无可行压缩区间(边界后消息过短或配对无法平衡),跳过");
8237
- return;
8317
+ return { failed: false };
8238
8318
  }
8239
8319
  logger.step(`观察:压缩区间 [${range.start}..${range.end}],遮蔽 ${range.shadowedSeqs.length} 个表层节点`);
8240
8320
  const replaceSeqs = range.shadowedSeqs;
8241
8321
  const replaceStart = replaceSeqs[0];
8242
8322
  if (replaceStart === void 0) {
8243
8323
  logger.step("观察:区间内无新消息(全部为压缩日志块),跳过");
8244
- return;
8324
+ return { failed: false };
8245
8325
  }
8246
8326
  const shadowedSet = new Set(replaceSeqs);
8247
8327
  const inRangeCms = indexCompleteMessages(session).filter((cm) => cm.seqs.every((seq) => shadowedSet.has(seq)));
8248
8328
  if (inRangeCms.length === 0) {
8249
8329
  logger.step("观察:区间内无完整消息,跳过");
8250
- return;
8330
+ return { failed: false };
8251
8331
  }
8252
8332
  const startIndex = inRangeCms[0]?.index ?? 0;
8253
8333
  const endIndex = inRangeCms[inRangeCms.length - 1]?.index ?? startIndex;
@@ -8264,7 +8344,7 @@ async function observePass(ctx, agent, config, tailCount, target, signal) {
8264
8344
  } catch (error) {
8265
8345
  const message = error instanceof Error ? error.message : String(error);
8266
8346
  logger.warn(`观察压缩启动失败: ${message}`);
8267
- return;
8347
+ return { failed: false };
8268
8348
  }
8269
8349
  const summaryResult = await runSummarySubagent(ctx, agent, instruction, contextText, config.compressMaxTokens, target, config.debug, signal, {
8270
8350
  maxAttempts: config.compressRetryCount + 1,
@@ -8274,12 +8354,16 @@ async function observePass(ctx, agent, config, tailCount, target, signal) {
8274
8354
  },
8275
8355
  rateLimitWaitMs: config.rateLimitWaitMs
8276
8356
  });
8277
- if (summaryResult === null || summaryResult.text.trim().length === 0) {
8278
- logger.step("观察:摘要调用失败/无输出,追加 compaction/end(error)");
8357
+ if (!summaryResult.ok) {
8358
+ logger.warn(`观察:摘要调用失败(${summaryResult.error}),追加 compaction/end(error)`);
8279
8359
  try {
8280
- appendCompactionEnd(session, lifecycle, "摘要调用失败/无输出");
8360
+ appendCompactionEnd(session, lifecycle, summaryResult.error);
8281
8361
  } catch {}
8282
- return;
8362
+ return {
8363
+ failed: true,
8364
+ error: summaryResult.error,
8365
+ aborted: summaryResult.aborted
8366
+ };
8283
8367
  }
8284
8368
  const report = summaryResult.text;
8285
8369
  const attemptCount = summaryResult.attemptCount - 1;
@@ -8321,34 +8405,45 @@ async function observePass(ctx, agent, config, tailCount, target, signal) {
8321
8405
  logger.step("观察提交:追加 compaction/end");
8322
8406
  appendCompactionEnd(session, lifecycle);
8323
8407
  logger.info(`观察压缩完成(净压力 ${netTokens} tokens(上下文压力 ${pressureTokens} − 已压缩块 ${historyTokens})≥ 阈值 ${threshold},替换 ${replaceSeqs.length} 个表层节点,约 ${shadowedTokenCount} tokens)`);
8408
+ return { failed: false };
8324
8409
  } catch (error) {
8325
8410
  const message = error instanceof Error ? error.message : String(error);
8326
8411
  logger.warn(`观察压缩提交失败: ${message}`);
8327
8412
  try {
8328
8413
  appendCompactionEnd(session, lifecycle, message);
8329
8414
  } catch {}
8415
+ return { failed: false };
8330
8416
  }
8331
8417
  }
8332
- /** 压力检查 + 两级压缩入口:先反思后观察,pre-step 阻塞串行执行;仅主会话生效。 */
8418
+ /**
8419
+ * 压力检查 + 两级压缩入口:先反思后观察,pre-step 阻塞串行执行;仅主会话生效。
8420
+ * 反思摘要耗尽失败时直接返回失败结果(观察无需继续,本轮将被拒绝);观察失败同样
8421
+ * 传播失败结果,由 pre-step 拒绝本 step 中断当前 turn。
8422
+ */
8333
8423
  async function maybeCompress(ctx, agent, config, signal) {
8334
8424
  const session = agent.session;
8335
8425
  const logger = makeLogger(ctx, config.debug);
8336
8426
  if (!config.omEnabled) {
8337
8427
  logger.step("omEnabled=false,跳过压缩");
8338
- return;
8428
+ return { failed: false };
8339
8429
  }
8340
8430
  const target = routedTarget(session);
8341
8431
  if (target === void 0) {
8342
8432
  logger.step("会话未路由(无 provider/model),跳过压缩");
8343
- return;
8433
+ return { failed: false };
8344
8434
  }
8345
8435
  logger.step(`会话路由:provider ${target.provider},model ${target.model}`);
8346
8436
  const tailCount = config.tailMessageCount;
8347
8437
  logger.step("反思 pass 开始");
8348
- await reflectPass(ctx, agent, config, target, signal);
8438
+ const reflect = await reflectPass(ctx, agent, config, target, signal);
8439
+ if (reflect.failed) {
8440
+ logger.step("反思 pass 失败(本轮将被拒绝),跳过观察 pass");
8441
+ return reflect;
8442
+ }
8349
8443
  logger.step("反思 pass 结束,观察 pass 开始");
8350
- await observePass(ctx, agent, config, tailCount, target, signal);
8444
+ const observe = await observePass(ctx, agent, config, tailCount, target, signal);
8351
8445
  logger.step("观察 pass 结束,压缩流程完成");
8446
+ return observe;
8352
8447
  }
8353
8448
  //#endregion
8354
8449
  //#region src/model-download.ts
@@ -8559,9 +8654,9 @@ function cosineSimilarity(a, b) {
8559
8654
  */
8560
8655
  /** 默认配置(冻结对象,resolveConfig 合并的基底;debug 缺省值在解析时按 NODE_ENV 判定)。 */
8561
8656
  const DEFAULT_CONFIG = Object.freeze({
8562
- observeThresholdTokens: 35e3,
8563
- reflectThresholdTokens: 4e4,
8564
- compressMaxTokens: 5e3,
8657
+ observeThresholdTokens: 45e3,
8658
+ reflectThresholdTokens: 12e4,
8659
+ compressMaxTokens: void 0,
8565
8660
  rateLimitWaitMs: 6e4,
8566
8661
  tailMessageCount: 5,
8567
8662
  compressRetryCount: 5,
@@ -8966,7 +9061,8 @@ function buildSemanticRecallTool(options) {
8966
9061
  /**
8967
9062
  * dsh-plugin-om 入口(tsdown 打包入口):导出 name / inject / apply。
8968
9063
  * apply 注册 recall / recall-semantic 工具,并接线 agent/pre-step 自动压缩
8969
- * (先反思后观察,仅主会话生效)。压缩与检索的实现见 compress.ts / recall.ts /
9064
+ * (先反思后观察,仅主会话生效)。压缩摘要尝试全部耗尽时拒绝本 step 中断当前
9065
+ * turn(signal 中止除外)。压缩与检索的实现见 compress.ts / recall.ts /
8970
9066
  * semantic-recall.ts。
8971
9067
  */
8972
9068
  /** 插件名(Loader 识别入口的稳定标识)。 */
@@ -8982,14 +9078,11 @@ const inject = [
8982
9078
  function apply(ctx, config) {
8983
9079
  const resolved = resolveConfig(config);
8984
9080
  const logger = makeLogger(ctx, resolved.debug);
8985
- logger.step(`apply 启动:observeThresholdTokens=${String(resolved.observeThresholdTokens)} reflectThresholdTokens=${String(resolved.reflectThresholdTokens)} compressMaxTokens=${String(resolved.compressMaxTokens)} tailMessageCount=${String(resolved.tailMessageCount)} omEnabled=${String(resolved.omEnabled)} debug=${String(resolved.debug)}`);
9081
+ logger.step(`apply 启动:observeThresholdTokens=${String(resolved.observeThresholdTokens)} reflectThresholdTokens=${String(resolved.reflectThresholdTokens)} compressMaxTokens=${resolved.compressMaxTokens === void 0 ? "未设置" : String(resolved.compressMaxTokens)} tailMessageCount=${String(resolved.tailMessageCount)} omEnabled=${String(resolved.omEnabled)} debug=${String(resolved.debug)}`);
8986
9082
  if (resolved.recallEnabled) ctx.tools.register(buildRecallTool(() => ctx.get("toolResultPruner")));
8987
9083
  if (resolved.semanticRecallEnabled) {
8988
9084
  const warnModel = (message) => ctx.logger.warn(`dsh-plugin-om: ${message}`);
8989
- const logModel = (message) => {
8990
- console.log(message);
8991
- logger.info(message);
8992
- };
9085
+ const logModel = (message) => logger.info(message);
8993
9086
  ensureModelReady(resolved.modelDir, warnModel, void 0, logModel);
8994
9087
  ctx.tools.register(buildSemanticRecallTool({
8995
9088
  getPruner: () => ctx.get("toolResultPruner"),
@@ -8998,16 +9091,21 @@ function apply(ctx, config) {
8998
9091
  }));
8999
9092
  }
9000
9093
  ctx.on("agent/pre-step", async ({ agent, signal }, next) => {
9094
+ let outcome = { failed: false };
9001
9095
  try {
9002
9096
  if (signal.aborted) logger.step("pre-step 已中止(signal aborted),跳过压缩");
9003
9097
  else if (!isMainSession(agent.session)) logger.step("subagent 会话,跳过压缩(仅主会话生效)");
9004
9098
  else {
9005
9099
  logger.step(`pre-step 触发压缩(会话 ${agent.session.id})`);
9006
- await maybeCompress(ctx, agent, resolved, signal);
9100
+ outcome = await maybeCompress(ctx, agent, resolved, signal);
9007
9101
  }
9008
9102
  } catch (error) {
9009
9103
  logger.warn(`pre-step 处理失败: ${error instanceof Error ? error.message : String(error)}`);
9010
9104
  }
9105
+ if (outcome.failed && !outcome.aborted) {
9106
+ logger.warn(`上下文压缩失败,拒绝本 step 中断当前 turn:${outcome.error}`);
9107
+ return { kind: "reject" };
9108
+ }
9011
9109
  return next();
9012
9110
  });
9013
9111
  }
@@ -5,7 +5,9 @@
5
5
  * 摘要以新会话方式直连 ctx.llm.stream():指令(共享提示词)作为 system、被压缩消息
6
6
  * (renderMessages 渲染为合法 <history> 块)作为 user 输入;输出经 extractSummaryLog
7
7
  * 定位与校验(首个 <history> 开标签到最后一个 </history>;整块 XML 非法时按条目标签
8
- * 模糊提取重建 / 无 reasoning / index 连续),失败按 maxAttempts 重试;每次请求前
8
+ * 模糊提取重建 / 无 reasoning / index 连续),失败按 maxAttempts 重试;每次尝试的
9
+ * 结果或报错始终写入日志(成功 info / 失败 warn,失败原因说明具体问题而非解析器
10
+ * 原始报错);全部耗尽返回失败结果(携带最后一次尝试的实际报错)。每次请求前
9
11
  * 过全局限流等待门;token usage 归入主会话记录。
10
12
  */
11
13
  import type { Agent, Context, Session, TokenUsage } from './types.ts';
@@ -23,13 +25,24 @@ export declare function buildHistoryPrompt(): string;
23
25
  * 文本经 XML 序列化自动转义;仅渲染 seqs 全部落在给定集合内的完整消息。
24
26
  */
25
27
  export declare function renderMessages(session: Session, seqs: readonly number[]): string;
26
- /** 摘要调用结果:文本 + 可选 token usage + 成功时的尝试次数。 */
27
- export type SummarySubagentResult = {
28
+ /** 摘要调用成功结果:文本 + 可选 token usage + 尝试次数。 */
29
+ export type SummarySuccess = {
30
+ ok: true;
28
31
  text: string;
29
32
  usage?: TokenUsage;
30
33
  /** 成功时的尝试次数(1 起;载荷层换算为重试次数 = 该值 - 1)。 */
31
34
  attemptCount: number;
32
35
  };
36
+ /** 摘要调用失败结果:最后一次尝试的实际报错/具体问题 + 是否因 signal 中止。 */
37
+ export type SummaryFailure = {
38
+ ok: false;
39
+ /** 最后一次尝试的实际报错(异常消息)或未通过校验的具体问题说明。 */
40
+ error: string;
41
+ /** 因 signal 中止(含限流等待被中止)而放弃;此时 error 为 COMPACTION_ABORTED_ERROR。 */
42
+ aborted: boolean;
43
+ };
44
+ /** 摘要调用结果(成功/失败二选一)。 */
45
+ export type SummaryOutcome = SummarySuccess | SummaryFailure;
33
46
  /** 日志最小有效长度:<history> 中间内容小于该长度视为不合法。 */
34
47
  export declare const MIN_HISTORY_LENGTH = 10;
35
48
  /** 产出日志后插入首个 <history> 后的格式说明(XML 注释,完整消息定义 + 条目标签语义)。 */
@@ -62,23 +75,34 @@ export type SummaryValidationRange = {
62
75
  /**
63
76
  * 从 AI 摘要输出中提取合法日志(不信任 AI 的总结结果):
64
77
  * 取首个 <history> 开标签(允许带属性)到最后一个 </history> 切为候选块,
65
- * 找不到或内容过短返回 null;候选块经 XML 结构校验,整块非法时按条目模糊
78
+ * 找不到或内容过短返回具体原因;候选块经 XML 结构校验,整块非法时按条目模糊
66
79
  * 提取重建为合法块(不要求模型输出整体合法 XML);随后统一校验:无 reasoning、
67
80
  * index 连续且与 expected 覆盖区间一致;通过后统一改写为带 tip 属性的开标签
68
- * 并在块顶插入格式说明注释。
81
+ * 并在块顶插入格式说明注释。所有失败原因均为说明性描述,不携带解析器原始报错。
82
+ */
83
+ export declare function extractSummaryDetailed(raw: string, expected?: SummaryValidationRange): {
84
+ log: string;
85
+ } | {
86
+ error: string;
87
+ };
88
+ /**
89
+ * extractSummaryDetailed 的简便形式:提取成功返回合法日志块,失败返回 null
90
+ * (具体失败原因经 extractSummaryDetailed 获取)。
69
91
  */
70
92
  export declare function extractSummaryLog(raw: string, expected?: SummaryValidationRange): string | null;
71
93
  /** 摘要调用总尝试次数兜底(无 options.maxAttempts 时;实际由 config.compressRetryCount + 1 传入)。 */
72
94
  export declare const SUMMARY_DEFAULT_MAX_ATTEMPTS = 11;
73
95
  /**
74
96
  * 直连 LLM 执行一次摘要(观察或反思),返回文本与可选 token usage。
75
- * 失败(抛异常 / 空输出 / 非 stop 结束 / 校验不通过)均记录日志并重试,最多尝试
76
- * options.maxAttempts 次;全部失败返回 null(不产生任何日志变更)。每次请求发出前
77
- * 先过全局限流等待门。
97
+ * 失败(抛异常 / 空输出 / 非 stop 结束 / 校验不通过)均记录日志并重试,每次尝试的
98
+ * 结果或报错始终写入日志(成功 info / 失败 warn,不受 debug 影响);全部尝试耗尽
99
+ * 返回失败结果(携带最后一次尝试的实际报错/具体问题,不产生任何日志变更)。
100
+ * signal 中止(含限流等待被中止)立即放弃并标记 aborted。每次请求发出前先过全局
101
+ * 限流等待门。
78
102
  */
79
- export declare function runSummarySubagent(ctx: Context, agent: Agent, instruction: string, contextText: string | undefined, maxTokens: number, target: RoutedTarget, debug: boolean, signal?: AbortSignal, options?: {
103
+ export declare function runSummarySubagent(ctx: Context, agent: Agent, instruction: string, contextText: string | undefined, maxTokens: number | undefined, target: RoutedTarget, debug: boolean, signal?: AbortSignal, options?: {
80
104
  maxAttempts?: number;
81
105
  expected?: SummaryValidationRange;
82
106
  rateLimitWaitMs?: number;
83
- }): Promise<SummarySubagentResult | null>;
107
+ }): Promise<SummaryOutcome>;
84
108
  //# sourceMappingURL=summarize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../src/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmBH,OAAO,KAAK,EAAE,KAAK,EAAmB,OAAO,EAAE,OAAO,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AACpG,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAuC3C;AAkCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsDhF;AAED,6CAA6C;AAC7C,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AA2EF,yCAAyC;AACzC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,yrBAA6K,CAAC;AAE9M,4CAA4C;AAC5C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,KAAK,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAoEF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CASrE;AACD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,iBAAiB,EAAE,GAC3B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAmBvC;AAED,sCAAsC;AACtC,MAAM,MAAM,sBAAsB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA+EtE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAsB/F;AAED,iFAAiF;AACjF,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAK/C;;;;;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,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9F,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAiFvC"}
1
+ {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../src/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,OAAO,KAAK,EAAE,KAAK,EAAmB,OAAO,EAAE,OAAO,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AACpG,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAuC3C;AA0CD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsDhF;AAED,2CAA2C;AAC3C,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,iDAAiD;AACjD,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,KAAK,CAAC;IACV,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,wBAAwB;AACxB,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,cAAc,CAAC;AA2E7D,yCAAyC;AACzC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,yrBAA6K,CAAC;AAE9M,4CAA4C;AAC5C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,KAAK,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAoEF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CASrE;AACD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,iBAAiB,EAAE,GAC3B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAmBvC;AAED,sCAAsC;AACtC,MAAM,MAAM,sBAAsB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA+EtE;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,sBAAsB,GAChC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CA4CrC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAG/F;AAED,iFAAiF;AACjF,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAK/C;;;;;;;GAOG;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,GAAG,SAAS,EAC7B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9F,OAAO,CAAC,cAAc,CAAC,CAoFzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-om",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "type": "module",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.mjs",