@wingsbutterfly/dsh-rtk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/README.zh.md +211 -0
  5. package/THIRD_PARTY_NOTICES.md +51 -0
  6. package/docs/assets/how-it-works.svg +47 -0
  7. package/docs/verification.md +279 -0
  8. package/lib/command.d.ts +41 -0
  9. package/lib/command.d.ts.map +1 -0
  10. package/lib/command.js +137 -0
  11. package/lib/command.js.map +1 -0
  12. package/lib/compact/build.d.ts +15 -0
  13. package/lib/compact/build.d.ts.map +1 -0
  14. package/lib/compact/build.js +140 -0
  15. package/lib/compact/build.js.map +1 -0
  16. package/lib/compact/detect.d.ts +22 -0
  17. package/lib/compact/detect.d.ts.map +1 -0
  18. package/lib/compact/detect.js +54 -0
  19. package/lib/compact/detect.js.map +1 -0
  20. package/lib/compact/dsh-result.d.ts +49 -0
  21. package/lib/compact/dsh-result.d.ts.map +1 -0
  22. package/lib/compact/dsh-result.js +84 -0
  23. package/lib/compact/dsh-result.js.map +1 -0
  24. package/lib/compact/git.d.ts +21 -0
  25. package/lib/compact/git.d.ts.map +1 -0
  26. package/lib/compact/git.js +197 -0
  27. package/lib/compact/git.js.map +1 -0
  28. package/lib/compact/index.d.ts +39 -0
  29. package/lib/compact/index.d.ts.map +1 -0
  30. package/lib/compact/index.js +236 -0
  31. package/lib/compact/index.js.map +1 -0
  32. package/lib/compact/linter.d.ts +12 -0
  33. package/lib/compact/linter.d.ts.map +1 -0
  34. package/lib/compact/linter.js +118 -0
  35. package/lib/compact/linter.js.map +1 -0
  36. package/lib/compact/search.d.ts +16 -0
  37. package/lib/compact/search.d.ts.map +1 -0
  38. package/lib/compact/search.js +67 -0
  39. package/lib/compact/search.js.map +1 -0
  40. package/lib/compact/source.d.ts +22 -0
  41. package/lib/compact/source.d.ts.map +1 -0
  42. package/lib/compact/source.js +224 -0
  43. package/lib/compact/source.js.map +1 -0
  44. package/lib/compact/test-output.d.ts +12 -0
  45. package/lib/compact/test-output.d.ts.map +1 -0
  46. package/lib/compact/test-output.js +168 -0
  47. package/lib/compact/test-output.js.map +1 -0
  48. package/lib/compact/text.d.ts +22 -0
  49. package/lib/compact/text.d.ts.map +1 -0
  50. package/lib/compact/text.js +87 -0
  51. package/lib/compact/text.js.map +1 -0
  52. package/lib/config.d.ts +243 -0
  53. package/lib/config.d.ts.map +1 -0
  54. package/lib/config.js +183 -0
  55. package/lib/config.js.map +1 -0
  56. package/lib/index.d.ts +46 -0
  57. package/lib/index.d.ts.map +1 -0
  58. package/lib/index.js +405 -0
  59. package/lib/index.js.map +1 -0
  60. package/lib/metrics.d.ts +35 -0
  61. package/lib/metrics.d.ts.map +1 -0
  62. package/lib/metrics.js +51 -0
  63. package/lib/metrics.js.map +1 -0
  64. package/lib/rtk-executable.d.ts +51 -0
  65. package/lib/rtk-executable.d.ts.map +1 -0
  66. package/lib/rtk-executable.js +75 -0
  67. package/lib/rtk-executable.js.map +1 -0
  68. package/lib/rtk-rewrite.d.ts +88 -0
  69. package/lib/rtk-rewrite.d.ts.map +1 -0
  70. package/lib/rtk-rewrite.js +150 -0
  71. package/lib/rtk-rewrite.js.map +1 -0
  72. package/lib/runtime-guard.d.ts +31 -0
  73. package/lib/runtime-guard.d.ts.map +1 -0
  74. package/lib/runtime-guard.js +32 -0
  75. package/lib/runtime-guard.js.map +1 -0
  76. package/package.json +82 -0
  77. package/scripts/link-dsh.mjs +135 -0
  78. package/src/command.ts +174 -0
  79. package/src/compact/build.ts +154 -0
  80. package/src/compact/detect.ts +54 -0
  81. package/src/compact/dsh-result.ts +99 -0
  82. package/src/compact/git.ts +209 -0
  83. package/src/compact/index.ts +284 -0
  84. package/src/compact/linter.ts +126 -0
  85. package/src/compact/search.ts +73 -0
  86. package/src/compact/source.ts +244 -0
  87. package/src/compact/test-output.ts +184 -0
  88. package/src/compact/text.ts +86 -0
  89. package/src/config.ts +263 -0
  90. package/src/index.ts +431 -0
  91. package/src/metrics.ts +84 -0
  92. package/src/rtk-executable.ts +117 -0
  93. package/src/rtk-rewrite.ts +179 -0
  94. package/src/runtime-guard.ts +44 -0
