dsh-plugin-om 0.0.11 → 0.0.13
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 +16 -10
- package/dist/compress.d.ts +8 -12
- package/dist/compress.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.mjs +95 -90
- package/dist/summarize.d.ts +2 -1
- package/dist/summarize.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,10 +74,15 @@ dsh的"预设"分为两层,`dsh web`等同于`dsh --profile web`,调用的
|
|
|
74
74
|
## 工作原理
|
|
75
75
|
|
|
76
76
|
1. 在未压缩的消息到达观察阈值后,进行摘要
|
|
77
|
-
2.
|
|
78
|
-
3.
|
|
77
|
+
2. 将新生成摘要追加到已有摘要后(每条压缩日志是独立的消息/块,旧块原地保留)
|
|
78
|
+
3. 如果摘要总长到达反思阈值,对其本身进行摘要(把全部 <om-history> 块合并为一条)
|
|
79
79
|
4. 提供recall/recall-semantic两个tool进行检索
|
|
80
80
|
|
|
81
|
+
### 压缩日志块结构
|
|
82
|
+
|
|
83
|
+
- 观察压缩只精确替换被压缩的新消息区间:旧 <om-history> 块保留为独立消息,新观察日志作为新块追加在其后(多块并存按序排列),历史不会随压缩次数膨胀;反思合并时才把多个块合并为一条更紧凑的摘要
|
|
84
|
+
- 块开标签带 `tip` 属性(对 AI 的提醒:"当前块是历史消息的压缩产物,不要复述");块顶为构成逻辑注释(完整消息三类与 index 定义),消息正文不再附加前缀句
|
|
85
|
+
|
|
81
86
|
### 注意
|
|
82
87
|
|
|
83
88
|
- recall 不截断,建议保留 `tool-result-pruner`
|
|
@@ -85,17 +90,16 @@ dsh的"预设"分为两层,`dsh web`等同于`dsh --profile web`,调用的
|
|
|
85
90
|
|
|
86
91
|
### 依赖策略
|
|
87
92
|
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
- 模型二进制:量化 ONNX 约 113MB,超过 GitHub 单文件 100MB 限制,**不进入 git 仓库,也不随 npm 包分发**(`package.json` 的 `files` 以 `!models/*/onnx/*.onnx` 排除,见 `tests/package-pack.test.ts`)。改为**运行时按需下载**:仅当配置键 `semanticRecallEnabled` 启用且共享目录 `$DSH_HOME/plugin-data/dsh-plugin-om/models/<id>/onnx/model_quantized.onnx` 缺失时,插件 apply 后台自动从 HuggingFace([Xenova 转换仓库](https://huggingface.co/Xenova/paraphrase-multilingual-MiniLM-L12-v2))下载到该共享目录(不阻塞;下载开始/结束经 console 与插件日志输出,失败仅记日志并附带设置 `HF_ENDPOINT=https://hf-mirror.com` 的镜像建议,下次调用自动重试;未就绪时 `recall-semantic` 工具返回文案告知模型);共享目录跨插件版本复用,升级/重装插件不重复下载,随包小文件(config/tokenizer 等)缺失时自动从插件包复制补齐(离线可用);本地开发也可用 `pnpm run download:model` 手动预下载到同一共享目录(已存在则跳过,`--force` 强制重下);直连 `huggingface.co` 受限时设置环境变量 `HF_ENDPOINT=https://hf-mirror.com` 走镜像
|
|
93
|
+
- dsh宿主提供的依赖,直接复用即可,如 cordis / dsh-tools / zod 等
|
|
94
|
+
- 向量模型模型二进制:量化 ONNX 约 113MB,启用语义召回时下载,位置`$DSH_HOME/plugin-data/dsh-plugin-om/models/<id>/onnx/model_quantized.onnx`。下载失败可以设置`HF_ENDPOINT=https://hf-mirror.com`
|
|
91
95
|
|
|
92
96
|
## 插件配置项
|
|
93
97
|
|
|
94
98
|
| 键 | 默认 | 含义 |
|
|
95
99
|
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
96
|
-
| `thresholdRatio` | `0.
|
|
97
|
-
| `historyMergeRatio` | `0.2` |
|
|
98
|
-
| `compressMaxTokens` | `
|
|
100
|
+
| `thresholdRatio` | `0.1` | 观察阈值:未压缩消息 ≥ 窗口 × 该比例触发压缩 |
|
|
101
|
+
| `historyMergeRatio` | `0.2` | 反思阈值:全部 <om-history> 块总长 ≥ 窗口 × 该比例触发精简合并 |
|
|
102
|
+
| `compressMaxTokens` | `10000` | 单次摘要(观察/反思调用)生成上限 |
|
|
99
103
|
| `tailMessageCount` | `10` | 尾部保留的不压缩消息条数(不压缩、不被替换、不进摘要日志) |
|
|
100
104
|
| `modelDir` | 共享目录 | recall-semantic 嵌入模型目录(默认 `$DSH_HOME/plugin-data/dsh-plugin-om/models/<id>`,跨插件版本共享;小文件随包分发并在缺失时自动补齐,onnx 缺失且启用语义召回时运行时自动下载到该目录;可指向自定义目录) |
|
|
101
105
|
| `summaryMode` | `fork` | 摘要模式:`fork`(缺省)/ `new` / `disable`(关闭自动压缩);非法值在插件加载时报错(见[摘要模式](#摘要模式)) |
|
|
@@ -103,6 +107,8 @@ dsh的"预设"分为两层,`dsh web`等同于`dsh --profile web`,调用的
|
|
|
103
107
|
| `recallEnabled` | `true` | 是否注册 `recall` 工具(`false` 时禁用,不注册) |
|
|
104
108
|
| `semanticRecallEnabled` | `true` | 是否注册 `recall-semantic` 工具(`false` 时禁用,不注册、不触发模型下载) |
|
|
105
109
|
|
|
110
|
+
> 不建议将thresholdRatio设置的过高,越早OM收益越高,且当前的机制需要模型对消息计数,过多的消息会导致历史混乱
|
|
111
|
+
|
|
106
112
|
### 摘要模式
|
|
107
113
|
|
|
108
114
|
摘要调用由配置键 `summaryMode` 控制:
|
|
@@ -140,7 +146,7 @@ src/
|
|
|
140
146
|
│ │ └─▶ model-download.ts # 模型下载原语(URL/跳过判定/原子落盘;dev CLI 复用)
|
|
141
147
|
│ └─ ④ 事件接线(仅主会话生效)
|
|
142
148
|
│ └─ ctx.on('agent/pre-step') → compress.ts # maybeCompress:两级压缩阻塞串行(先反思后观察;turn 中间即可触发)
|
|
143
|
-
│ ├─ reflectPass → summarize.ts #
|
|
149
|
+
│ ├─ reflectPass → summarize.ts # 全部 <om-history> 块总长 ≥ 窗口 × historyMergeRatio:摘要调用合并整个块区段
|
|
144
150
|
│ ├─ observePass → summarize.ts # 未压缩消息 ≥ 窗口 × thresholdRatio:摘要调用观察日志 → 追加 + 替换
|
|
145
151
|
│ └─ 提交 → compress.ts # compaction/start → summary → 替换消息(checkpoint) → end;usage 归入主会话
|
|
146
152
|
├── constants.ts # 共享常量(PLUGIN_LABEL / HISTORY_TAG / COMPACT_CHECKPOINT_PLUGIN)
|
|
@@ -157,7 +163,7 @@ src/
|
|
|
157
163
|
models/
|
|
158
164
|
└── paraphrase-multilingual-MiniLM-L12-v2/ # 嵌入模型目录(小文件随包分发;onnx 二进制不随包分发、由运行时按需下载到跨版本共享目录,不进 git)
|
|
159
165
|
scripts/ # release-archive.mjs(CHANGELOG 归档)/ download-model.mjs(开发手动预下载 CLI)
|
|
160
|
-
tests/ # vitest 单元测试(
|
|
166
|
+
tests/ # vitest 单元测试(146 例)
|
|
161
167
|
.dsh/skills/ # 项目级 skill(feature-defect-workflow:需求/缺陷完成工作流)
|
|
162
168
|
```
|
|
163
169
|
|
package/dist/compress.d.ts
CHANGED
|
@@ -11,11 +11,6 @@ export type TokenEstimator = {
|
|
|
11
11
|
* (观察阈值衡量对象)。
|
|
12
12
|
*/
|
|
13
13
|
export declare function measureUncompressedTokens(session: Session, meter: TokenEstimator): number;
|
|
14
|
-
/** 定位日志中最后一次 <om-history> 压缩日志(内文 + 事件 seq);无则 undefined。 */
|
|
15
|
-
export declare function findLatestHistory(session: Session): {
|
|
16
|
-
text: string;
|
|
17
|
-
seq: number;
|
|
18
|
-
} | undefined;
|
|
19
14
|
/**
|
|
20
15
|
* 判定表层节点 seq 之后的切点是否 tool-call/result 配对平衡(与宿主
|
|
21
16
|
* dsh-compaction 的 toolPairingBalancedAfter 同语义):按表层顺序折叠未闭合的
|
|
@@ -34,17 +29,18 @@ export declare function computeCompressRange(session: Session, tailCount: number
|
|
|
34
29
|
shadowedSeqs: number[];
|
|
35
30
|
} | undefined;
|
|
36
31
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
32
|
+
* 收集表层中全部 <om-history> 压缩日志(内文 + seq,按表层顺序)。
|
|
33
|
+
* 压缩日志消息始终位于表层头部连续区段(每次观察在旧块之后替换新消息区间,
|
|
34
|
+
* 反思把整个块区段合并为一条),因此表层中所有压缩日志即头部区段;
|
|
35
|
+
* 头部区段后即结束扫描(不遍历尾部消息)。
|
|
40
36
|
*/
|
|
41
|
-
export declare function
|
|
37
|
+
export declare function listHistoryBlocks(session: Session): Array<{
|
|
42
38
|
text: string;
|
|
43
39
|
seq: number;
|
|
44
|
-
}
|
|
40
|
+
}>;
|
|
45
41
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
42
|
+
* 反思:全部 <om-history> 块 tokens 合计 ≥ 窗口 × historyMergeRatio 时,摘要调用
|
|
43
|
+
* 精简合并,把整个块区段替换为一条更紧凑的摘要。失败不产生部分替换。
|
|
48
44
|
*/
|
|
49
45
|
export declare function reflectPass(ctx: Context, agent: Agent, config: Readonly<PluginConfig>, window: number, target: RoutedTarget, signal?: AbortSignal): Promise<void>;
|
|
50
46
|
/**
|
package/dist/compress.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EACV,KAAK,EAEL,OAAO,EACP,YAAY,EACZ,OAAO,EAIR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAgB,KAAK,YAAY,EAAsB,MAAM,YAAY,CAAC;AAEjF,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAyBD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,CAAC;AAE3E;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAYzF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAc1E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,CAuBpE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAUxF;AA4FD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAwGf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA0If;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAwCf"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
export declare const PLUGIN_LABEL = "dsh-plugin-om";
|
|
6
6
|
/** 压缩日志标签名:<om-history>...</om-history> 包裹观察/反思日志块。 */
|
|
7
7
|
export declare const HISTORY_TAG = "om-history";
|
|
8
|
+
/** 压缩日志块开标签上的 tip 属性(对 AI 的提醒:本块是历史压缩产物,不要复述)。 */
|
|
9
|
+
export declare const HISTORY_TIP = "\u5F53\u524D\u5757\u662F\u5386\u53F2\u6D88\u606F\u7684\u538B\u7F29\u4EA7\u7269\uFF0C\u4E0D\u8981\u590D\u8FF0";
|
|
8
10
|
/** 宿主压缩 checkpoint 标记的 plugin 名(dsh-compaction-basic 的 COMPACT_CHECKPOINT_MARKER.plugin)。 */
|
|
9
11
|
export declare const COMPACT_CHECKPOINT_PLUGIN = "compact";
|
|
10
12
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uCAAuC;AACvC,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAE5C,uDAAuD;AACvD,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC,6FAA6F;AAC7F,eAAO,MAAM,yBAAyB,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uCAAuC;AACvC,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAE5C,uDAAuD;AACvD,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC,kDAAkD;AAClD,eAAO,MAAM,WAAW,iHAAuB,CAAC;AAEhD,6FAA6F;AAC7F,eAAO,MAAM,yBAAyB,YAAY,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,8 @@ import { z } from "zod";
|
|
|
11
11
|
const PLUGIN_LABEL = "dsh-plugin-om";
|
|
12
12
|
/** 压缩日志标签名:<om-history>...</om-history> 包裹观察/反思日志块。 */
|
|
13
13
|
const HISTORY_TAG = "om-history";
|
|
14
|
+
/** 压缩日志块开标签上的 tip 属性(对 AI 的提醒:本块是历史压缩产物,不要复述)。 */
|
|
15
|
+
const HISTORY_TIP = "当前块是历史消息的压缩产物,不要复述";
|
|
14
16
|
/** 宿主压缩 checkpoint 标记的 plugin 名(dsh-compaction-basic 的 COMPACT_CHECKPOINT_MARKER.plugin)。 */
|
|
15
17
|
const COMPACT_CHECKPOINT_PLUGIN = "compact";
|
|
16
18
|
//#endregion
|
|
@@ -87,14 +89,6 @@ function routedTarget(session) {
|
|
|
87
89
|
//#endregion
|
|
88
90
|
//#region src/log-index.ts
|
|
89
91
|
/**
|
|
90
|
-
* 查找 seq 在表层节点序列中的下标(不在则返回 -1)。
|
|
91
|
-
* 表层节点按日志顺序排列,用于压缩边界定位与遮蔽范围计算。
|
|
92
|
-
*/
|
|
93
|
-
function surfaceIndexOf(nodes, seq) {
|
|
94
|
-
for (let i = 0; i < nodes.length; i += 1) if (nodes[i] === seq) return i;
|
|
95
|
-
return -1;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
92
|
* 完整消息索引:按日志顺序把消息事件折叠为完整消息序列(三类,见文件头)。
|
|
99
93
|
* 工具调用结果按 source.callId 匹配其 tool-call 并入该条;未匹配的 result 独立成条(防御)。
|
|
100
94
|
*/
|
|
@@ -311,7 +305,7 @@ function buildReflectPrompt(mode) {
|
|
|
311
305
|
return [
|
|
312
306
|
mode === "fork" ? "停止一切现有任务,禁止调用任何工具,改为将当前压缩日志精简合并为一份更紧凑的日志。" : "将当前压缩日志精简合并为一份更紧凑的日志。",
|
|
313
307
|
"",
|
|
314
|
-
...mode === "new" ? ["下方的消息记录包含当前的 <om-history>
|
|
308
|
+
...mode === "new" ? ["下方的消息记录包含当前的 <om-history> 压缩日志(全部 <om-history> 块)。", "只对这份压缩日志做精简合并;不要涉及日志之外的消息。"] : ["上方的消息记录是主会话的完整历史,其中包含当前的 <om-history> 压缩日志(全部 <om-history> 块)。", "只对这份压缩日志做精简合并;不要涉及日志之外的消息。"],
|
|
315
309
|
"",
|
|
316
310
|
"【规则】",
|
|
317
311
|
"- 当前消息仅作为指令,**不得**进入日志。",
|
|
@@ -336,7 +330,7 @@ function buildReflectPrompt(mode) {
|
|
|
336
330
|
"</assistant>",
|
|
337
331
|
`</${HISTORY_TAG}>`,
|
|
338
332
|
"",
|
|
339
|
-
|
|
333
|
+
`【说明】你的合并结果会替换全部 <${HISTORY_TAG}> 块。`
|
|
340
334
|
].join("\n");
|
|
341
335
|
}
|
|
342
336
|
/** 流收集器:提取文本输出 + usage + finish(不依赖宿主 BlockAssembler,保持零运行时依赖)。 */
|
|
@@ -449,20 +443,24 @@ const HISTORY_FORMAT_NOTE = "<!-- 完整消息分三类(用户消息 / AI 文
|
|
|
449
443
|
* - 取首个 <om-history> 到最后一个 </om-history>(含两个首尾)切为日志;
|
|
450
444
|
* - 找不到、顺序颠倒(首个开标签在最后一个闭标签之后)或中间内容长度 < MIN_HISTORY_LENGTH
|
|
451
445
|
* 视为不合法(返回 null,调用方按失败重试);
|
|
452
|
-
* -
|
|
446
|
+
* - 产出后把首个开标签改写为带 tip 属性的版本(对 AI 的提醒),并在其后插入
|
|
447
|
+
* 格式说明注释(HISTORY_FORMAT_NOTE,块顶)。
|
|
453
448
|
*/
|
|
454
449
|
function extractSummaryLog(raw) {
|
|
455
|
-
/**
|
|
450
|
+
/** 开标签(模型输出格式)。 */
|
|
456
451
|
const openTag = `<${HISTORY_TAG}>`;
|
|
457
452
|
/** 闭标签。 */
|
|
458
453
|
const closeTag = `</${HISTORY_TAG}>`;
|
|
454
|
+
/** 带 tip 属性的开标签(插件产出格式)。 */
|
|
455
|
+
const openTagWithTip = `<${HISTORY_TAG} tip="${HISTORY_TIP}">`;
|
|
459
456
|
/** 首个开标签位置(无则 -1)。 */
|
|
460
457
|
const open = raw.indexOf(openTag);
|
|
461
458
|
/** 最后一个闭标签位置(无则 -1)。 */
|
|
462
459
|
const close = raw.lastIndexOf(closeTag);
|
|
463
460
|
if (open === -1 || close === -1 || close < open) return null;
|
|
464
461
|
if (raw.slice(open + openTag.length, close).trim().length < 10) return null;
|
|
465
|
-
|
|
462
|
+
/** 首个开标签改写为带 tip 版本,块顶紧跟格式说明注释。 */
|
|
463
|
+
return raw.slice(open, close + closeTag.length).replace(openTag, openTagWithTip).replace(openTagWithTip, `${openTagWithTip}\n${HISTORY_FORMAT_NOTE}`);
|
|
466
464
|
}
|
|
467
465
|
/**
|
|
468
466
|
* 直连 LLM 执行一次摘要(观察或反思),返回文本与可选 token usage。
|
|
@@ -524,9 +522,10 @@ async function runSummarySubagent(ctx, agent, instruction, contextText, maxToken
|
|
|
524
522
|
* 自动压缩(OM 观察/反思两级阈值,思路参考 Mastra Observational Memory):
|
|
525
523
|
* - 观察:未压缩消息 tokens ≥ 窗口 × thresholdRatio → 直连 ctx.llm.stream() 摘要
|
|
526
524
|
* (fork 模式复用主会话请求前缀缓存;new 模式指令作为 system)把未压缩消息压缩为
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
525
|
+
* 观察日志,作为独立的新 <om-history> 块,只精确替换被压缩的新消息区间(旧块
|
|
526
|
+
* 原地保留,多块并存按序排列;不再把旧+新合并进一条消息);
|
|
527
|
+
* - 反思:全部 <om-history> 块 tokens 合计 ≥ 窗口 × historyMergeRatio(默认 0.2)→
|
|
528
|
+
* 同上摘要调用精简合并,把整个块区段合并为一条更紧凑的摘要。
|
|
530
529
|
* 两级检查在 pre-step 阻塞串行执行(先反思后观察),避免压缩失败或重复压缩。
|
|
531
530
|
*
|
|
532
531
|
* 压缩结果写入宿主 compaction/* 生命周期事件(compaction/start → compaction/summary →
|
|
@@ -562,8 +561,11 @@ function historyTextOf(event) {
|
|
|
562
561
|
if (source.plugin !== "compact" && source.plugin !== "dsh-plugin-om") return void 0;
|
|
563
562
|
/** 消息纯文本。 */
|
|
564
563
|
const text = blocksToText(event.data.content);
|
|
565
|
-
/**
|
|
566
|
-
|
|
564
|
+
/** 日志文本起点:优先块前换行定位(旧格式注入前缀句含行内 <om-history>,裸 indexOf 会抢先
|
|
565
|
+
* 命中前缀里的标签),回退首个开标签;无则整段视为日志。 */
|
|
566
|
+
const tag = `<${HISTORY_TAG}>`;
|
|
567
|
+
const newlineStart = text.indexOf(`\n${tag}`);
|
|
568
|
+
const start = newlineStart === -1 ? text.indexOf(tag) : newlineStart + 1;
|
|
567
569
|
return start === -1 ? text : text.slice(start);
|
|
568
570
|
}
|
|
569
571
|
/**
|
|
@@ -583,19 +585,6 @@ function measureUncompressedTokens(session, meter) {
|
|
|
583
585
|
}
|
|
584
586
|
return total;
|
|
585
587
|
}
|
|
586
|
-
/** 定位日志中最后一次 <om-history> 压缩日志(内文 + 事件 seq);无则 undefined。 */
|
|
587
|
-
function findLatestHistory(session) {
|
|
588
|
-
/** 会话事件(仅追加,从后向前扫描)。 */
|
|
589
|
-
const events = session.events;
|
|
590
|
-
for (let seq = events.length - 1; seq >= 0; seq -= 1) {
|
|
591
|
-
/** <om-history> 内文(非压缩日志消息时为 undefined)。 */
|
|
592
|
-
const text = historyTextOf(events[seq]);
|
|
593
|
-
if (text !== void 0) return {
|
|
594
|
-
text,
|
|
595
|
-
seq
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
588
|
/**
|
|
600
589
|
* 判定表层节点 seq 之后的切点是否 tool-call/result 配对平衡(与宿主
|
|
601
590
|
* dsh-compaction 的 toolPairingBalancedAfter 同语义):按表层顺序折叠未闭合的
|
|
@@ -646,22 +635,24 @@ function computeCompressRange(session, tailCount) {
|
|
|
646
635
|
};
|
|
647
636
|
}
|
|
648
637
|
/**
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
638
|
+
* 收集表层中全部 <om-history> 压缩日志(内文 + seq,按表层顺序)。
|
|
639
|
+
* 压缩日志消息始终位于表层头部连续区段(每次观察在旧块之后替换新消息区间,
|
|
640
|
+
* 反思把整个块区段合并为一条),因此表层中所有压缩日志即头部区段;
|
|
641
|
+
* 头部区段后即结束扫描(不遍历尾部消息)。
|
|
652
642
|
*/
|
|
653
|
-
function
|
|
654
|
-
/**
|
|
655
|
-
|
|
656
|
-
for (const seq of
|
|
657
|
-
/** <om-history>
|
|
643
|
+
function listHistoryBlocks(session) {
|
|
644
|
+
/** 收集结果(按表层顺序)。 */
|
|
645
|
+
const out = [];
|
|
646
|
+
for (const seq of session.surface.nodes) {
|
|
647
|
+
/** <om-history> 内文(非压缩日志消息时为 undefined)。 */
|
|
658
648
|
const text = historyTextOf(session.events[seq]);
|
|
659
|
-
if (text
|
|
649
|
+
if (text === void 0) break;
|
|
650
|
+
out.push({
|
|
660
651
|
text,
|
|
661
652
|
seq
|
|
662
|
-
};
|
|
653
|
+
});
|
|
663
654
|
}
|
|
664
|
-
return
|
|
655
|
+
return out;
|
|
665
656
|
}
|
|
666
657
|
/** 当前打开中的 turn 号(最近 turn/start 且未被 turn/end 关闭);无则 null(跨轮次场景)。 */
|
|
667
658
|
function openTurnOf(session) {
|
|
@@ -709,17 +700,14 @@ function appendCompactionEnd(session, lifecycle, error) {
|
|
|
709
700
|
}
|
|
710
701
|
/** 追加 <om-history> 压缩日志消息(surfaceOp 替换遮蔽区间,source 为宿主 checkpoint 标记)。 */
|
|
711
702
|
function appendHistoryMessage(session, content, sourceEventSeqs, surfaceOp, compactionId) {
|
|
712
|
-
/**
|
|
703
|
+
/** 压缩替换消息(内容即 <om-history> 标签块,不再附加前缀句;对 AI 的提醒在块开标签 tip 属性上;
|
|
704
|
+
* source 标记宿主 checkpoint 供 UI 关联)。 */
|
|
713
705
|
const message = {
|
|
714
706
|
id: uuid(),
|
|
715
707
|
role: "user",
|
|
716
708
|
content: [{
|
|
717
709
|
type: "text",
|
|
718
|
-
text:
|
|
719
|
-
"以下是过往会话的压缩日志(<om-history>),为已确立背景:直接继续,不要复述。",
|
|
720
|
-
"",
|
|
721
|
-
content
|
|
722
|
-
].join("\n")
|
|
710
|
+
text: content
|
|
723
711
|
}],
|
|
724
712
|
source: {
|
|
725
713
|
kind: "plugin",
|
|
@@ -733,8 +721,8 @@ function appendHistoryMessage(session, content, sourceEventSeqs, surfaceOp, comp
|
|
|
733
721
|
});
|
|
734
722
|
}
|
|
735
723
|
/**
|
|
736
|
-
*
|
|
737
|
-
*
|
|
724
|
+
* 反思:全部 <om-history> 块 tokens 合计 ≥ 窗口 × historyMergeRatio 时,摘要调用
|
|
725
|
+
* 精简合并,把整个块区段替换为一条更紧凑的摘要。失败不产生部分替换。
|
|
738
726
|
*/
|
|
739
727
|
async function reflectPass(ctx, agent, config, window, target, signal) {
|
|
740
728
|
/** 当前会话。 */
|
|
@@ -742,28 +730,32 @@ async function reflectPass(ctx, agent, config, window, target, signal) {
|
|
|
742
730
|
/** 插件日志门面。 */
|
|
743
731
|
const logger = makeLogger(ctx, config.debug);
|
|
744
732
|
logger.step(`反思检查(窗口 ${window} × historyMergeRatio ${config.historyMergeRatio})`);
|
|
745
|
-
/**
|
|
746
|
-
const
|
|
747
|
-
if (
|
|
733
|
+
/** 全部 <om-history> 块(头部连续区段;无则跳过)。 */
|
|
734
|
+
const blocks = listHistoryBlocks(session);
|
|
735
|
+
if (blocks.length === 0) {
|
|
748
736
|
logger.step("反思:无 <om-history> 压缩日志,跳过");
|
|
749
737
|
return;
|
|
750
738
|
}
|
|
751
739
|
/** 反思阈值(窗口 × historyMergeRatio 向下取整)。 */
|
|
752
740
|
const threshold = Math.floor(window * config.historyMergeRatio);
|
|
753
|
-
/**
|
|
754
|
-
const tokens = estimateTextTokens(
|
|
741
|
+
/** 全部块 token 估算合计(摘要总长)。 */
|
|
742
|
+
const tokens = blocks.reduce((total, block) => total + estimateTextTokens(block.text), 0);
|
|
755
743
|
if (tokens < threshold) {
|
|
756
744
|
logger.step(`反思:摘要 ${tokens} tokens < 阈值 ${threshold},跳过`);
|
|
757
745
|
return;
|
|
758
746
|
}
|
|
759
|
-
logger.step(`反思:摘要 ${tokens} tokens ≥ 阈值 ${threshold}
|
|
760
|
-
/**
|
|
761
|
-
|
|
762
|
-
|
|
747
|
+
logger.step(`反思:摘要 ${tokens} tokens ≥ 阈值 ${threshold},触发精简合并(${blocks.length} 个块)`);
|
|
748
|
+
/** 块区段首尾(listHistoryBlocks 只收集表层节点,天然在表层)。 */
|
|
749
|
+
const first = blocks[0];
|
|
750
|
+
const last = blocks[blocks.length - 1];
|
|
751
|
+
if (first === void 0 || last === void 0) {
|
|
752
|
+
logger.step("反思:块区段缺失,跳过");
|
|
763
753
|
return;
|
|
764
754
|
}
|
|
755
|
+
/** 被替换块区段的 seq 列表。 */
|
|
756
|
+
const blockSeqs = blocks.map((block) => block.seq);
|
|
765
757
|
/** 反思摘要结果(null 表示失败/跳过)。 */
|
|
766
|
-
const summaryResult = await runSummarySubagent(ctx, agent, `${REFLECTOR_PERSONA}\n\n${buildReflectPrompt(config.summaryMode)}`, config.summaryMode === "new" ?
|
|
758
|
+
const summaryResult = await runSummarySubagent(ctx, agent, `${REFLECTOR_PERSONA}\n\n${buildReflectPrompt(config.summaryMode)}`, config.summaryMode === "new" ? blocks.map((block) => block.text).join("\n") : void 0, config.compressMaxTokens, config.summaryMode, 0, target, config.debug, signal);
|
|
767
759
|
if (summaryResult === null || summaryResult.text.trim().length === 0) {
|
|
768
760
|
logger.step("反思:摘要调用失败/无输出,不产生替换");
|
|
769
761
|
return;
|
|
@@ -784,25 +776,25 @@ async function reflectPass(ctx, agent, config, window, target, signal) {
|
|
|
784
776
|
lifecycle,
|
|
785
777
|
summary: report,
|
|
786
778
|
shadowedRange: {
|
|
787
|
-
start:
|
|
788
|
-
end:
|
|
779
|
+
start: first.seq,
|
|
780
|
+
end: last.seq
|
|
789
781
|
},
|
|
790
|
-
shadowedSeqs:
|
|
782
|
+
shadowedSeqs: blockSeqs,
|
|
791
783
|
shadowedTokenCount: tokens,
|
|
792
784
|
provider: target.provider,
|
|
793
785
|
model: target.model,
|
|
794
786
|
maxTokens: config.compressMaxTokens,
|
|
795
787
|
...summaryResult.usage === void 0 ? {} : { usage: summaryResult.usage }
|
|
796
788
|
});
|
|
797
|
-
logger.step("
|
|
798
|
-
appendHistoryMessage(session, report, [summarySeq,
|
|
789
|
+
logger.step("反思提交:替换整个 <om-history> 块区段为合并摘要");
|
|
790
|
+
appendHistoryMessage(session, report, [summarySeq, ...blockSeqs], {
|
|
799
791
|
op: "replace",
|
|
800
|
-
start:
|
|
801
|
-
end:
|
|
792
|
+
start: first.seq,
|
|
793
|
+
end: last.seq
|
|
802
794
|
}, lifecycle.compactionId);
|
|
803
795
|
logger.step("反思提交:追加 compaction/end");
|
|
804
796
|
appendCompactionEnd(session, lifecycle);
|
|
805
|
-
logger.info(`反思完成(摘要 ${tokens} tokens ≥ 阈值 ${threshold}
|
|
797
|
+
logger.info(`反思完成(摘要 ${tokens} tokens ≥ 阈值 ${threshold},合并 ${blocks.length} 个块为一条)`);
|
|
806
798
|
} catch (error) {
|
|
807
799
|
/** 提交失败信息(统一字符串)。 */
|
|
808
800
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -838,32 +830,45 @@ async function observePass(ctx, agent, config, window, tailCount, target, signal
|
|
|
838
830
|
return;
|
|
839
831
|
}
|
|
840
832
|
logger.step(`观察:压缩区间 [${range.start}..${range.end}],遮蔽 ${range.shadowedSeqs.length} 个表层节点`);
|
|
841
|
-
/**
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
833
|
+
/** 当前表层节点(按日志顺序)。 */
|
|
834
|
+
const surface = [...session.surface.nodes];
|
|
835
|
+
/** 头部 <om-history> 块区段(旧摘要;多块并存:保留不替换、不进新消息遮蔽)。 */
|
|
836
|
+
const blocks = listHistoryBlocks(session);
|
|
837
|
+
/** 区间终点在表层中的位置。 */
|
|
838
|
+
const endIdx = surface.indexOf(range.end);
|
|
839
|
+
/** 被替换的新消息区段起点(头部块区段之后)。 */
|
|
840
|
+
const startIdx = blocks.length;
|
|
841
|
+
/** 实际被替换的新消息表层节点(旧块不进入遮蔽、不被重写)。 */
|
|
842
|
+
const replaceSeqs = surface.slice(startIdx, endIdx + 1);
|
|
843
|
+
/** 替换区间起点(首个新消息节点;区间内全部为块时无新消息可压缩)。 */
|
|
844
|
+
const replaceStart = replaceSeqs[0];
|
|
845
|
+
if (replaceStart === void 0) {
|
|
846
|
+
logger.step("观察:区间内无新消息(全部为压缩日志块),跳过");
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
/** 实际保留的参考尾部条数(配对回退可能多于 tailCount;fork 输入从尾部之前实际截断)。 */
|
|
850
|
+
const actualTailCount = surface.length - range.shadowedSeqs.length;
|
|
851
|
+
/** 被替换 seq 集合(计算新消息起始 index)。 */
|
|
852
|
+
const shadowedSet = new Set(replaceSeqs);
|
|
846
853
|
/** 压缩区间内第一个完整消息的 index(新消息起始编号;区间内无完整消息则 0)。 */
|
|
847
854
|
const startIndex = indexCompleteMessages(session).find((cm) => cm.seqs.every((seq) => shadowedSet.has(seq)))?.index ?? 0;
|
|
848
|
-
logger.step(
|
|
855
|
+
logger.step(`观察:保留旧块 ${blocks.length} 条,替换新消息 [${replaceSeqs[0]}..${range.end}](${replaceSeqs.length} 条),实际保留尾部 ${actualTailCount} 条(不压缩、不进日志),新消息起始 index ${startIndex}`);
|
|
849
856
|
/** 观察指令(persona + 规则主体)。 */
|
|
850
857
|
const prompt = buildObservePrompt({
|
|
851
858
|
startIndex,
|
|
852
|
-
hasOldHistory:
|
|
859
|
+
hasOldHistory: blocks.length > 0,
|
|
853
860
|
mode: config.summaryMode
|
|
854
861
|
});
|
|
855
862
|
/** 观察摘要结果(null 表示失败/跳过)。 */
|
|
856
|
-
const summaryResult = await runSummarySubagent(ctx, agent, `${OBSERVER_PERSONA}\n\n${prompt}`, config.summaryMode === "new" ? renderMessages(session,
|
|
863
|
+
const summaryResult = await runSummarySubagent(ctx, agent, `${OBSERVER_PERSONA}\n\n${prompt}`, config.summaryMode === "new" ? renderMessages(session, replaceSeqs) : void 0, config.compressMaxTokens, config.summaryMode, actualTailCount, target, config.debug, signal);
|
|
857
864
|
if (summaryResult === null || summaryResult.text.trim().length === 0) {
|
|
858
865
|
logger.step("观察:摘要调用失败/无输出,不产生替换");
|
|
859
866
|
return;
|
|
860
867
|
}
|
|
861
|
-
/**
|
|
868
|
+
/** 观察摘要文本(独立新块;旧块保留,不再合并)。 */
|
|
862
869
|
const report = summaryResult.text;
|
|
863
|
-
/**
|
|
864
|
-
const
|
|
865
|
-
/** 被遮蔽表层节点的 token 估算合计。 */
|
|
866
|
-
const shadowedTokenCount = range.shadowedSeqs.reduce((total, seq) => {
|
|
870
|
+
/** 被替换表层节点的 token 估算合计(仅新消息区间)。 */
|
|
871
|
+
const shadowedTokenCount = replaceSeqs.reduce((total, seq) => {
|
|
867
872
|
/** 当前表层事件。 */
|
|
868
873
|
const event = session.events[seq];
|
|
869
874
|
/** 事件对应的消息(用于 token 估算)。 */
|
|
@@ -882,27 +887,27 @@ async function observePass(ctx, agent, config, window, tailCount, target, signal
|
|
|
882
887
|
/** compaction/summary 事件 seq(承担影子价格认领,紧随其后的替换消息消费)。 */
|
|
883
888
|
const summarySeq = appendCompactionSummary(session, {
|
|
884
889
|
lifecycle,
|
|
885
|
-
summary:
|
|
890
|
+
summary: report,
|
|
886
891
|
shadowedRange: {
|
|
887
|
-
start:
|
|
892
|
+
start: replaceStart,
|
|
888
893
|
end: range.end
|
|
889
894
|
},
|
|
890
|
-
shadowedSeqs:
|
|
895
|
+
shadowedSeqs: replaceSeqs,
|
|
891
896
|
shadowedTokenCount,
|
|
892
897
|
provider: target.provider,
|
|
893
898
|
model: target.model,
|
|
894
899
|
maxTokens: config.compressMaxTokens,
|
|
895
900
|
...summaryResult.usage === void 0 ? {} : { usage: summaryResult.usage }
|
|
896
901
|
});
|
|
897
|
-
logger.step("
|
|
898
|
-
appendHistoryMessage(session,
|
|
902
|
+
logger.step("观察提交:替换被压缩新消息区间为 <om-history>(旧块保留)");
|
|
903
|
+
appendHistoryMessage(session, report, [summarySeq, ...replaceSeqs], {
|
|
899
904
|
op: "replace",
|
|
900
|
-
start:
|
|
905
|
+
start: replaceStart,
|
|
901
906
|
end: range.end
|
|
902
907
|
}, lifecycle.compactionId);
|
|
903
908
|
logger.step("观察提交:追加 compaction/end");
|
|
904
909
|
appendCompactionEnd(session, lifecycle);
|
|
905
|
-
logger.info(`观察压缩完成(未压缩 ${uncompressedTokens} tokens ≥ 阈值 ${threshold}
|
|
910
|
+
logger.info(`观察压缩完成(未压缩 ${uncompressedTokens} tokens ≥ 阈值 ${threshold},替换 ${replaceSeqs.length} 个表层节点,约 ${shadowedTokenCount} tokens)`);
|
|
906
911
|
} catch (error) {
|
|
907
912
|
/** 提交失败信息(统一字符串)。 */
|
|
908
913
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1196,9 +1201,9 @@ function cosineSimilarity(a, b) {
|
|
|
1196
1201
|
*/
|
|
1197
1202
|
/** 默认配置(冻结对象,resolveConfig 合并的基底;debug 缺省值在解析时按 NODE_ENV 判定)。 */
|
|
1198
1203
|
const DEFAULT_CONFIG = Object.freeze({
|
|
1199
|
-
thresholdRatio: .
|
|
1204
|
+
thresholdRatio: .1,
|
|
1200
1205
|
historyMergeRatio: .2,
|
|
1201
|
-
compressMaxTokens:
|
|
1206
|
+
compressMaxTokens: 1e4,
|
|
1202
1207
|
tailMessageCount: 10,
|
|
1203
1208
|
summaryMode: "fork",
|
|
1204
1209
|
debug: false,
|
package/dist/summarize.d.ts
CHANGED
|
@@ -65,7 +65,8 @@ export declare const HISTORY_FORMAT_NOTE = "<!-- \u5B8C\u6574\u6D88\u606F\u5206\
|
|
|
65
65
|
* - 取首个 <om-history> 到最后一个 </om-history>(含两个首尾)切为日志;
|
|
66
66
|
* - 找不到、顺序颠倒(首个开标签在最后一个闭标签之后)或中间内容长度 < MIN_HISTORY_LENGTH
|
|
67
67
|
* 视为不合法(返回 null,调用方按失败重试);
|
|
68
|
-
* -
|
|
68
|
+
* - 产出后把首个开标签改写为带 tip 属性的版本(对 AI 的提醒),并在其后插入
|
|
69
|
+
* 格式说明注释(HISTORY_FORMAT_NOTE,块顶)。
|
|
69
70
|
*/
|
|
70
71
|
export declare function extractSummaryLog(raw: string): string | null;
|
|
71
72
|
/** 单次摘要最多尝试次数(首次 + 失败重试,总上限;失败/未完成均重试)。 */
|
package/dist/summarize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../src/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,YAAY,CAAC;AAErD,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,yRACqB,CAAC;AAEnD,wCAAwC;AACxC,eAAO,MAAM,iBAAiB,mQAC4B,CAAC;AAE3D,gDAAgD;AAChD,MAAM,MAAM,oBAAoB,GAAG;IACjC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAuDxE;AAED;qEACqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA+C5D;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB,CAAC;AA4CF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAehF;AA4DD,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,4hBAC+J,CAAC;AAEhM
|
|
1
|
+
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../src/summarize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,YAAY,CAAC;AAErD,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,yRACqB,CAAC;AAEnD,wCAAwC;AACxC,eAAO,MAAM,iBAAiB,mQAC4B,CAAC;AAE3D,gDAAgD;AAChD,MAAM,MAAM,oBAAoB,GAAG;IACjC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAuDxE;AAED;qEACqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA+C5D;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB,CAAC;AA4CF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAehF;AA4DD,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,4hBAC+J,CAAC;AAEhM;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB5D;AAED,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAKtC;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA+EvC"}
|