@@ -0,0 +1,279 @@
1
+ # 验收证据
2
+
3
+ 本文件记录 `dsh-rtk` 的**闸门留痕**:每条关于框架行为的断言都配一条可复现命令与实测输出;每个回归测试都留下一次「修复前失败」的记录。
4
+
5
+ 日期:2026-09-12 · 环境:Node v26.7.0 · rtk 0.48.0 · DSH 0.1.5-rc.1
6
+
7
+ ---
8
+
9
+ ## 1. 事实核对(动手前完成)
10
+
11
+ 这些断言决定了架构选择。每一条都用命令实测过,不是从文档推断的。
12
+
13
+ | 断言 | 命令 | 实测输出 |
14
+ |---|---|---|
15
+ | `rtk rewrite` 存在并输出等价命令 | `rtk rewrite "ls -la"` | `rtk ls -la`,exit 3 |
16
+ | rtk 的警告写 stderr,stdout 干净 | `rtk rewrite "ls -la" 2>/dev/null` | 只有命令本身(若警告混进 stdout,改写结果会被污染) |
17
+ | rewrite 退出码语义 | `for c in "ls -la" "echo hi" "git status"; do rtk rewrite "$c"; echo $?; done` | `ls -la`→3、`git status`→3、`echo hi`→1 且无输出 |
18
+ | rewrite 足够快,可放在每次 bash 调用前 | `time rtk rewrite "git status"` | real 0m0.026s |
19
+ | 复合命令与 env 前缀能正确改写 | `rtk rewrite "git status && cargo test"` / `rtk rewrite "FOO=1 ls -la"` | `rtk git status && rtk cargo test` / `FOO=1 rtk ls -la` |
20
+ | `tools/post-execute` 可以替换工具结果内容 | 动态 Cordis 插件:监听 post-execute,剥离 ANSI 并追加诊断行 | `[rtk-probe] call#1 blocks=1 ansiStripped=1 ...` 出现在模型可见输出中 |
21
+ | post-execute 的 `next()` 返回值**不带** content | 同上 | `decisionHadContent=false` — 原始内容必须从 `result.content` 取 |
22
+ | `tools/execute` 可以替换 `exec.arguments` 且真正生效 | 动态 Cordis 插件:把命令替换为 `echo "RTK-REWRITE-PROVEN-1"` | 实际执行的是替换后的命令,原命令未运行 |
23
+ | `tools/pre-execute` 不能改写参数 | `@deepseek-ai/dsh-tools` 类型注释 + `PreToolDecision` 定义 | 决策只有 `allow`/`deny`/`ask`;注释写明 "Input rewriting is excluded because arguments are already logged and presented" |
24
+ | `bash` 工具不在全局层,而在 preset 层 | 动态插件 `ctx.tools.get('bash')` | `baseFound: false`;读 `cordis` preset 组合文件确认 `dsh-tool-bash` 是 preset 的一行 |
25
+ | preset 的 standing scope 是每个会话 scope 的祖先 | `@deepseek-ai/dsh-scope` 的 `bindScopeParent` + `chainLayers` 实现 | 祖先层的 listener 能收到后代的事件(`scopeTarget`: "admits tagged listeners for a matching key or any of its ancestors") |
26
+
27
+ **由这些断言推出的两处架构决策:**
28
+
29
+ 1. **命令重写只能走 `tools/execute`。** pre-execute 被设计为不可改参数,`ctx.tools.register` 在同一 layer 内重复注册同名工具会失败(preset 的所有行共享一个 layer),而从 preset 内部又拿不到 agent 自己的 scope。around-dispatch 是唯一能改「实际执行什么」的接缝。
30
+ 2. **插件是 agent 平面的一行,不发布服务。** 因此不需要 `isolate` realm,也不该放 host 组合。
31
+
32
+ ---
33
+
34
+ ## 2. 验收标准表
35
+
36
+ | # | 标准 | 判定命令 | 期望 | 实测 |
37
+ |---|---|---|---|---|
38
+ | 1 | 类型检查通过(用真实 harness 类型) | `tsc -p tsconfig.json --noEmit` | 无输出、exit 0 | PASS |
39
+ | 2 | 构建产出 `lib/` | `tsc -p tsconfig.json && ls lib/index.js` | 文件存在 | PASS |
40
+ | 3 | 全部测试通过 | `node --run test`(先构建再测) | `pass 92 / fail 0` | PASS |
41
+ | 4 | 模块导出符合 cordis 插件约定 | `node -e "import('./lib/index.js').then(m=>console.log(Object.keys(m)))"` | `Config, apply, inject, name`(与 `dsh-tool-bash` 同约定) | PASS |
42
+ | 5 | 模块能被宿主解析 | `~/.dsh/node_modules/dsh-rtk` 软链存在;插件**实际生效**(见第 6 节) | 组合与加载均成功 | PASS |
43
+ | 6 | preset 方案当初能被真实组合引擎挂载 | 动态插件调用 `agentPresets.standingKeyFor('rtk')` | `MOUNT OK`(修复前、修复后各一次) | PASS(该平面最终被 host 取代,见第 5 节) |
44
+ | 7 | host patch 语法正确并被组合 | `dsh --profile web --dump-config \| grep -A3 "id: rtk"` | 组合树中出现 `- id: rtk / name: dsh-rtk` | PASS(**最终采用**) |
45
+ | 8 | bash 真被改写(端到端) | host 平面重启后运行 `ls -la <dir>`(**不带管道**) | 输出为 rtk 紧凑格式 | **PASS**——见第 6 节 |
46
+
47
+ ---
48
+
49
+ ## 3. 验红记录(测试确实能失败)
50
+
51
+ ### 验红 #1 — 移除退出码标记保护
52
+
53
+ 注入方式:把 `compactBashText` 中 `renderBashResult({ ..., markers: parts.markers })` 的 `markers` 改为 `[]`。
54
+
55
+ ```
56
+ ✖ compaction preserves the harness contract
57
+ ℹ tests 50
58
+ ℹ pass 44
59
+ ℹ fail 6
60
+ ✖ failing tests:
61
+ ✖ keeps the exit-code marker through a git rewrite
62
+ ✖ keeps a non-zero exit marker
63
+ ✖ keeps a stderr section alongside the marker
64
+ ✖ never inflates a result
65
+ ✖ strips ANSI codes but keeps the marker
66
+ ✖ refuses to cut through a marker when truncating
67
+ ```
68
+
69
+ 恢复后复测:`tests 50 / pass 50 / fail 0`。
70
+
71
+ ### 验红 #2 — 移除「永不增大」守卫
72
+
73
+ 注入方式:把 `if (rendered.length >= text.length) return { text, techniques: [] }` 改为 `if (false) ...`。
74
+
75
+ 第一次跑**全部通过** —— 说明原用例(`'x\n[exit code: 0]'`)根本没触发这条守卫(该输入不匹配 git status 的原始格式,压缩器本来就返回 null)。这是一处**真实的测试盲区**,被验红抓出来了。
76
+
77
+ 修正用例为 `'## main\n[exit code: 0]'`(合法 status 正文,其摘要 `Branch: main` 更长,只有守卫能保住原文)后重跑:
78
+
79
+ ```
80
+ ✖ never inflates a result
81
+ ℹ tests 50
82
+ ℹ pass 49
83
+ ℹ fail 1
84
+ ```
85
+
86
+ 恢复后复测:`tests 50 / pass 50 / fail 0`。
87
+
88
+ > 两次验红都在 `test/compact.test.ts` 上做,因为这两条正是默认开启路径上的不变量。验红当时套件为 50 项;补入 `integration.test.ts`(`apply()` 接线 + 真实 rtk 二进制)与 `plugin-surface.test.ts`(可执行解析、运行时守卫、统计、`/rtk` 全部子命令)后为 84;独立复核后又补入 `regressions.test.ts` 的 8 项,现为 92。**注**:这些注入验红改的是 `lib/`(测试的目标是构建产物),`src/` 的同类改动不重新构建就测不出来——这一点由复核者指出(第 8 节 D4)并已修复。
89
+
90
+ ---
91
+
92
+ ## 4. 不变量
93
+
94
+ | 不变量 | 类型 | 实测值 |
95
+ |---|---|---|
96
+ | `bash` 结果的状态标记在压缩后原样保留 | 测试 | `[exit code: N]`、`[stderr]`、`[timed out after …]`、`[sandbox: …]`、截断提示,均有断言覆盖 |
97
+ | 压缩结果永不大于原文 | 测试 | 每条技术路径都走「不短于原文就放弃」守卫;命门用例见验红 #2。**此条曾被违反**:`read` + 源码过滤路径的守卫未把 banner 计入长度,实测 +31 字符。已修复并新增回归用例,见第 8 节 D1 |
98
+ | 压缩失败不影响工具调用 | 测试 | `never breaks a tool call when compaction throws`:content 非数组时按原样 accept |
99
+ | 改写只作用于单次调用,随后恢复原参数 | 测试 | `rewrites a supported command and restores the original afterwards` 断言派发后 `exec.arguments.command === 'git status'` |
100
+ | rtk 不可用时命令原样执行 | 测试 | `degrades to the original command when rtk cannot be resolved`(指向不存在的二进制) |
101
+ | 配置数值被钳制在公布区间 | 测试 | `clamps truncation budgets to their published bounds`(maxChars 1000–200000、maxLines 40–4000) |
102
+ | 关闭总开关后行为完全惰性 | 测试 | `does nothing at all when disabled` |
103
+
104
+ ---
105
+
106
+ ## 5. 安装方式的取舍(一次回滚留痕)
107
+
108
+ 最初按「与 pi-rtk-optimizer 体验一致(装了全局生效)」把插件装到 host 平面:`~/.dsh/cordis.patch.yml` 追加 `- id: rtk`。`--dump-config` 证实该行被正确组合。
109
+
110
+ 但随后 `standingKeyFor('rtk')` 的 mount 验证失败:
111
+
112
+ ```
113
+ - failed to apply loader entry rtk (dsh-rtk): settings namespace "dsh-rtk" is already registered
114
+ - failed to apply loader entry tool-cordis (@deepseek-ai/dsh-tool-cordis): Host Cordis inspect provider "Service" is already registered
115
+ ```
116
+
117
+ 两条错误,两种修法:
118
+
119
+ 1. **`settings` 命名空间是进程级全局的** —— host 行与 preset 行不能同时存在。插件内已改为 `try/catch` 降级:注册失败的实例改为通过 `settings/updated` 事件跟随已注册的实例,**配置便利性绝不能让整个 preset 挂载失败**。
120
+ 2. **`tool-cordis` 注册进程级 Cordis inspect provider** —— `cordis` preset 的副本会与正在运行的 `cordis` 会话冲突。已从 `rtk` preset 副本中移除该行,并留注释说明需要自我修改时切回 `cordis` preset。
121
+
122
+ **最终处置(用户决定):采用 host 平面。** 用户希望「装上就一直生效」,与 `pi-rtk-optimizer` 作为全局扩展的体验一致;preset 方案需要每次新建会话手动选择。于是重新写入 host patch,并删除已被取代的 `rtk` preset 目录。
123
+
124
+ 中间为排查冲突曾回滚过一次 host patch,回滚后重新 mount preset 得到:
125
+
126
+ ```
127
+ [rtk-diag] rtkNamespaceRegistered=false namespaces=[...] MOUNT OK; key=[object Object]
128
+ ```
129
+
130
+ `rtkNamespaceRegistered=false` 同时反证了 host 行从未真正激活(`ls` 也一直是原生格式),因此上述冲突来自首次失败的 mount 残留,而非 host 行。
131
+
132
+ ---
133
+
134
+ ## 6. 端到端验证(PASS)
135
+
136
+ **最终形态:host 平面。** 插件行写在 `~/.dsh/cordis.patch.yml`,模块经 `~/.dsh/node_modules/dsh-rtk` 软链解析,宿主重启后对**所有会话**生效。
137
+
138
+ ### 6.1 配置被正确组合
139
+
140
+ ```
141
+ $ dsh --profile web --dump-config | grep -A5 "^- id: rtk"
142
+ 647:- id: rtk
143
+ 648- name: dsh-rtk
144
+ 649- config:
145
+ 650- enabled: true
146
+ 651- mode: rewrite
147
+ ```
148
+
149
+ ### 6.2 命令确实被改写(决定性证据)
150
+
151
+ 重启宿主后,模型发出 `ls -la $HOME/.agents/`(**不带管道**),实际返回:
152
+
153
+ ```
154
+ 755 .git/
155
+ 755 skills/
156
+ 644 .gitignore 796B
157
+ 644 .skill-lock.json 15.6K
158
+ 644 AGENTS.md 5.2K
159
+ 600 doctor.py 9.1K
160
+ ```
161
+
162
+ 这是 rtk 的 token-optimized 格式——权限位折叠成八进制、无 owner/日期列、目录在前、大小带单位。同一目录的原生格式是:
163
+
164
+ ```
165
+ $ /bin/ls -la $HOME/.agents/ | head -3
166
+ -rw-r--r-- 1 wings wings 796 .gitignore
167
+ ```
168
+
169
+ 两者逐列不同,改写生效无疑。
170
+
171
+ ### 6.3 三条对照实验的解读
172
+
173
+ | 命令 | 结果 |
174
+ |---|---|
175
+ | `ls -la <dir>`(无管道) | rtk 格式 → **被改写** |
176
+ | `rtk ls -la <dir>`(已带 rtk) | rtk 格式 → 被跳过改写,但结果相同 |
177
+ | `/bin/ls -la <dir>` | 原生格式 → 未被改写(rtk 不认绝对路径形式) |
178
+
179
+ ### 6.4 一个必须知道的行为:带管道的命令不会被改写
180
+
181
+ ```
182
+ $ rtk rewrite "ls -la /tmp" → exit 3 rtk ls -la /tmp
183
+ $ rtk rewrite "ls -la /tmp | head -5" → exit 1 (无输出)
184
+ ```
185
+
186
+ **rtk 对含管道的命令保守跳过改写**——这是 rtk 自身的支持策略,插件忠实转发。排查期间一度误判为缺陷,实为测试命令自带了 `| head`,绕过了改写。用户若想让某条命令走 rtk,去掉管道(或用 `rtk …` 显式调用)。
187
+
188
+ ### 6.5 `/rtk` 命令在真实环境已注册
189
+
190
+ 交付的能力里,命令改写与输出压缩已直接观察到;`/rtk` 是最后一项未在真实会话中确认的。用动态插件读取宿主的命令注册表(一次 bash 调用触发):
191
+
192
+ ```
193
+ [rtk-cmd] rtkCommand=FOUND (RTK command rewriting and output compaction: sta)
194
+ | all=[compact,export,feedback,goal,permission,plan,rtk]
195
+ ```
196
+
197
+ `rtk` 出现在该 agent 可见的命令列表中。用户可自行输入 `/rtk`、`/rtk verify`、`/rtk stats` 复核。
198
+
199
+ ### 6.6 另一条被 harness 拒绝的路径(留档)
200
+
201
+ 曾尝试把**正在运行的会话**重挂到 preset 以取得进程内证据,`agentPresets.select` 明确拒绝:
202
+
203
+ ```
204
+ [rtk-switch] SELECT FAIL: session "session-44856891-…" has already started; its agent preset is fixed
205
+ ```
206
+
207
+ **preset 在会话创建时锁定**,这是 harness 的设计约束。`SubagentStartRequest` 也没有 preset 覆盖字段(只有 `label`/`prompt`/`parent`/`signal`/`agentOptions`),所以 subagent 同样继承父会话 preset、无法用于验证。这也是最终选择 host 平面的原因之一:host 层是进程级的,重启即对**包括当前会话在内**的所有会话生效。
208
+
209
+ ## 7. 已知限制
210
+
211
+ - **流式 bash 输出未清洗。** Pi 有 `tool_execution_start/update/end` 钩子可以边流边清洗;harness 没有对应事件,故未移植。
212
+ - **Windows 兼容修正与 hashline 锚点保护的 read 处理未移植。** 目标部署是 POSIX,且 harness 的 `read` 输出格式与 Pi 不同(read 压缩默认关闭,影响面为零)。
213
+ - **`/rtk` 没有交互式设置面板。** harness 的 TUI 设置面板是 client 侧能力;配置改由 `dsh-rtk` settings 命名空间承载,可用 `/rtk show` 查看、在设置文档中修改。
214
+ - **含管道的命令不会被改写。** `rtk rewrite` 对 `cmd | other` 返回 exit 1(不支持),插件随之原样执行。这不是缺陷,是 rtk 的支持策略;但意味着 `ls | head` 这类常见写法享受不到优化。
215
+ - **`pwsh` 的环境前缀已按 PowerShell 方言分派**(`$env:NAME = '…'`,不是 POSIX 的 `export`);修复前 Windows 上每次被改写的 pwsh 调用都会语法错误。**本机无 pwsh,该项仅由单元测试覆盖,未真机执行。**
216
+ - **验收第 8 项依赖一个真实会话。** 前 7 项都能在进程外复现;第 8 项需要新建一个使用 `rtk` preset 的会话。
217
+
218
+ ---
219
+
220
+ ## 8. 独立复核(subagent)与修复
221
+
222
+ 由另一个 agent 独立复核(不共享本对话上下文):自行跑测试、读 harness 源码核实接口断言、做注入验红、与 pi 版逐文件对照。裁决为「核心结论**部分成立**」,共找出 5 处真实缺陷。
223
+
224
+ **全部已修**,且每一条都**先由我独立复现再修**——不是直接采信复核者的描述:
225
+
226
+ | 编号 | 级别 | 缺陷 | 我的复现 | 修复 |
227
+ |---|---|---|---|---|
228
+ | D1 | 高 | `compactReadText` 的「永不增大」守卫在拼 banner **之前**执行,`read` + 源码过滤路径实测 `1487 → 1518`(+31 字符)且 `changed=true` | `test/regressions.test.ts` 的 banner 用例,修前失败 | banner 计入长度比较 |
229
+ | D2 | 中高 | post-execute 重建 decision 时吞掉内层 listener 的 `additionalContexts`;shipped 的 `dsh-tool-fs-search` 在结果被截断时正是这样返回 | `preserves contexts attached by an inner listener`,修前失败 | 重建时透传 `additionalContexts` |
230
+ | D3 | 中 | `pwsh` 也是改写目标,却统一加 POSIX 前缀 `export …` → Windows 上语法错误 | 三个 PowerShell 语法用例,修前失败 | `applyRtkHistoryScope` 增加 shell 方言参数,按工具分派 |
231
+ | D4 | 中 | 全部测试只 import `lib/`,改 `src/` 不构建就测不出行为回归;`check` 顺序又是「先测后构建」 | 复核者实测:只改 `src` → 84/84 全绿;改 `lib` 同一处 → 83/1 | `test` script 改为先 `build` 再测 |
232
+ | D5 | 低 | `suggest` 分支的 `pendingSuggestions` 在 `next()` 抛错时不清理 | 代码审读 | 加 `try/catch` 清理 |
233
+
234
+ 修复后复跑:`ℹ tests 92 / pass 92 / fail 0`。
235
+
236
+ ### 复核对本报告的两处纠正(已落进文档)
237
+
238
+ 1. **「唯一接缝」的说法不完整。** 本报告此前只写「`tools/execute` 是唯一能改写命令的接缝」,**没有披露**官方 JSDoc 明确限定 wrapper「may change only `exec.signal`」。复核实测确认:改写能生效,只是因为执行对象直到 `notifyResult` 才被冻结——属于**未被支持的用法**,前向兼容无保证。这一披露现已写进 `README.md` 与 `README.zh.md` 的「注意」小节。
239
+ 2. **「压缩结果永不大于原文」曾不成立**(D1)。本报告此前把它列为无条件不变量,属于**夸大**;现已改为带修复历史的准确表述。
240
+
241
+ ### 复核确认成立的部分
242
+
243
+ 改写确实只能经 `tools/execute`(复核者用真实 `ToolRuntime` + 3 路并发实测)、退出码标记在 bash 路径确实保住、rtk 缺失时正确退化、参数在 `finally` 中恢复、并行调用无串扰、`lib` 与 `src` 除 `.map` 外一致。
244
+
245
+ ### 仍未覆盖
246
+
247
+ pwsh 真机执行、grep 超限时的真实会话 e2e、`/rtk` 命令面与 settings 命名空间冲突路径、`test-output`/`linter`/`search` 与 pi 版的逐行对照。
248
+
249
+ ---
250
+
251
+ ## 9. 超长输出:与 harness spill 的职责冲突(已修)
252
+
253
+ 由 token 效率复核发现、我实测确认。
254
+
255
+ **事实**:`dsh-spill-policy` 确实挂载,`maxInlineBytes = 50000`;它把完整输出落盘、上下文只留头尾预览——**可恢复**。而 dsh-rtk 的硬截断**不可恢复**,且**先执行**。
256
+
257
+ **实测**:`seq 1 12000`(约 60 KB)→ 输出被砍在 2619 行(≈12000 字符,末尾 `2...`),**没有任何 spill 落盘路径**。因为 12000 < 50000,spill 永远等不到大输入。
258
+
259
+ **修复**:新增 `deferToHarnessSpill`(默认 `true`)。插件在 `apply` 时用 `ctx.get('spillStore')` 探测 spill 是否挂载;挂载则关闭自身的 `truncate`,把超大输出让给可恢复的 spill 路径;未挂载则保留截断作为唯一上限。
260
+
261
+ **为什么用显式开关而不是"用户是否设过 truncate"**:loader 传给 `apply` 的是 **schema 归一化后**的配置,每个字段都带默认值,"键不存在"与"显式设为默认值"无法区分。第一版据此判断,被测试当场抓出(协调永不生效)。显式开关语义清晰且可覆盖。
262
+
263
+ **未覆盖**:spill 真实触发路径未在会话内观察到(需要一次 >50000 字节且不被 rtk 改写的输出);本项结论基于配置 + 截断行为的实测。
264
+
265
+ ---
266
+
267
+ ## 10. settings 命名空间静默失败(已修)
268
+
269
+ **症状**:`/rtk show` 打印 `config: settings service unavailable`;`settings.describe()` 里始终没有 `dsh-rtk`;所有 settings 编辑无效且**毫无提示**。
270
+
271
+ **根因**:同一份 `apply()` 里出现矛盾 —— `ctx.get('spillStore')` 成功(`truncate off` 证明协调生效),而 `ctx.get('settings')` 返回 `undefined`。服务不是不存在,而是**注册得晚**:settings provider 要先读设置文档。`inject` 当时只声明了 `tools`,插件在 settings 出现之前就已经 apply 过了。
272
+
273
+ **修复**:`inject = ['tools', 'settings']`。Cordis 对这个声明的语义正是「等待服务出现后再激活」。
274
+
275
+ **验证**(重启后):`hasDshRtk=true`,namespace 总数 17 → 18,解析值 `readCompaction.enabled=true`、`smartTruncate={enabled:true,maxLines:220}`、`deferToHarnessSpill=true`,均与 patch 配置一致。
276
+
277
+ **一个容易误读的点**:settings 里 `truncate.enabled` 显示 `true`(schema 解析值),而实际行为是**关闭**的 —— 因为插件在运行时探测到 spill 后做了协调。`deferToHarnessSpill` 才是表达该意图的字段。
278
+
279
+ **注**:这个 bug 之所以能被找到,靠的是先前那次「让失败可见」的改动 —— 在此之前它是完全静默的。
@@ -0,0 +1,41 @@
1
+ import type { RtkConfig } from './config.js';
2
+ import type { MetricsSummary } from './metrics.js';
3
+ import type { RtkRuntimeStatus } from './runtime-guard.js';
4
+ /** Everything the `/rtk` command needs from the plugin. */
5
+ export interface RtkCommandController {
6
+ getConfig(): RtkConfig;
7
+ /** Replace the user layer wholesale; `{}` restores composition defaults. */
8
+ resetConfig(): Promise<void>;
9
+ getRuntimeStatus(): RtkRuntimeStatus;
10
+ refreshRuntimeStatus(): Promise<RtkRuntimeStatus>;
11
+ getMetrics(): MetricsSummary;
12
+ clearMetrics(): void;
13
+ /** Where the user layer of the configuration lives, when it is persisted. */
14
+ configLocation(): string;
15
+ }
16
+ /** The command result shape the harness normalizes. */
17
+ export type RtkCommandResult = {
18
+ kind: 'success';
19
+ text?: string;
20
+ } | {
21
+ kind: 'error';
22
+ text: string;
23
+ };
24
+ /**
25
+ * Build the `/rtk` command definition.
26
+ *
27
+ * The harness has no interactive settings modal, so every subcommand returns
28
+ * text; the configuration itself is editable through the harness settings
29
+ * document, which this namespace is registered in.
30
+ *
31
+ * @param controller - accessors into the live plugin state.
32
+ * @returns the command definition to register.
33
+ */
34
+ export declare function createRtkCommand(controller: RtkCommandController): {
35
+ name: string;
36
+ description: string;
37
+ handler: (invocation: {
38
+ rawInput: string;
39
+ }) => Promise<RtkCommandResult>;
40
+ };
41
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE1D,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,SAAS,IAAI,SAAS,CAAA;IACtB,4EAA4E;IAC5E,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,gBAAgB,IAAI,gBAAgB,CAAA;IACpC,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACjD,UAAU,IAAI,cAAc,CAAA;IAC5B,YAAY,IAAI,IAAI,CAAA;IACpB,6EAA6E;IAC7E,cAAc,IAAI,MAAM,CAAA;CACzB;AAED,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AA8FnG;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,oBAAoB,GAAG;IAClE,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,CAAC,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;CACzE,CA+CA"}
package/lib/command.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Official install routes, taken from the project's own README.
3
+ *
4
+ * `cargo install rtk` is called out because crates.io carries an unrelated
5
+ * crate of the same name; only the `--git` form installs this tool.
6
+ */
7
+ const INSTALL_HINT = [
8
+ 'Install rtk with one of:',
9
+ ' brew install rtk # macOS / Linux (Homebrew, recommended)',
10
+ ' winget install rtk-ai.rtk # Windows',
11
+ ' curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh',
12
+ ' cargo install --git https://github.com/rtk-ai/rtk',
13
+ '',
14
+ 'Note: plain `cargo install rtk` installs an unrelated crate of the same name.',
15
+ 'Releases: https://github.com/rtk-ai/rtk/releases',
16
+ ].join('\n');
17
+ const HELP_TEXT = [
18
+ 'dsh-rtk — rewrite bash commands to rtk and compact tool output.',
19
+ '',
20
+ ' /rtk show configuration and runtime status',
21
+ ' /rtk show same as /rtk',
22
+ ' /rtk path where the configuration is stored',
23
+ ' /rtk verify check whether the rtk executable is usable (prints install options when it is not)',
24
+ ' /rtk stats compaction savings for this session',
25
+ ' /rtk clear-stats reset the savings counters',
26
+ ' /rtk reset restore configuration defaults',
27
+ ' /rtk help this text',
28
+ ].join('\n');
29
+ function yesNo(value) {
30
+ return value ? 'on' : 'off';
31
+ }
32
+ function configReport(config, status, location) {
33
+ const compaction = config.outputCompaction;
34
+ const lines = [
35
+ `dsh-rtk: ${yesNo(config.enabled)} mode: ${config.mode}`,
36
+ `rtk binary: ${status.rtkAvailable ? 'available' : 'unavailable'}${status.rtkExecutablePath ? ` (${status.rtkExecutablePath})` : ''}`,
37
+ `config: ${location}`,
38
+ '',
39
+ 'command rewriting',
40
+ ` guardWhenRtkMissing ${yesNo(config.guardWhenRtkMissing)}`,
41
+ ` showRewriteNotifications ${yesNo(config.showRewriteNotifications)}`,
42
+ ` rewriteTimeoutMs ${config.rewriteTimeoutMs}`,
43
+ '',
44
+ 'output compaction',
45
+ ` enabled ${yesNo(compaction.enabled)}`,
46
+ ` tools ${config.compactedTools.join(', ')}`,
47
+ ` stripAnsi ${yesNo(compaction.stripAnsi)}`,
48
+ ` readCompaction ${yesNo(compaction.readCompaction.enabled)}`,
49
+ ` sourceCodeFiltering ${compaction.sourceCodeFiltering}`,
50
+ ` filterBuildOutput ${yesNo(compaction.filterBuildOutput)}`,
51
+ ` aggregateTestOutput ${yesNo(compaction.aggregateTestOutput)}`,
52
+ ` compactGitOutput ${yesNo(compaction.compactGitOutput)}`,
53
+ ` aggregateLinterOutput ${yesNo(compaction.aggregateLinterOutput)}`,
54
+ ` groupSearchOutput ${yesNo(compaction.groupSearchOutput)}`,
55
+ ` smartTruncate ${yesNo(compaction.smartTruncate.enabled)} (max ${compaction.smartTruncate.maxLines} lines)`,
56
+ ` truncate ${yesNo(compaction.truncate.enabled)} (max ${compaction.truncate.maxChars} chars)`,
57
+ ];
58
+ if (status.lastError)
59
+ lines.push('', `last rtk error: ${status.lastError}`);
60
+ return lines.join('\n');
61
+ }
62
+ function statsReport(summary) {
63
+ if (summary.calls === 0)
64
+ return 'dsh-rtk: no compaction recorded in this session yet.';
65
+ const lines = [
66
+ `compacted calls: ${summary.calls}`,
67
+ `characters: ${summary.originalChars} -> ${summary.compactedChars} (saved ${summary.savedChars}, ${summary.savedPercent}%)`,
68
+ ];
69
+ const tools = Object.entries(summary.byTool);
70
+ if (tools.length > 0) {
71
+ lines.push('', 'by tool:');
72
+ for (const [tool, totals] of tools.sort((left, right) => right[1].originalChars - left[1].originalChars)) {
73
+ const saved = Math.max(0, totals.originalChars - totals.compactedChars);
74
+ lines.push(` ${tool}: ${totals.calls} call(s), saved ${saved} chars`);
75
+ }
76
+ }
77
+ const techniques = Object.entries(summary.byTechnique);
78
+ if (techniques.length > 0) {
79
+ lines.push('', 'techniques:');
80
+ for (const [technique, count] of techniques.sort((left, right) => right[1] - left[1])) {
81
+ lines.push(` ${technique}: ${count}`);
82
+ }
83
+ }
84
+ return lines.join('\n');
85
+ }
86
+ /**
87
+ * Build the `/rtk` command definition.
88
+ *
89
+ * The harness has no interactive settings modal, so every subcommand returns
90
+ * text; the configuration itself is editable through the harness settings
91
+ * document, which this namespace is registered in.
92
+ *
93
+ * @param controller - accessors into the live plugin state.
94
+ * @returns the command definition to register.
95
+ */
96
+ export function createRtkCommand(controller) {
97
+ return {
98
+ name: 'rtk',
99
+ description: 'RTK command rewriting and output compaction: status, verification, and savings.',
100
+ async handler(invocation) {
101
+ const subcommand = invocation.rawInput.trim().split(/\s+/)[0]?.toLowerCase() ?? '';
102
+ switch (subcommand) {
103
+ case '':
104
+ case 'show':
105
+ return { kind: 'success', text: configReport(controller.getConfig(), controller.getRuntimeStatus(), controller.configLocation()) };
106
+ case 'path':
107
+ return { kind: 'success', text: controller.configLocation() };
108
+ case 'verify': {
109
+ const status = await controller.refreshRuntimeStatus();
110
+ if (status.rtkAvailable) {
111
+ const parts = [`rtk is available at ${status.rtkExecutablePath ?? status.rtkExecutableCommand ?? 'rtk'}`];
112
+ if (status.rtkExecutableResolutionWarning)
113
+ parts.push(`note: ${status.rtkExecutableResolutionWarning}`);
114
+ return { kind: 'success', text: parts.join('\n') };
115
+ }
116
+ return {
117
+ kind: 'error',
118
+ text: `rtk is not usable: ${status.lastError ?? 'unknown error'}\n\n${INSTALL_HINT}`,
119
+ };
120
+ }
121
+ case 'stats':
122
+ return { kind: 'success', text: statsReport(controller.getMetrics()) };
123
+ case 'clear-stats':
124
+ controller.clearMetrics();
125
+ return { kind: 'success', text: 'dsh-rtk: savings counters cleared.' };
126
+ case 'reset':
127
+ await controller.resetConfig();
128
+ return { kind: 'success', text: 'dsh-rtk: configuration restored to defaults.' };
129
+ case 'help':
130
+ return { kind: 'success', text: HELP_TEXT };
131
+ default:
132
+ return { kind: 'error', text: `unknown subcommand "${subcommand}".\n\n${HELP_TEXT}` };
133
+ }
134
+ },
135
+ };
136
+ }
137
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reduce compiler output to its errors and warnings.
3
+ *
4
+ * Errors keep their indented continuation lines (the source excerpt and the
5
+ * `-->` location) because that detail is what makes an error actionable;
6
+ * progress lines and everything else are dropped. A build with no diagnostics
7
+ * collapses to a one-line success marker, which is the common case and the
8
+ * largest saving.
9
+ *
10
+ * @param output - raw build output.
11
+ * @param command - the command that produced it; drives the applicability check.
12
+ * @returns the summary, or `null` when the command is not a build command.
13
+ */
14
+ export declare function filterBuildOutput(output: string, command: string | undefined | null): string | null;
15
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/compact/build.ts"],"names":[],"mappings":"AA6DA;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CA+EnG"}
@@ -0,0 +1,140 @@
1
+ import { matchesCommandPatterns } from './detect.js';
2
+ const BUILD_COMMAND_PATTERNS = [
3
+ /^cargo\s+(build|check)\b/,
4
+ /^bun\s+build\b/,
5
+ /^npm\s+run\s+build\b/,
6
+ /^yarn\s+build\b/,
7
+ /^pnpm\s+build\b/,
8
+ /^(?:npx\s+)?tsc\b/,
9
+ /^make\b/,
10
+ /^cmake\b/,
11
+ /^gradle\b/,
12
+ /^mvn\b/,
13
+ /^go\s+(build|install)\b/,
14
+ /^python\s+setup\.py\s+build\b/,
15
+ /^pip\s+install\b/,
16
+ ];
17
+ /** Progress chatter that carries no actionable information. */
18
+ const SKIP_PATTERNS = [
19
+ /^\s*Compiling\s+/,
20
+ /^\s*Checking\s+/,
21
+ /^\s*Downloading\s+/,
22
+ /^\s*Downloaded\s+/,
23
+ /^\s*Fetching\s+/,
24
+ /^\s*Fetched\s+/,
25
+ /^\s*Updating\s+/,
26
+ /^\s*Updated\s+/,
27
+ /^\s*Building\s+/,
28
+ /^\s*Generated\s+/,
29
+ /^\s*Creating\s+/,
30
+ /^\s*Running\s+/,
31
+ ];
32
+ const ERROR_START_PATTERNS = [
33
+ /^error\[/,
34
+ /^error:/,
35
+ /^\[ERROR\]/,
36
+ /^FAIL/,
37
+ // `tsc` and friends: `path(line,col): error TSxxxx: message`.
38
+ /^[^\s:][^:]*(?:\(\d+[,:]\d+\)|:\d+:\d+):\s*(?:fatal\s+)?error\b/,
39
+ // The `path:line:col: error: message` shape used by many linters and compilers.
40
+ /^[^\s:][^:]*:\d+:\s*(?:fatal\s+)?error\b/,
41
+ ];
42
+ const WARNING_PATTERNS = [/^warning:/, /^\[WARNING\]/, /^warn:/];
43
+ const COMPILE_PROGRESS_PATTERN = /^\s*(Compiling|Checking|Building)\s+/;
44
+ const MAX_ERRORS = 5;
45
+ const MAX_ERROR_LINES = 10;
46
+ const BLANKS_TO_CLOSE_BLOCK = 2;
47
+ function isSkipLine(line) {
48
+ return SKIP_PATTERNS.some((pattern) => pattern.test(line));
49
+ }
50
+ /**
51
+ * Reduce compiler output to its errors and warnings.
52
+ *
53
+ * Errors keep their indented continuation lines (the source excerpt and the
54
+ * `-->` location) because that detail is what makes an error actionable;
55
+ * progress lines and everything else are dropped. A build with no diagnostics
56
+ * collapses to a one-line success marker, which is the common case and the
57
+ * largest saving.
58
+ *
59
+ * @param output - raw build output.
60
+ * @param command - the command that produced it; drives the applicability check.
61
+ * @returns the summary, or `null` when the command is not a build command.
62
+ */
63
+ export function filterBuildOutput(output, command) {
64
+ if (!matchesCommandPatterns(command, BUILD_COMMAND_PATTERNS))
65
+ return null;
66
+ const lines = output.split('\n');
67
+ const stats = { compiled: 0, errors: [], warnings: [] };
68
+ let inErrorBlock = false;
69
+ let currentError = [];
70
+ let blankCount = 0;
71
+ for (const line of lines) {
72
+ if (COMPILE_PROGRESS_PATTERN.test(line)) {
73
+ stats.compiled++;
74
+ continue;
75
+ }
76
+ if (isSkipLine(line))
77
+ continue;
78
+ if (ERROR_START_PATTERNS.some((pattern) => pattern.test(line))) {
79
+ if (inErrorBlock && currentError.length > 0)
80
+ stats.errors.push([...currentError]);
81
+ inErrorBlock = true;
82
+ currentError = [line];
83
+ blankCount = 0;
84
+ continue;
85
+ }
86
+ if (WARNING_PATTERNS.some((pattern) => pattern.test(line))) {
87
+ stats.warnings.push(line);
88
+ continue;
89
+ }
90
+ if (!inErrorBlock)
91
+ continue;
92
+ if (line.trim() === '') {
93
+ blankCount++;
94
+ if (blankCount >= BLANKS_TO_CLOSE_BLOCK && currentError.length > 3) {
95
+ stats.errors.push([...currentError]);
96
+ inErrorBlock = false;
97
+ currentError = [];
98
+ }
99
+ else {
100
+ currentError.push(line);
101
+ }
102
+ continue;
103
+ }
104
+ if (line.match(/^\s/) || line.match(/^-->/)) {
105
+ currentError.push(line);
106
+ blankCount = 0;
107
+ continue;
108
+ }
109
+ stats.errors.push([...currentError]);
110
+ inErrorBlock = false;
111
+ currentError = [];
112
+ }
113
+ if (inErrorBlock && currentError.length > 0)
114
+ stats.errors.push(currentError);
115
+ if (stats.errors.length === 0 && stats.warnings.length === 0) {
116
+ // Success may only be claimed on positive evidence that the build ran and
117
+ // was clean. With no diagnostics AND no progress lines the parser simply
118
+ // did not understand the output — and it cannot see an exit code, so
119
+ // guessing "success" would replace a real compiler failure with a
120
+ // reassuring lie. Declining to summarize leaves the text intact.
121
+ if (stats.compiled === 0)
122
+ return null;
123
+ return `[OK] Build successful (${stats.compiled} units compiled)`;
124
+ }
125
+ const result = [];
126
+ if (stats.errors.length > 0) {
127
+ result.push(`[ERROR] ${stats.errors.length} error(s):`);
128
+ for (const error of stats.errors.slice(0, MAX_ERRORS)) {
129
+ result.push(...error.slice(0, MAX_ERROR_LINES));
130
+ if (error.length > MAX_ERROR_LINES)
131
+ result.push(' ...');
132
+ }
133
+ if (stats.errors.length > MAX_ERRORS)
134
+ result.push(`... and ${stats.errors.length - MAX_ERRORS} more errors`);
135
+ }
136
+ if (stats.warnings.length > 0)
137
+ result.push(`\n[WARN] ${stats.warnings.length} warning(s)`);
138
+ return result.join('\n');
139
+ }
140
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/compact/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAQpD,MAAM,sBAAsB,GAAG;IAC7B,0BAA0B;IAC1B,gBAAgB;IAChB,sBAAsB;IACtB,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;IACnB,SAAS;IACT,UAAU;IACV,WAAW;IACX,QAAQ;IACR,yBAAyB;IACzB,+BAA+B;IAC/B,kBAAkB;CACV,CAAA;AAEV,+DAA+D;AAC/D,MAAM,aAAa,GAAG;IACpB,kBAAkB;IAClB,iBAAiB;IACjB,oBAAoB;IACpB,mBAAmB;IACnB,iBAAiB;IACjB,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;IACjB,gBAAgB;CACjB,CAAA;AAED,MAAM,oBAAoB,GAAG;IAC3B,UAAU;IACV,SAAS;IACT,YAAY;IACZ,OAAO;IACP,8DAA8D;IAC9D,iEAAiE;IACjE,gFAAgF;IAChF,0CAA0C;CAC3C,CAAA;AACD,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAA;AAChE,MAAM,wBAAwB,GAAG,sCAAsC,CAAA;AAEvE,MAAM,UAAU,GAAG,CAAC,CAAA;AACpB,MAAM,eAAe,GAAG,EAAE,CAAA;AAC1B,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAE/B,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,OAAkC;IAClF,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,CAAC;QAAE,OAAO,IAAI,CAAA;IAEzE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAChC,MAAM,KAAK,GAAe,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;IAEnE,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,IAAI,YAAY,GAAa,EAAE,CAAA;IAC/B,IAAI,UAAU,GAAG,CAAC,CAAA;IAElB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,KAAK,CAAC,QAAQ,EAAE,CAAA;YAChB,SAAQ;QACV,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,SAAQ;QAE9B,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC/D,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;gBAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAA;YACjF,YAAY,GAAG,IAAI,CAAA;YACnB,YAAY,GAAG,CAAC,IAAI,CAAC,CAAA;YACrB,UAAU,GAAG,CAAC,CAAA;YACd,SAAQ;QACV,CAAC;QAED,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC3D,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACzB,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,YAAY;YAAE,SAAQ;QAE3B,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACvB,UAAU,EAAE,CAAA;YACZ,IAAI,UAAU,IAAI,qBAAqB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAA;gBACpC,YAAY,GAAG,KAAK,CAAA;gBACpB,YAAY,GAAG,EAAE,CAAA;YACnB,CAAC;iBAAM,CAAC;gBACN,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACzB,CAAC;YACD,SAAQ;QACV,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5C,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACvB,UAAU,GAAG,CAAC,CAAA;YACd,SAAQ;QACV,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAA;QACpC,YAAY,GAAG,KAAK,CAAA;QACpB,YAAY,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAE5E,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7D,0EAA0E;QAC1E,yEAAyE;QACzE,qEAAqE;QACrE,kEAAkE;QAClE,iEAAiE;QACjE,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACrC,OAAO,0BAA0B,KAAK,CAAC,QAAQ,kBAAkB,CAAA;IACnE,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,MAAM,YAAY,CAAC,CAAA;QACvD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAA;YAC/C,IAAI,KAAK,CAAC,MAAM,GAAG,eAAe;gBAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,UAAU,cAAc,CAAC,CAAA;IAC9G,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,QAAQ,CAAC,MAAM,aAAa,CAAC,CAAA;IAE1F,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC1B,CAAC"}