dsh-remote-file-system 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -18,6 +18,8 @@ DSH 只有本地文件系统的读写能力,而读写远程文件只能用 Pow
18
18
  | `write_remote` | 远端新建文件 | 只允许新建,目标已存在即拒绝,绝无覆写分支 |
19
19
  | `edit_remote` | 远端字面替换 | 唯一匹配,多现拒绝,未命中报错;支持 replace_all |
20
20
 
21
+ `read_remote` 只接受有效 UTF-8,读到非文本报 `FS_NOT_TEXT`。`edit_remote` 会核对上一次读取的内容指纹,读完之后文件被外部改过时报 `FS_STALE_VERSION`,需要重新读取再编辑。
22
+
21
23
  ### 右侧栏预览
22
24
 
23
25
  内置查看器只认主机本地文件:点开 `wsl:~/x.md` 时地址是 `dsh-resource://file/session/<id>/wsl:~/x.md`,内置 `text` 类型经 `ctx.fs` 在本地查找而失败。
@@ -33,9 +35,10 @@ ssh 目标与远程路径合在一个 `file_path` 字段,沿用「别名:路
33
35
  ```text
34
36
  wsl:~/x.md
35
37
  myserver:/etc/hosts
38
+ winbox:D:/work/test.txt
36
39
  ```
37
40
 
38
- 只收绝对路径或 ~/ 开头;裸相对路径直接拒绝。
41
+ 只收绝对路径、~/ 或盘符开头;裸相对路径直接拒绝。远端是 Windows 时路径原样交给 PowerShell,反斜杠与正斜杠都认。
39
42
 
40
43
  ## 宣传片
41
44
 
@@ -73,6 +76,8 @@ dsh plugin --profile web remove dsh-remote-file-system
73
76
 
74
77
  文件后端 `RemoteFileSystem` 刻意不继承 `@deepseek-ai/dsh-fs` 的 `FileSystem` 与 cordis `Service`,避免「继承即注册」污染会话 `ctx.fs`;只借用 `dsh-fs` 类型词表与 `FsError`。字节经 base64 再经 ssh 传输。
75
78
 
79
+ 远端平台按别名探测一次并缓存:能跑通 `cmd /c echo Windows_NT` 判为 Windows,否则按 POSIX。POSIX 走 `bash -s`,Windows 走 `powershell -EncodedCommand`,脚本不进命令行以免被 32k 上限卡住,写入的数据经 stdin 传。
80
+
76
81
  浏览器半身经 Host 自注册的 `/remote-file-system` 通道读取远端文本,复用 `connection.requestRejection` 做 Host 校验与浏览器鉴权,与 dsh-classic-coding 的 `src/rpc-channel.ts` 同源。
77
82
 
78
83
  ## 开发
@@ -82,6 +87,7 @@ pnpm install # 装依赖,含 devDeps 类型与 esbuild
82
87
  pnpm build # esbuild 打包 host 与 client 半身到 lib/
83
88
  pnpm typecheck
84
89
  node smoke/verify.mjs # 手动组装最小 cordis 组合,对 wsl 冒烟三工具
90
+ node smoke/remote-win-smoke.mjs # 对 Windows 远端的 stat/read/edit/write 冒烟
85
91
  node smoke/rpc-smoke.mjs # 对源码的 RPC 通道做信封级冒烟,不依赖 @deepseek-ai 包
86
92
  ```
87
93
 
@@ -92,3 +98,5 @@ node smoke/rpc-smoke.mjs # 对源码的 RPC 通道做信封级冒烟,不依
92
98
  ## License
93
99
 
94
100
  [MIT](./LICENSE)
101
+
102
+ [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness)
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  FsTargetKey,
5
5
  FsVersion
6
6
  } from "@deepseek-ai/dsh-fs";
7
+ import { createHash } from "node:crypto";
7
8
 
8
9
  // src/protocol.ts
9
10
  import { spawn } from "node:child_process";
@@ -12,6 +13,18 @@ var ERR_PREFIX = "__ERR__";
12
13
  function b64(input) {
13
14
  return Buffer.from(input, "utf8").toString("base64");
14
15
  }
16
+ var PLATFORM_CACHE = /* @__PURE__ */ new Map();
17
+ function windowsCommand(script) {
18
+ return "powershell -NoProfile -NonInteractive -EncodedCommand " + Buffer.from(script, "utf16le").toString("base64");
19
+ }
20
+ async function detectPlatform(alias) {
21
+ const hit = PLATFORM_CACHE.get(alias);
22
+ if (hit) return hit;
23
+ const raw = await sshExec(alias, {}, "", { timeoutMs: 15e3 }, "cmd /c echo Windows_NT");
24
+ const platform = /Windows_NT/.test(raw.stdout) ? "windows" : "posix";
25
+ PLATFORM_CACHE.set(alias, platform);
26
+ return platform;
27
+ }
15
28
  var PATH_PROLOGUE = [
16
29
  "set +e",
17
30
  'b64d() { printf %s "$1" | base64 -d 2>/dev/null; }',
@@ -21,7 +34,7 @@ var PATH_PROLOGUE = [
21
34
  function envPrefix(env) {
22
35
  return Object.entries(env).map(([k, v]) => k + "='" + v + "'").join(" ") + " bash -s";
23
36
  }
24
- async function sshExec(alias, env, scriptBody, opts = {}) {
37
+ async function sshExec(alias, env, scriptBody, opts = {}, remoteCommand) {
25
38
  const timeoutMs = opts.timeoutMs ?? 3e4;
26
39
  return await new Promise((resolve, reject) => {
27
40
  const child = spawn("ssh", [
@@ -30,7 +43,7 @@ async function sshExec(alias, env, scriptBody, opts = {}) {
30
43
  "-o",
31
44
  "ConnectTimeout=" + Math.max(1, Math.floor(timeoutMs / 1e3)),
32
45
  alias,
33
- envPrefix(env)
46
+ remoteCommand ?? envPrefix(env)
34
47
  ], { stdio: ["pipe", "pipe", "pipe"] });
35
48
  let stdout = "";
36
49
  let stderr = "";
@@ -64,8 +77,16 @@ async function sshExec(alias, env, scriptBody, opts = {}) {
64
77
  child.stdin.end();
65
78
  });
66
79
  }
67
- async function sshOutcome(alias, env, scriptBody, opts = {}) {
68
- const raw = await sshExec(alias, env, scriptBody, opts).catch((e) => {
80
+ async function sshOutcome(alias, env, scriptBody, opts = {}, win) {
81
+ const platform = await detectPlatform(alias);
82
+ let body = scriptBody;
83
+ let remoteCommand;
84
+ if (platform === "windows") {
85
+ if (!win) throw new FsError("该操作缺少 Windows 版本的远端脚本", "FS_IO_ERROR");
86
+ body = win.input ?? "";
87
+ remoteCommand = windowsCommand(win.script);
88
+ }
89
+ const raw = await sshExec(alias, env, body, opts, remoteCommand).catch((e) => {
69
90
  throw new FsError(e.message, "FS_IO_ERROR");
70
91
  });
71
92
  if (raw.exitCode === -1) throw new FsError("远程执行超时", "FS_IO_ERROR");
@@ -98,20 +119,35 @@ async function withLock(key, fn) {
98
119
  return run;
99
120
  }
100
121
  var ALIAS_RE = /^[A-Za-z0-9._-]+$/;
122
+ function winPrologue(rawPath) {
123
+ return [
124
+ "$ErrorActionPreference='Stop'",
125
+ 'function Die($c,$m){ [Console]::Error.WriteLine("__ERR__"+$c+"__"+$m); exit 1 }',
126
+ "function Decode($s){ [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($s)) }",
127
+ "$P = Decode('" + b64(rawPath) + "')",
128
+ "if ($P.StartsWith('~/')) { $P = Join-Path $env:USERPROFILE $P.Substring(2) }"
129
+ ].join("\n");
130
+ }
101
131
  function splitAliasPath(filePath) {
102
132
  const idx = filePath.indexOf(":");
103
133
  if (idx <= 0) throw new FsError2("file_path 须为「别名:路径」形式,缺少别名前缀:" + filePath, "FS_IO_ERROR");
104
134
  const alias = filePath.slice(0, idx);
105
135
  const rawPath = filePath.slice(idx + 1);
106
136
  if (!ALIAS_RE.test(alias)) throw new FsError2("非法的别名前缀:" + alias, "FS_IO_ERROR");
107
- if (!(rawPath.startsWith("/") || rawPath.startsWith("~/"))) {
108
- throw new FsError2("远程路径只接受绝对路径或 ~/ 开头,收到相对路径:" + rawPath, "FS_IO_ERROR");
137
+ const winAbs = /^[A-Za-z]:[\\/]/.test(rawPath);
138
+ if (!(rawPath.startsWith("/") || rawPath.startsWith("~/") || winAbs)) {
139
+ throw new FsError2("远程路径只接受绝对路径、~/ 或盘符开头,收到相对路径:" + rawPath, "FS_IO_ERROR");
109
140
  }
110
141
  return { alias, rawPath };
111
142
  }
112
143
  var RemoteFileSystem = class {
113
144
  /** 单次读取字节上限,超限拒绝且不做流式,见 DESIGN §7 资源上限。 */
114
145
  readMaxBytes = 50 * 1024 * 1024;
146
+ /** 读过的内容指纹,供 editText 判断「读到之后又被改过」。只在 host 进程内有效。 */
147
+ observed = /* @__PURE__ */ new Map();
148
+ fingerprint(text) {
149
+ return createHash("sha256").update(text, "utf8").digest("hex");
150
+ }
115
151
  /** 解析「别名:路径」为稳定 FsTarget。targetKey 含 alias 与 rawPath,displayPath 即原名。 */
116
152
  async resolve(path) {
117
153
  const { alias, rawPath } = splitAliasPath(path);
@@ -140,7 +176,17 @@ var RemoteFileSystem = class {
140
176
  'MT=$(stat -c %Y "$P" 2>/dev/null || echo 0)',
141
177
  'echo "FILE:$SIZE:$MT"'
142
178
  ].join("\n");
143
- const out = await sshOutcome(alias, env, action);
179
+ const winScript = [
180
+ winPrologue(rawPath),
181
+ "try {",
182
+ " $i = Get-Item -LiteralPath $P -Force -ErrorAction Stop",
183
+ " if ($i.PSIsContainer) { 'DIR' }",
184
+ " elseif ($i.Attributes -band [IO.FileAttributes]::ReparsePoint) { 'OTHER' }",
185
+ " elseif ($i -is [IO.FileInfo]) { 'FILE:' + $i.Length + ':' + ([DateTimeOffset]$i.LastWriteTimeUtc).ToUnixTimeSeconds() }",
186
+ " else { 'OTHER' }",
187
+ "} catch { 'ABSENT' }"
188
+ ].join("\n");
189
+ const out = await sshOutcome(alias, env, action, {}, { script: winScript });
144
190
  const s = out.stdout.trim();
145
191
  if (s === "ABSENT") return void 0;
146
192
  if (s === "DIR") return { version: FsVersion("dir:" + Date.now()), type: "directory" };
@@ -156,7 +202,11 @@ var RemoteFileSystem = class {
156
202
  }
157
203
  /** 读整文件 utf8。缺失归 FS_NOT_FOUND,非普通归 FS_NOT_REGULAR_FILE,超限归 FS_TOO_LARGE。 */
158
204
  async readText(target) {
159
- return withLock(target.targetKey, () => this.readTextLocked(target));
205
+ return withLock(target.targetKey, async () => {
206
+ const text = await this.readTextLocked(target);
207
+ this.observed.set(target.targetKey, this.fingerprint(text));
208
+ return text;
209
+ });
160
210
  }
161
211
  async readTextLocked(target) {
162
212
  const { alias, rawPath } = splitAliasPath(target.displayPath);
@@ -167,12 +217,32 @@ var RemoteFileSystem = class {
167
217
  'if [ ! -e "$P" ] && [ ! -L "$P" ]; then die FS_NOT_FOUND 文件不存在; exit 0; fi',
168
218
  'if [ -d "$P" ]; then die FS_NOT_REGULAR_FILE 目标是一个目录; exit 0; fi',
169
219
  'if [ ! -f "$P" ]; then die FS_NOT_REGULAR_FILE 目标不是普通文件; exit 0; fi',
220
+ 'if [ ! -r "$P" ]; then die FS_PERMISSION_DENIED 没有读取权限; exit 0; fi',
170
221
  'SIZE=$(wc -c < "$P" 2>/dev/null | tr -d " ")',
171
222
  'if [ -n "$SIZE" ] && [ "$SIZE" -gt "$RM_MAX" ] 2>/dev/null; then die FS_TOO_LARGE 文件超过单次读取上限,不做流式; exit 0; fi',
172
223
  'base64 -w0 < "$P" 2>/dev/null || die FS_IO_ERROR 读取内容失败'
173
224
  ].join("\n");
174
- const out = await sshOutcome(alias, env, action);
175
- return Buffer.from(out.stdout.replace(/\s+$/u, ""), "base64").toString("utf8");
225
+ const winScript = [
226
+ winPrologue(rawPath),
227
+ "if (-not (Test-Path -LiteralPath $P)) { Die 'FS_NOT_FOUND' '文件不存在'; exit 0 }",
228
+ "if (Test-Path -LiteralPath $P -PathType Container) { Die 'FS_NOT_REGULAR_FILE' '目标是目录'; exit 0 }",
229
+ "try {",
230
+ " $b = [IO.File]::ReadAllBytes($P)",
231
+ "} catch [System.UnauthorizedAccessException] {",
232
+ " Die 'FS_PERMISSION_DENIED' '没有权限'; exit 0",
233
+ "} catch {",
234
+ " Die 'FS_IO_ERROR' $_.Exception.Message; exit 0",
235
+ "}",
236
+ "if ($b.Length -gt " + this.readMaxBytes + ") { Die 'FS_TOO_LARGE' '文件超过单次读取上限'; exit 0 }",
237
+ "[Convert]::ToBase64String($b)"
238
+ ].join("\n");
239
+ const out = await sshOutcome(alias, env, action, {}, { script: winScript });
240
+ const raw = Buffer.from(out.stdout.replace(/\s+$/u, ""), "base64");
241
+ try {
242
+ return new TextDecoder("utf-8", { fatal: true }).decode(raw);
243
+ } catch {
244
+ throw new FsError2("目标不是有效的 UTF-8 文本", "FS_NOT_TEXT");
245
+ }
176
246
  }
177
247
  /** readBytes:raw 读取,文本只读,返回 utf8 字节。 */
178
248
  async readBytes(target, maxBytes) {
@@ -193,35 +263,88 @@ var RemoteFileSystem = class {
193
263
  if (expected !== void 0 && expected.kind !== "createIfAbsent") {
194
264
  throw new FsError2("write_remote 只允许新建,仅支持 createIfAbsent 意图", "FS_NOT_OBSERVED");
195
265
  }
196
- const env = { RM_PATH: b64(rawPath), RM_DATA: b64(content) };
266
+ const env = { RM_PATH: b64(rawPath) };
267
+ const data = b64(content);
197
268
  const action = [
198
269
  PATH_PROLOGUE,
199
270
  DIE_HELPER,
271
+ 'D=$(dirname "$P")',
272
+ 'if [ ! -d "$D" ]; then die FS_NOT_FOUND 目标目录不存在; exit 0; fi',
273
+ 'if [ ! -w "$D" ]; then die FS_PERMISSION_DENIED 目标目录不可写; exit 0; fi',
200
274
  'TMP=$(mktemp "$P.XXXXXXXX.tmp" 2>/dev/null)',
201
- 'if [ -z "$TMP" ]; then die FS_IO_ERROR 无法创建临时文件,目录可能不可写; exit 0; fi',
202
- 'printf %s "$(b64d "$RM_DATA")" > "$TMP" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR 写入临时文件失败; exit 0; }',
275
+ 'if [ -z "$TMP" ]; then die FS_IO_ERROR 无法创建临时文件; exit 0; fi',
276
+ `printf %s '${data}' | base64 -d > "$TMP" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR 写入临时文件失败; exit 0; }`,
203
277
  'if [ -e "$P" ] || [ -L "$P" ]; then rm -f "$TMP"; die FS_NOT_OBSERVED 文件已存在,write_remote 只允许新建; exit 0; fi',
204
278
  'mv -f "$TMP" "$P" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR mv 发布失败; exit 0; }',
205
279
  "echo OK"
206
280
  ].join("\n");
207
- await sshOutcome(alias, env, action);
281
+ const winScript = [
282
+ winPrologue(rawPath),
283
+ "$in = [Console]::In.ReadToEnd()",
284
+ "$data = [Convert]::FromBase64String($in.Trim())",
285
+ "if (Test-Path -LiteralPath $P) { Die 'FS_NOT_OBSERVED' '文件已存在,write_remote 只允许新建'; exit 0 }",
286
+ "$dir = [IO.Path]::GetDirectoryName($P)",
287
+ "if (-not $dir) { $dir = '.' }",
288
+ "if (-not (Test-Path -LiteralPath $dir)) { Die 'FS_NOT_FOUND' '目标目录不存在'; exit 0 }",
289
+ "$tmp = Join-Path $dir ([IO.Path]::GetRandomFileName())",
290
+ "try {",
291
+ " [IO.File]::WriteAllBytes($tmp, $data)",
292
+ "} catch [System.UnauthorizedAccessException] {",
293
+ " Die 'FS_PERMISSION_DENIED' '没有权限'; exit 0",
294
+ "} catch {",
295
+ " Die 'FS_IO_ERROR' $_.Exception.Message; exit 0",
296
+ "}",
297
+ "try {",
298
+ " if (Test-Path -LiteralPath $P) { throw 'exists' }",
299
+ " Move-Item -LiteralPath $tmp -Destination $P -ErrorAction Stop",
300
+ "} catch {",
301
+ " Remove-Item -LiteralPath $tmp -ErrorAction SilentlyContinue",
302
+ " Die 'FS_NOT_OBSERVED' '文件已存在,write_remote 只允许新建'; exit 0",
303
+ "}",
304
+ "'OK'"
305
+ ].join("\n");
306
+ await sshOutcome(alias, env, action, {}, { script: winScript, input: data });
307
+ this.observed.set(target.targetKey, this.fingerprint(content));
208
308
  return { operation: "create", version: FsVersion("w:" + Date.now()), before: null, after: content };
209
309
  }
210
310
  /** 编辑后写回,整块替换。 */
211
311
  async writeReplaced(target, content) {
212
312
  const { alias, rawPath } = splitAliasPath(target.displayPath);
213
- const env = { RM_PATH: b64(rawPath), RM_DATA: b64(content) };
313
+ const env = { RM_PATH: b64(rawPath) };
314
+ const data = b64(content);
214
315
  const action = [
215
316
  PATH_PROLOGUE,
216
317
  DIE_HELPER,
318
+ 'D=$(dirname "$P")',
319
+ 'if [ ! -d "$D" ]; then die FS_NOT_FOUND 目标目录不存在; exit 0; fi',
217
320
  'TMP=$(mktemp "$P.XXXXXXXX.tmp" 2>/dev/null)',
218
321
  'if [ -z "$TMP" ]; then die FS_IO_ERROR 无法创建临时文件; exit 0; fi',
219
- 'printf %s "$(b64d "$RM_DATA")" > "$TMP" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR 写入临时文件失败; exit 0; }',
322
+ `printf %s '${data}' | base64 -d > "$TMP" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR 写入临时文件失败; exit 0; }`,
220
323
  'if [ ! -e "$P" ]; then rm -f "$TMP"; die FS_NOT_FOUND 文件已不存在; exit 0; fi',
324
+ 'if [ ! -w "$P" ]; then rm -f "$TMP"; die FS_PERMISSION_DENIED 文件不可写; exit 0; fi',
221
325
  'mv -f "$TMP" "$P" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR mv 替换失败; exit 0; }',
222
326
  "echo OK"
223
327
  ].join("\n");
224
- await sshOutcome(alias, env, action);
328
+ const winScript = [
329
+ winPrologue(rawPath),
330
+ "$in = [Console]::In.ReadToEnd()",
331
+ "$data = [Convert]::FromBase64String($in.Trim())",
332
+ "$dir = [IO.Path]::GetDirectoryName($P)",
333
+ "if (-not $dir) { $dir = '.' }",
334
+ "if (-not (Test-Path -LiteralPath $dir)) { Die 'FS_NOT_FOUND' '目标目录不存在'; exit 0 }",
335
+ "$tmp = Join-Path $dir ([IO.Path]::GetRandomFileName())",
336
+ "try {",
337
+ " [IO.File]::WriteAllBytes($tmp, $data)",
338
+ "} catch [System.UnauthorizedAccessException] {",
339
+ " Die 'FS_PERMISSION_DENIED' '没有权限'; exit 0",
340
+ "} catch {",
341
+ " Die 'FS_IO_ERROR' $_.Exception.Message; exit 0",
342
+ "}",
343
+ "if (-not (Test-Path -LiteralPath $P)) { Remove-Item -LiteralPath $tmp -ErrorAction SilentlyContinue; Die 'FS_NOT_FOUND' '文件已不存在'; exit 0 }",
344
+ "try { Move-Item -LiteralPath $tmp -Destination $P -Force -ErrorAction Stop } catch { Remove-Item -LiteralPath $tmp -ErrorAction SilentlyContinue; Die 'FS_IO_ERROR' '替换失败'; exit 0 }",
345
+ "'OK'"
346
+ ].join("\n");
347
+ await sshOutcome(alias, env, action, {}, { script: winScript, input: data });
225
348
  return content;
226
349
  }
227
350
  /**
@@ -233,6 +356,10 @@ var RemoteFileSystem = class {
233
356
  const { alias, rawPath } = splitAliasPath(target.displayPath);
234
357
  const readTarget = { targetKey: target.targetKey, displayPath: alias + ":" + rawPath };
235
358
  const before = await this.readTextLocked(readTarget);
359
+ const seen = this.observed.get(target.targetKey);
360
+ if (seen !== void 0 && seen !== this.fingerprint(before)) {
361
+ throw new FsError2("文件在读取后已被修改,请先重新读取再编辑", "FS_STALE_VERSION");
362
+ }
236
363
  const matches = this.countMatches(before, edit.oldString);
237
364
  if (matches === 0) throw new FsError2("old_string 未命中任何匹配", "FS_EDIT_NOT_FOUND");
238
365
  if (!edit.replaceAll && matches > 1) {
@@ -240,6 +367,7 @@ var RemoteFileSystem = class {
240
367
  }
241
368
  const after = edit.replaceAll ? before.split(edit.oldString).join(edit.newString) : before.replace(edit.oldString, edit.newString);
242
369
  await this.writeReplaced(readTarget, after);
370
+ this.observed.set(target.targetKey, this.fingerprint(after));
243
371
  return { version: FsVersion("e:" + Date.now()), before, after };
244
372
  });
245
373
  }
package/lib/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/remote-fs.ts", "../src/protocol.ts", "../src/read.ts", "../src/render.ts", "../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js", "../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/line.js", "../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/patch/create.js", "../src/write.ts", "../src/edit.ts", "../src/rpc-channel.ts", "../src/rpc.ts", "../src/index.ts"],
4
- "sourcesContent": ["/**\n * RemoteFileSystem — 远程文件后端层。\n *\n * 刻意不继承 @deepseek-ai/dsh-fs 的 FileSystem 与 cordis Service。\n * FileSystem 是 Service 子类,构造即注册为 ctx.fs,继承会污染会话级 ctx.fs,实测见 DESIGN §9.2。\n * 这里只借用 dsh-fs 的类型词表与 FsError,方法面同构 FileSystem,仅实现远程读写用到的子集。\n *\n * 实现走 ssh:host 进程内对同一 targetKey 加 per-target 锁,串行本 agent 读改写;远端发布用「同目录临时文件 + mv」近似原子,createIfAbsent 用 no-clobber 近似。\n * 原子性只保证 host 进程内与单条 ssh 命令内,跨连接与跨主机并发覆盖为已接受局限,见 DESIGN §7。\n */\n\nimport {\n FsError,\n FsTargetKey,\n FsVersion,\n} from '@deepseek-ai/dsh-fs'\nimport type {\n FsEditRequest,\n FsEditOutcome,\n FsInfo,\n FsTarget,\n FsVersion as FsVersionType,\n FsWriteIntent,\n FsWriteOutcome,\n} from '@deepseek-ai/dsh-fs'\nimport { b64, PATH_PROLOGUE, DIE_HELPER, B64_HELPER, sshOutcome } from './protocol.ts'\n\n/** 每 targetKey 串行队列,维护每个 key 的尾 Promise。 */\nconst TAILS = new Map<string, Promise<unknown>>()\nasync function withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prev = TAILS.get(key) ?? Promise.resolve()\n const run = prev.then(() => fn())\n TAILS.set(key, run.catch(() => {}))\n return run\n}\n\nconst ALIAS_RE = /^[A-Za-z0-9._-]+$/\n\n/** 解析「别名:路径」。路径须绝对或 ~/ 开头,裸相对路径拒绝。 */\nexport function splitAliasPath(filePath: string): { alias: string; rawPath: string } {\n const idx = filePath.indexOf(':')\n if (idx <= 0) throw new FsError('file_path 须为「别名:路径」形式,缺少别名前缀:' + filePath, 'FS_IO_ERROR')\n const alias = filePath.slice(0, idx)\n const rawPath = filePath.slice(idx + 1)\n if (!ALIAS_RE.test(alias)) throw new FsError('非法的别名前缀:' + alias, 'FS_IO_ERROR')\n if (!(rawPath.startsWith('/') || rawPath.startsWith('~/'))) {\n throw new FsError('远程路径只接受绝对路径或 ~/ 开头,收到相对路径:' + rawPath, 'FS_IO_ERROR')\n }\n return { alias, rawPath }\n}\n\n/**\n * RemoteFileSystem 后端。普通类,不继承 FileSystem 与 cordis Service,不注册服务。\n */\nexport class RemoteFileSystem {\n /** 单次读取字节上限,超限拒绝且不做流式,见 DESIGN §7 资源上限。 */\n readonly readMaxBytes = 50 * 1024 * 1024\n\n /** 解析「别名:路径」为稳定 FsTarget。targetKey 含 alias 与 rawPath,displayPath 即原名。 */\n async resolve(path: string): Promise<FsTarget> {\n const { alias, rawPath } = splitAliasPath(path)\n const displayPath = alias + ':' + rawPath\n return { targetKey: FsTargetKey(alias + '::' + rawPath), displayPath }\n }\n\n /** FileSystem 同形占位:无进程内路径,返回 displayPath。 */\n processPath(target: FsTarget): string { return target.displayPath }\n\n /** stat:类型 + size + 版本依据,缺失返回 undefined。版本只作 host 进程内陈旧排序。 */\n async stat(target: FsTarget): Promise<FsInfo | undefined> {\n return withLock(target.targetKey, () => this.statLocked(target))\n }\n\n private async statLocked(target: FsTarget): Promise<FsInfo | undefined> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n const env = { RM_PATH: b64(rawPath) }\n // 存在与否与类型判定在远端一次完成,把结果打进 stdout,失败则 die\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'if [ ! -e \"$P\" ] && [ ! -L \"$P\" ]; then echo ABSENT; exit 0; fi',\n 'if [ -L \"$P\" ]; then echo OTHER; exit 0; fi',\n 'if [ -d \"$P\" ]; then echo DIR; exit 0; fi',\n 'if [ ! -f \"$P\" ]; then echo OTHER; exit 0; fi',\n 'SIZE=$(wc -c < \"$P\" 2>/dev/null | tr -d \" \")',\n 'MT=$(stat -c %Y \"$P\" 2>/dev/null || echo 0)',\n 'echo \"FILE:$SIZE:$MT\"',\n ].join('\\n')\n const out = await sshOutcome(alias, env, action)\n const s = out.stdout.trim()\n if (s === 'ABSENT') return undefined\n if (s === 'DIR') return { version: FsVersion('dir:' + Date.now()), type: 'directory' }\n if (s === 'OTHER') return { version: FsVersion('other:' + Date.now()), type: 'other' }\n const m = /^FILE:(\\d*):(\\d*)$/.exec(s)\n const size = m && m[1] !== '' ? Number(m[1]) : undefined\n const mtime = m && m[2] !== '' ? m[2] : '0'\n return {\n version: FsVersion('m:' + mtime + (size !== undefined ? ':' + size : '')),\n type: 'file',\n ...(size !== undefined ? { size } : {}),\n }\n }\n\n /** 读整文件 utf8。缺失归 FS_NOT_FOUND,非普通归 FS_NOT_REGULAR_FILE,超限归 FS_TOO_LARGE。 */\n async readText(target: FsTarget): Promise<string> {\n return withLock(target.targetKey, () => this.readTextLocked(target))\n }\n\n private async readTextLocked(target: FsTarget): Promise<string> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n const env = { RM_PATH: b64(rawPath), RM_MAX: String(this.readMaxBytes) }\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'if [ ! -e \"$P\" ] && [ ! -L \"$P\" ]; then die FS_NOT_FOUND 文件不存在; exit 0; fi',\n 'if [ -d \"$P\" ]; then die FS_NOT_REGULAR_FILE 目标是一个目录; exit 0; fi',\n 'if [ ! -f \"$P\" ]; then die FS_NOT_REGULAR_FILE 目标不是普通文件; exit 0; fi',\n 'SIZE=$(wc -c < \"$P\" 2>/dev/null | tr -d \" \")',\n 'if [ -n \"$SIZE\" ] && [ \"$SIZE\" -gt \"$RM_MAX\" ] 2>/dev/null; then die FS_TOO_LARGE 文件超过单次读取上限,不做流式; exit 0; fi',\n 'base64 -w0 < \"$P\" 2>/dev/null || die FS_IO_ERROR 读取内容失败',\n ].join('\\n')\n const out = await sshOutcome(alias, env, action)\n return Buffer.from(out.stdout.replace(/\\s+$/u, ''), 'base64').toString('utf8')\n }\n\n /** readBytes:raw 读取,文本只读,返回 utf8 字节。 */\n async readBytes(target: FsTarget, maxBytes: number): Promise<Uint8Array> {\n const text = await this.readText(target)\n const buf = Buffer.from(text, 'utf8')\n if (buf.byteLength > maxBytes) throw new FsError('超出字节上限', 'FS_TOO_LARGE')\n return buf\n }\n\n /**\n * 全文件写,恒用 createIfAbsent,只新建。目标已存在即 FS_NOT_OBSERVED,绝无覆写。\n * 只接受 createIfAbsent 意图,其他拒绝。\n */\n async writeText(target: FsTarget, content: string, expected?: FsWriteIntent): Promise<FsWriteOutcome> {\n return withLock(target.targetKey, () => this.writeTextLocked(target, content, expected))\n }\n\n private async writeTextLocked(target: FsTarget, content: string, expected?: FsWriteIntent): Promise<FsWriteOutcome> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n if (expected !== undefined && expected.kind !== 'createIfAbsent') {\n throw new FsError('write_remote 只允许新建,仅支持 createIfAbsent 意图', 'FS_NOT_OBSERVED')\n }\n const env = { RM_PATH: b64(rawPath), RM_DATA: b64(content) }\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'TMP=$(mktemp \"$P.XXXXXXXX.tmp\" 2>/dev/null)',\n 'if [ -z \"$TMP\" ]; then die FS_IO_ERROR 无法创建临时文件,目录可能不可写; exit 0; fi',\n 'printf %s \"$(b64d \"$RM_DATA\")\" > \"$TMP\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR 写入临时文件失败; exit 0; }',\n 'if [ -e \"$P\" ] || [ -L \"$P\" ]; then rm -f \"$TMP\"; die FS_NOT_OBSERVED 文件已存在,write_remote 只允许新建; exit 0; fi',\n 'mv -f \"$TMP\" \"$P\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR mv 发布失败; exit 0; }',\n 'echo OK',\n ].join('\\n')\n await sshOutcome(alias, env, action)\n return { operation: 'create', version: FsVersion('w:' + Date.now()), before: null, after: content }\n }\n\n /** 编辑后写回,整块替换。 */\n private async writeReplaced(target: FsTarget, content: string): Promise<string> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n const env = { RM_PATH: b64(rawPath), RM_DATA: b64(content) }\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'TMP=$(mktemp \"$P.XXXXXXXX.tmp\" 2>/dev/null)',\n 'if [ -z \"$TMP\" ]; then die FS_IO_ERROR 无法创建临时文件; exit 0; fi',\n 'printf %s \"$(b64d \"$RM_DATA\")\" > \"$TMP\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR 写入临时文件失败; exit 0; }',\n 'if [ ! -e \"$P\" ]; then rm -f \"$TMP\"; die FS_NOT_FOUND 文件已不存在; exit 0; fi',\n 'mv -f \"$TMP\" \"$P\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR mv 替换失败; exit 0; }',\n 'echo OK',\n ].join('\\n')\n await sshOutcome(alias, env, action)\n return content\n }\n\n /**\n * 字面编辑:读原文 → host 内唯一匹配或全替换 → 写回。匹配类错误走原生词表,即 FS_AMBIGUOUS_EDIT 与 FS_EDIT_NOT_FOUND;远端失败归远程失败类。\n * 整块读改写,保留远端换行。\n */\n async editText(\n target: FsTarget,\n edit: FsEditRequest,\n _expected?: { version: FsVersionType } | undefined,\n ): Promise<FsEditOutcome> {\n return withLock(target.targetKey, async () => {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n // 先读原文,锁内读改写临界区,缺失即 FS_NOT_FOUND\n const readTarget: FsTarget = { targetKey: target.targetKey, displayPath: alias + ':' + rawPath }\n const before = await this.readTextLocked(readTarget)\n const matches = this.countMatches(before, edit.oldString)\n if (matches === 0) throw new FsError('old_string 未命中任何匹配', 'FS_EDIT_NOT_FOUND')\n if (!edit.replaceAll && matches > 1) {\n throw new FsError('old_string 出现 ' + matches + ' 次,需更精确或设 replace_all', 'FS_AMBIGUOUS_EDIT')\n }\n const after = edit.replaceAll\n ? before.split(edit.oldString).join(edit.newString)\n : before.replace(edit.oldString, edit.newString)\n await this.writeReplaced(readTarget, after)\n return { version: FsVersion('e:' + Date.now()), before, after }\n })\n }\n\n private countMatches(text: string, oldString: string): number {\n if (oldString === '') return 0\n let count = 0\n let idx = text.indexOf(oldString)\n while (idx !== -1) { count++; idx = text.indexOf(oldString, idx + oldString.length) }\n return count\n }\n}\n", "/**\n * 远端执行协议:host 进程内用子进程 ssh 对远程主机执行一条 bash 脚本。\n *\n * 可变数据即路径与写内容,一律 base64 编码,经环境变量传给远端,值用单引号包裹。\n * base64 只含 [A-Za-z0-9+/=],不夹带单引号或换行,从根上避开 shell 引号转义与注入。\n * 远端统一用 bash -s 从 stdin 读脚本,字节经 base64 传输,不受本地 shell 方言约束。\n *\n * 结果约定:脚本成功写 stdout,内容是 base64 或留空;失败向 stderr 打一行 __ERR__<code>__<message> 并 exit 1。\n * 本地据此归 FsError,保留远端中文详情。\n */\n\nimport { spawn } from 'node:child_process'\nimport { FsError } from '@deepseek-ai/dsh-fs'\n\nconst ERR_PREFIX = '__ERR__'\n\n/** base64 编码。 */\nexport function b64(input: string): string { return Buffer.from(input, 'utf8').toString('base64') }\n/** base64 解码为 utf8。 */\nexport function b64d(input: string): string { return Buffer.from(input, 'base64').toString('utf8') }\n\n/**\n * 在远端 bash 里解码 RM_PATH 环境变量并做 ~/ 展开。返回一段可拼进脚本前部的代码。\n * 注:不直接内嵌复杂 bash,只用最简 case,~/ 展开交给远端 $HOME。\n */\nexport const PATH_PROLOGUE = [\n 'set +e',\n 'b64d() { printf %s \"$1\" | base64 -d 2>/dev/null; }',\n 'P=$(b64d \"$RM_PATH\")',\n 'case \"$P\" in \"~\"*) eval \"P=\\\"$P\\\"\" ;; esac',\n].join('\\n')\n\n/** 拼出远端命令行的 env 前缀:VAR='<b64>' VAR2=... bash -s。 */\nexport function envPrefix(env: Record<string, string>): string {\n return Object.entries(env)\n .map(([k, v]) => k + \"='\" + v + \"'\")\n .join(' ') + ' bash -s'\n}\n\n/** 一次 ssh 调用的输出。 */\nexport interface SshRawResult { exitCode: number | null; stdout: string; stderr: string }\n\n/**\n * 执行远端脚本。env 提供 base64 数据变量,脚本主体写进 stdin。\n * 返回原始 ssh 输出,未做成功与失败判定。\n */\nexport async function sshExec(\n alias: string,\n env: Record<string, string>,\n scriptBody: string,\n opts: { timeoutMs?: number } = {},\n): Promise<SshRawResult> {\n const timeoutMs = opts.timeoutMs ?? 30_000\n return await new Promise<SshRawResult>((resolve, reject) => {\n const child = spawn('ssh', [\n '-o', 'BatchMode=yes',\n '-o', 'ConnectTimeout=' + Math.max(1, Math.floor(timeoutMs / 1000)),\n alias,\n envPrefix(env),\n ], { stdio: ['pipe', 'pipe', 'pipe'] })\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let settled = false\n const timer = setTimeout(() => { timedOut = true; child.kill('SIGKILL') }, timeoutMs)\n child.stdout.setEncoding('utf8')\n child.stderr.setEncoding('utf8')\n child.stdout.on('data', (d: string) => { stdout += d })\n child.stderr.on('data', (d: string) => { stderr += d })\n child.on('error', (err) => {\n if (settled) return; settled = true; clearTimeout(timer)\n reject(new Error('ssh 连接失败:' + err.message))\n })\n child.on('close', (code) => {\n if (settled) return; settled = true; clearTimeout(timer)\n resolve({ exitCode: timedOut ? -1 : code, stdout, stderr })\n })\n child.stdin.write(scriptBody)\n child.stdin.end()\n })\n}\n\n/**\n * 执行并把结果翻译成本地约定:exit 0 视为成功,返回 stdout 文本;否则解析 stderr 首行 __ERR__<code>__<message> 归对应 FsError,找不到则统一 FS_IO_ERROR 并带详情。\n * 超时即 exit -1,归 FS_IO_ERROR。\n */\nexport async function sshOutcome(\n alias: string,\n env: Record<string, string>,\n scriptBody: string,\n opts: { timeoutMs?: number } = {},\n): Promise<{ code: string; stdout: string }> {\n const raw = await sshExec(alias, env, scriptBody, opts).catch((e: Error) => {\n throw new FsError(e.message, 'FS_IO_ERROR')\n })\n if (raw.exitCode === -1) throw new FsError('远程执行超时', 'FS_IO_ERROR')\n if (raw.exitCode === 0) return { code: 'OK', stdout: raw.stdout }\n // 解析 stderr 的 __ERR__ 行\n const line = raw.stderr.split('\\n').find((l: string) => l.startsWith(ERR_PREFIX))\n if (line) {\n const rest = line.slice(ERR_PREFIX.length)\n const sep = rest.indexOf('__')\n const code = sep === -1 ? 'FS_IO_ERROR' : rest.slice(0, sep)\n const message = sep === -1 ? rest : rest.slice(sep + 2)\n throw new FsError(message || '远程执行失败', code === 'FS_IO_ERROR' ? 'FS_IO_ERROR' : (code as never))\n }\n const detail = raw.stderr.trim() || '远程执行失败且无输出'\n throw new FsError(detail, 'FS_IO_ERROR')\n}\n\n/** 远端 bash 失败打一行 __ERR__ 并退出的辅助片段,拼进脚本。 */\nexport const DIE_HELPER = [\n 'die() { local c=\"$1\"; shift; local m=\"$*\"; echo \"__ERR__${c}__${m}\" >&2; exit 1; }',\n].join('\\n')\n\n/** 远端 bash 输出 base64 的辅助。echo_b64() { printf %s \"$1\" | base64 -w0; } */\nexport const B64_HELPER = [\n 'b64out() { printf %s \"$1\" | base64 -w0 2>/dev/null; }',\n].join('\\n')\n", "/**\n * read_remote 工具定义。语义逐字移植自官方 read 与 read-render:行号分页、offset、limit、totalLines、幻影空行处理。\n * 唯一差异是走 RemoteFileSystem,即 ssh,且 file_path 支持「别名:路径」前缀。\n */\n\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { GenericCallView, ReadResultView, ToolResult } from '@deepseek-ai/dsh-tools'\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport {\n buildWindow,\n formatReadOutput,\n langFromPath,\n readMetaFromMeta,\n} from './render.ts'\n\n/** 读行上限、单行字符上限与输出字节上限,与原生 read 默认一致。 */\nexport const READ_LIMIT = 2000\nexport const READ_MAX_LINE_LENGTH = 2000\nexport const READ_MAX_BYTES = 50 * 1024\n\nfunction parsePositiveInteger(value: number, name: string): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {\n throw new Error(name + ' must be a positive integer')\n }\n return value\n}\n\ninterface ReadInput { filePath: string; offset: number; limit: number }\n\n/** 校验 read_remote 参数:空 file_path 拒绝,offset 与 limit 须为正整数。 */\nexport function parseReadArgs(args: { file_path: string; offset?: number; limit?: number }): ReadInput {\n if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')\n const offset = args.offset === undefined ? 1 : parsePositiveInteger(args.offset, 'offset')\n const limit = args.limit === undefined ? READ_LIMIT : parsePositiveInteger(args.limit, 'limit')\n if (limit > READ_LIMIT) throw new Error('limit must be less than or equal to ' + READ_LIMIT)\n return { filePath: args.file_path, offset, limit }\n}\n\n/** 构建 read_remote 的 defineTool 对象。 */\nexport function createReadRemoteTool(fs: RemoteFileSystem): ReturnType<typeof defineTool> {\n return defineTool({\n name: 'read_remote',\n description: 'Read a UTF-8 text file on a remote host over ssh and return line-numbered content.',\n parameters: {\n file_path: { type: 'string', required: true, description: 'Remote target and path in one: alias:path, e.g. wsl:~/x.md or myserver:/etc/hosts. Alias comes from your ssh config.' },\n offset: { type: 'number', description: '1-based first line to return. Defaults to 1.' },\n limit: { type: 'number', description: 'Maximum number of lines to return. Defaults to ' + READ_LIMIT + '.' },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n path: { type: 'string', required: true },\n offset: { type: 'integer', required: true },\n lines: {\n type: 'array',\n required: true,\n items: { type: 'object', additionalProperties: false, properties: { number: { type: 'integer', required: true }, text: { type: 'string', required: true } } },\n },\n totalLines: { type: 'integer', required: true },\n },\n },\n render: (args, value) => {\n const input = parseReadArgs(args as { file_path: string; offset?: number; limit?: number })\n const endLine = value.lines.at(-1)?.number ?? Math.max(0, value.offset - 1)\n const truncatedByBytes = value.lines.length < input.limit && endLine < value.totalLines\n return [{ type: 'text', text: formatReadOutput(value.path, { offset: value.offset, lines: value.lines, totalLines: value.totalLines, ...(truncatedByBytes ? { truncatedByBytes: true } : {}) }) }]\n },\n presentationMeta: (_args, value) => {\n const lang = langFromPath(value.path)\n return { path: value.path, offset: value.offset, lines: value.lines.map(({ number, text }) => ({ number, text })), totalLines: value.totalLines, ...(lang === undefined ? {} : { lang }) }\n },\n },\n isConcurrencySafe: () => true,\n async execute(args, exec) {\n const input = parseReadArgs(args as { file_path: string; offset?: number; limit?: number })\n if (exec.signal.aborted) throw new FsError('cancelled', 'FS_ABORTED')\n const target = await fs.resolve(input.filePath)\n const text = await fs.readText(target)\n const window = buildWindow(text, { offset: input.offset, limit: input.limit, maxLineLength: READ_MAX_LINE_LENGTH, maxBytes: READ_MAX_BYTES }, target.displayPath)\n return { path: target.displayPath, offset: input.offset, lines: window.lines, totalLines: window.totalLines }\n },\n presentResult(_args, result: ToolResult): ReadResultView | undefined {\n if (result.isError) return undefined\n const meta = readMetaFromMeta(result.meta)\n if (meta === undefined) return undefined\n const only = result.content.length === 1 ? result.content[0] : undefined\n const text = only?.type === 'text' ? only.text : undefined\n if (text === undefined) return undefined\n const body = /^<path>[^\\n]*<\\/path>\\n<type>file<\\/type>\\n<content>\\n([\\s\\S]*)\\n<\\/content>$/u.exec(text)?.[1]\n if (body === undefined) return undefined\n return { card: 'read', path: meta.path, offset: meta.offset, lines: meta.lines, totalLines: meta.totalLines, ...(meta.lang === undefined ? {} : { lang: meta.lang }), content: [{ type: 'text', text: body }] }\n },\n presentCall(args): GenericCallView {\n const a = args as { file_path: string; offset?: number; limit?: number }\n const windowText = a.limit !== undefined && a.limit > 0\n ? ' (' + (a.offset ?? 1) + ' - ' + ((a.offset ?? 1) + a.limit - 1) + ')'\n : a.offset !== undefined ? ' (from line ' + a.offset + ')' : ''\n return { card: 'generic', title: 'Read ' + a.file_path + windowText, kind: 'read', locations: [{ path: a.file_path, line: a.offset ?? 1 }] }\n },\n })\n}", "/**\n * 远程工具的纯渲染辅助:读窗口、模型文本信封、diff hunk 卡片与 read 卡 meta。\n * 语义逐字移植自官方 @deepseek-ai/dsh-tool-fs,覆盖 read-render、diff、read、write、edit;不引该包,因其不导出这些,只为保持与原生卡片能力一致。\n */\n\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { FileDiff } from '@deepseek-ai/dsh-tools'\nimport { structuredPatch } from 'diff'\n\n/** 单个 read 行。 */\nexport interface FileTextLine { number: number; text: string }\n/** read 窗口请求。 */\nexport interface ReadWindowRequest {\n offset: number;\n limit: number;\n maxLineLength: number;\n maxBytes: number;\n}\n/** 读窗口结果。 */\nexport interface WindowResult {\n lines: FileTextLine[];\n totalLines: number;\n truncatedByBytes: boolean;\n}\n\nfunction truncateLine(line: string, maxLineLength: number): string {\n return line.length > maxLineLength\n ? line.substring(0, maxLineLength) + '... (line truncated to ' + maxLineLength + ' chars)'\n : line\n}\nfunction stripCarriageReturn(line: string): string {\n return line.endsWith('\\r') ? line.slice(0, -1) : line\n}\n\n/**\n * 从整文件文本切出带行号的窗口。幻影空行与末尾无换行按原生语义处理,offset 越界抛 FS_NOT_FOUND。\n */\nexport function buildWindow(text: string, request: ReadWindowRequest, displayPath: string): WindowResult {\n const linesOut: FileTextLine[] = []\n let totalLines = 0\n let truncatedByBytes = false\n let outputBytes = 0\n\n // 逐行扫描:先精确统计 totalLines,同时收集窗口。\n // 解析规则:按 \\n 切分,文件以换行结尾时不追加尾随空行,空文件为 0 行;\n // 无换行的残余也计为一行。与原生逐 chunk 扫描等价。\n const parts = text.split('\\n')\n const hasTrailingNewline = text.endsWith('\\n')\n const meaningful = hasTrailingNewline ? parts.slice(0, -1) : parts\n // 空文件:split 得空串且无末尾换行,故为 0 行\n if (text.length === 0) {\n return { lines: [], totalLines: 0, truncatedByBytes: false }\n }\n totalLines = meaningful.length\n for (let i = 0; i < meaningful.length; i += 1) {\n const raw = meaningful[i]\n if (truncatedByBytes) break\n const lineNumber = i + 1\n if (lineNumber < request.offset || linesOut.length >= request.limit) continue\n const shown = truncateLine(stripCarriageReturn(raw), request.maxLineLength)\n const bytes = Buffer.byteLength(shown, 'utf8') + (linesOut.length > 0 ? 1 : 0)\n if (outputBytes + bytes > request.maxBytes) { truncatedByBytes = true; break }\n outputBytes += bytes\n linesOut.push({ number: lineNumber, text: shown })\n }\n if (!truncatedByBytes && request.offset > totalLines && !(totalLines === 0 && request.offset === 1)) {\n throw new FsError('offset ' + request.offset + ' is out of range for \"' + displayPath + '\" (' + totalLines + ' lines)', 'FS_NOT_FOUND')\n }\n return { lines: linesOut, totalLines, truncatedByBytes }\n}\n/** 读结果模型信封正文。 */\nexport interface FileReadOutcome {\n offset: number;\n lines: FileTextLine[];\n totalLines: number;\n truncatedByBytes?: true;\n}\n/** 组装 read 的模型可见信封文本,含行号与结尾说明。 */\nexport function formatReadOutput(displayPath: string, outcome: FileReadOutcome): string {\n const endLine = outcome.lines.at(-1)?.number ?? Math.max(0, outcome.offset - 1)\n let footer: string\n if (outcome.truncatedByBytes) {\n footer = '(Output capped. Showing lines ' + outcome.offset + '-' + endLine + '. Use offset=' + (endLine + 1) + ' to continue.)'\n } else if (endLine < outcome.totalLines) {\n footer = '(Showing lines ' + outcome.offset + '-' + endLine + ' of ' + outcome.totalLines + '. Use offset=' + (endLine + 1) + ' to continue.)'\n } else {\n footer = '(End of file - total ' + outcome.totalLines + ' lines)'\n }\n const body = outcome.lines.length > 0\n ? outcome.lines.map((line) => line.number + ': ' + line.text).join('\\n') + '\\n\\n' + footer\n : footer\n return '<path>' + displayPath + '</path>\\n<type>file</type>\\n<content>\\n' + body + '\\n</content>'\n}\n\n/** 从路径推断高亮语言。 */\nconst LANG_BY_EXTENSION: Record<string, string> = {\n ts:'ts', tsx:'tsx', mts:'ts', cts:'ts', js:'js', jsx:'jsx', mjs:'js', cjs:'js',\n json:'json', jsonc:'json', py:'py', rb:'rb', go:'go', rs:'rs', java:'java',\n c:'c', h:'c', cc:'cpp', cpp:'cpp', hpp:'cpp', cxx:'cpp', cs:'cs', kt:'kotlin',\n swift:'swift', php:'php', sh:'sh', bash:'sh', zsh:'sh', yaml:'yaml', yml:'yaml',\n toml:'toml', ini:'ini', md:'md', markdown:'md', mdx:'mdx', html:'html', htm:'html',\n css:'css', scss:'scss', less:'less', sql:'sql', xml:'xml', lua:'lua',\n}\n/** 由路径扩展名取语言提示。 */\nexport function langFromPath(path: string): string | undefined {\n const base = path.slice(Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\\\')) + 1)\n const dot = base.lastIndexOf('.')\n if (dot <= 0) return undefined\n const ext = base.slice(dot + 1).toLowerCase()\n return Object.hasOwn(LANG_BY_EXTENSION, ext) ? LANG_BY_EXTENSION[ext] : undefined\n}\n\n/** read 卡的可回放 meta。 */\nexport interface FsReadMeta {\n path: string;\n offset: number;\n lines: FileTextLine[];\n totalLines: number;\n lang?: string;\n}\n/** 从回放 meta 收紧为合法 read 窗口,不合法返回 undefined 走通用卡。 */\nexport function readMetaFromMeta(meta: unknown): FsReadMeta | undefined {\n if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return undefined\n const m = meta as Record<string, unknown>\n const { path, offset, lines, totalLines, lang } = m\n if (typeof path !== 'string' || typeof totalLines !== 'number' || typeof offset !== 'number') return undefined\n if (!Number.isInteger(offset) || offset < 1) return undefined\n if (!Number.isInteger(totalLines) || totalLines < 0) return undefined\n if (!Array.isArray(lines) || !lines.every((l) => l !== null && typeof l === 'object'\n && !Array.isArray(l) && typeof (l as { number?: unknown }).number === 'number'\n && Number.isInteger((l as { number: number }).number) && (l as { number: number }).number >= 1\n && typeof (l as { text?: unknown }).text === 'string')) return undefined\n if (lang !== undefined && typeof lang !== 'string') return undefined\n const typed = lines as FileTextLine[]\n let prev = offset - 1\n for (const { number } of typed) { if (number <= prev || number > totalLines) return undefined; prev = number }\n return { path, offset, lines: typed, totalLines, ...(lang === undefined ? {} : { lang }) }\n}\n\n/** write 与 edit 结果卡 meta,diffs 数组。 */\nexport type FsDiffMeta = { diffs: FileDiff[] }\n\n/** 计算 before 与 after 的 hunk diff,3 行上下文,供结果卡展示。 */\nexport function computeHunkDiffs(path: string, before: string, after: string): FileDiff[] {\n const patch = structuredPatch('', '', before, after, undefined, undefined, { context: 3 })\n const diffs: FileDiff[] = []\n for (const hunk of patch.hunks) {\n const oldLines: string[] = []\n const newLines: string[] = []\n for (const line of hunk.lines) {\n if (line.startsWith('\\\\')) continue\n const text = line.slice(1)\n if (line.startsWith('-')) oldLines.push(text)\n else if (line.startsWith('+')) newLines.push(text)\n else { oldLines.push(text); newLines.push(text) }\n }\n diffs.push({ path, oldText: oldLines.length > 0 ? oldLines.join('\\n') : null, newText: newLines.join('\\n') })\n }\n return diffs\n}\n\n/** 从回放 meta 收紧为合法 diffs,不合法返回 undefined。 */\nexport function diffsFromMeta(meta: unknown): FileDiff[] | undefined {\n if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return undefined\n const diffs = (meta as Record<string, unknown>).diffs\n if (!Array.isArray(diffs) || diffs.length === 0) return undefined\n const valid = diffs.every((d) => d !== null && typeof d === 'object' && !Array.isArray(d)\n && typeof (d as { path?: unknown }).path === 'string'\n && ((d as { oldText?: unknown }).oldText === null || typeof (d as { oldText?: unknown }).oldText === 'string')\n && typeof (d as { newText?: unknown }).newText === 'string')\n return valid ? diffs as FileDiff[] : undefined\n}\n\n/** write 成功信封。 */\nexport function formatWriteOutput(displayPath: string, operation: 'create' | 'update'): string {\n const verb = operation === 'create' ? 'Created' : 'Updated'\n return '<path>' + displayPath + '</path>\\n<type>file</type>\\n<content>\\n' + verb + ' file\\n</content>'\n}\n\n/** edit 成功信封。 */\nexport function formatEditOutput(displayPath: string, replaceAll: boolean): string {\n return replaceAll\n ? 'The file ' + displayPath + ' has been updated. All occurrences were successfully replaced.'\n : 'The file ' + displayPath + ' has been updated successfully.'\n}\n", "export default class Diff {\n diff(oldStr, newStr, \n // Type below is not accurate/complete - see above for full possibilities - but it compiles\n options = {}) {\n let callback;\n if (typeof options === 'function') {\n callback = options;\n options = {};\n }\n else if ('callback' in options) {\n callback = options.callback;\n }\n // Allow subclasses to massage the input prior to running\n const oldString = this.castInput(oldStr, options);\n const newString = this.castInput(newStr, options);\n const oldTokens = this.removeEmpty(this.tokenize(oldString, options));\n const newTokens = this.removeEmpty(this.tokenize(newString, options));\n return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);\n }\n diffWithOptionsObj(oldTokens, newTokens, options, callback) {\n var _a;\n const done = (value) => {\n value = this.postProcess(value, options);\n if (callback) {\n setTimeout(function () { callback(value); }, 0);\n return undefined;\n }\n else {\n return value;\n }\n };\n const newLen = newTokens.length, oldLen = oldTokens.length;\n let editLength = 1;\n let maxEditLength = newLen + oldLen;\n if (options.maxEditLength != null) {\n maxEditLength = Math.min(maxEditLength, options.maxEditLength);\n }\n const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;\n const abortAfterTimestamp = Date.now() + maxExecutionTime;\n const bestPath = [{ oldPos: -1, lastComponent: undefined }];\n // Seed editLength = 0, i.e. the content starts with the same values\n let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);\n if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {\n // Identity per the equality and tokenizer\n return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));\n }\n // Once we hit the right edge of the edit graph on some diagonal k, we can\n // definitely reach the end of the edit graph in no more than k edits, so\n // there's no point in considering any moves to diagonal k+1 any more (from\n // which we're guaranteed to need at least k+1 more edits).\n // Similarly, once we've reached the bottom of the edit graph, there's no\n // point considering moves to lower diagonals.\n // We record this fact by setting minDiagonalToConsider and\n // maxDiagonalToConsider to some finite value once we've hit the edge of\n // the edit graph.\n // This optimization is not faithful to the original algorithm presented in\n // Myers's paper, which instead pointlessly extends D-paths off the end of\n // the edit graph - see page 7 of Myers's paper which notes this point\n // explicitly and illustrates it with a diagram. This has major performance\n // implications for some common scenarios. For instance, to compute a diff\n // where the new text simply appends d characters on the end of the\n // original text of length n, the true Myers algorithm will take O(n+d^2)\n // time while this optimization needs only O(n+d) time.\n let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;\n // Main worker method. checks all permutations of a given edit length for acceptance.\n const execEditLength = () => {\n for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {\n let basePath;\n const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];\n if (removePath) {\n // No one else is going to attempt to use this value, clear it\n // @ts-expect-error - perf optimisation. This type-violating value will never be read.\n bestPath[diagonalPath - 1] = undefined;\n }\n let canAdd = false;\n if (addPath) {\n // what newPos will be after we do an insertion:\n const addPathNewPos = addPath.oldPos - diagonalPath;\n canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;\n }\n const canRemove = removePath && removePath.oldPos + 1 < oldLen;\n if (!canAdd && !canRemove) {\n // If this path is a terminal then prune\n // @ts-expect-error - perf optimisation. This type-violating value will never be read.\n bestPath[diagonalPath] = undefined;\n continue;\n }\n // Select the diagonal that we want to branch from. We select the prior\n // path whose position in the old string is the farthest from the origin\n // and does not pass the bounds of the diff graph\n if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {\n basePath = this.addToPath(addPath, true, false, 0, options);\n }\n else {\n basePath = this.addToPath(removePath, false, true, 1, options);\n }\n newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);\n if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {\n // If we have hit the end of both strings, then we are done\n return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;\n }\n else {\n bestPath[diagonalPath] = basePath;\n if (basePath.oldPos + 1 >= oldLen) {\n maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);\n }\n if (newPos + 1 >= newLen) {\n minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);\n }\n }\n }\n editLength++;\n };\n // Performs the length of edit iteration. Is a bit fugly as this has to support the\n // sync and async mode which is never fun. Loops over execEditLength until a value\n // is produced, or until the edit length exceeds options.maxEditLength (if given),\n // in which case it will return undefined.\n if (callback) {\n (function exec() {\n setTimeout(function () {\n if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {\n return callback(undefined);\n }\n if (!execEditLength()) {\n exec();\n }\n }, 0);\n }());\n }\n else {\n while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {\n const ret = execEditLength();\n if (ret) {\n return ret;\n }\n }\n }\n }\n addToPath(path, added, removed, oldPosInc, options) {\n const last = path.lastComponent;\n if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {\n return {\n oldPos: path.oldPos + oldPosInc,\n lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }\n };\n }\n else {\n return {\n oldPos: path.oldPos + oldPosInc,\n lastComponent: { count: 1, added: added, removed: removed, previousComponent: last }\n };\n }\n }\n extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {\n const newLen = newTokens.length, oldLen = oldTokens.length;\n let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;\n while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {\n newPos++;\n oldPos++;\n commonCount++;\n if (options.oneChangePerToken) {\n basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };\n }\n }\n if (commonCount && !options.oneChangePerToken) {\n basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };\n }\n basePath.oldPos = oldPos;\n return newPos;\n }\n equals(left, right, options) {\n if (options.comparator) {\n return options.comparator(left, right);\n }\n else {\n return left === right\n || (!!options.ignoreCase && left.toLowerCase() === right.toLowerCase());\n }\n }\n removeEmpty(array) {\n const ret = [];\n for (let i = 0; i < array.length; i++) {\n if (array[i]) {\n ret.push(array[i]);\n }\n }\n return ret;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n castInput(value, options) {\n return value;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n tokenize(value, options) {\n return Array.from(value);\n }\n join(chars) {\n // Assumes ValueT is string, which is the case for most subclasses.\n // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op)\n // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF\n // assume tokens and values are strings, but not completely - is weird and janky.\n return chars.join('');\n }\n postProcess(changeObjects, \n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n options) {\n return changeObjects;\n }\n get useLongestToken() {\n return false;\n }\n buildValues(lastComponent, newTokens, oldTokens) {\n // First we convert our linked list of components in reverse order to an\n // array in the right order:\n const components = [];\n let nextComponent;\n while (lastComponent) {\n components.push(lastComponent);\n nextComponent = lastComponent.previousComponent;\n delete lastComponent.previousComponent;\n lastComponent = nextComponent;\n }\n components.reverse();\n const componentLen = components.length;\n let componentPos = 0, newPos = 0, oldPos = 0;\n for (; componentPos < componentLen; componentPos++) {\n const component = components[componentPos];\n if (!component.removed) {\n if (!component.added && this.useLongestToken) {\n let value = newTokens.slice(newPos, newPos + component.count);\n value = value.map(function (value, i) {\n const oldValue = oldTokens[oldPos + i];\n return oldValue.length > value.length ? oldValue : value;\n });\n component.value = this.join(value);\n }\n else {\n component.value = this.join(newTokens.slice(newPos, newPos + component.count));\n }\n newPos += component.count;\n // Common case\n if (!component.added) {\n oldPos += component.count;\n }\n }\n else {\n component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));\n oldPos += component.count;\n }\n }\n return components;\n }\n}\n", "import Diff from './base.js';\nimport { generateOptions } from '../util/params.js';\nclass LineDiff extends Diff {\n constructor() {\n super(...arguments);\n this.tokenize = tokenize;\n }\n equals(left, right, options) {\n // If we're ignoring whitespace, we need to normalise lines by stripping\n // whitespace before checking equality. (This has an annoying interaction\n // with newlineIsToken that requires special handling: if newlines get their\n // own token, then we DON'T want to trim the *newline* tokens down to empty\n // strings, since this would cause us to treat whitespace-only line content\n // as equal to a separator between lines, which would be weird and\n // inconsistent with the documented behavior of the options.)\n if (options.ignoreWhitespace) {\n if (!options.newlineIsToken || !left.includes('\\n')) {\n left = left.trim();\n }\n if (!options.newlineIsToken || !right.includes('\\n')) {\n right = right.trim();\n }\n }\n else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {\n if (left.endsWith('\\n')) {\n left = left.slice(0, -1);\n }\n if (right.endsWith('\\n')) {\n right = right.slice(0, -1);\n }\n }\n return super.equals(left, right, options);\n }\n}\nexport const lineDiff = new LineDiff();\nexport function diffLines(oldStr, newStr, options) {\n return lineDiff.diff(oldStr, newStr, options);\n}\nexport function diffTrimmedLines(oldStr, newStr, options) {\n options = generateOptions(options, { ignoreWhitespace: true });\n return lineDiff.diff(oldStr, newStr, options);\n}\n// Exported standalone so it can be used from jsonDiff too.\nexport function tokenize(value, options) {\n if (options.stripTrailingCr) {\n // remove one \\r before \\n to match GNU diff's --strip-trailing-cr behavior\n value = value.replace(/\\r\\n/g, '\\n');\n }\n const retLines = [], linesAndNewlines = value.split(/(\\n|\\r\\n)/);\n // Ignore the final empty token that occurs if the string ends with a new line\n if (!linesAndNewlines[linesAndNewlines.length - 1]) {\n linesAndNewlines.pop();\n }\n // Merge the content and line separators into single tokens\n for (let i = 0; i < linesAndNewlines.length; i++) {\n const line = linesAndNewlines[i];\n if (i % 2 && !options.newlineIsToken) {\n retLines[retLines.length - 1] += line;\n }\n else {\n retLines.push(line);\n }\n }\n return retLines;\n}\n", "import { diffLines } from '../diff/line.js';\n/**\n * Returns true if the filename contains characters that require C-style\n * quoting (as used by Git and GNU diffutils in diff output).\n */\nfunction needsQuoting(s) {\n for (let i = 0; i < s.length; i++) {\n if (s[i] < '\\x20' || s[i] > '\\x7e' || s[i] === '\"' || s[i] === '\\\\') {\n return true;\n }\n }\n return false;\n}\n/**\n * C-style quotes a filename, encoding special characters as escape sequences\n * and non-ASCII bytes as octal escapes. This is the inverse of\n * `parseQuotedFileName` in parse.ts.\n *\n * Non-ASCII bytes are encoded as UTF-8 before being emitted as octal escapes.\n * This matches the behaviour of both Git and GNU diffutils, which always emit\n * UTF-8 octal escapes regardless of the underlying filesystem encoding (e.g.\n * Git for Windows converts from NTFS's UTF-16 to UTF-8 internally).\n *\n * If the filename doesn't need quoting, returns it as-is.\n */\nfunction quoteFileNameIfNeeded(s) {\n if (!needsQuoting(s)) {\n return s;\n }\n let result = '\"';\n const bytes = new TextEncoder().encode(s);\n let i = 0;\n while (i < bytes.length) {\n const b = bytes[i];\n // See https://en.wikipedia.org/wiki/Escape_sequences_in_C#Escape_sequences\n if (b === 0x07) {\n result += '\\\\a';\n }\n else if (b === 0x08) {\n result += '\\\\b';\n }\n else if (b === 0x09) {\n result += '\\\\t';\n }\n else if (b === 0x0a) {\n result += '\\\\n';\n }\n else if (b === 0x0b) {\n result += '\\\\v';\n }\n else if (b === 0x0c) {\n result += '\\\\f';\n }\n else if (b === 0x0d) {\n result += '\\\\r';\n }\n else if (b === 0x22) {\n result += '\\\\\"';\n }\n else if (b === 0x5c) {\n result += '\\\\\\\\';\n }\n else if (b >= 0x20 && b <= 0x7e) {\n // Just a printable ASCII character that is neither a double quote nor a\n // backslash; no need to escape it.\n result += String.fromCharCode(b);\n }\n else {\n // Either part of a non-ASCII character or a control character without a\n // special escape sequence; needs escaping as a 3-digit octal escape\n result += '\\\\' + b.toString(8).padStart(3, '0');\n }\n i++;\n }\n result += '\"';\n return result;\n}\nexport const INCLUDE_HEADERS = {\n includeIndex: true,\n includeUnderline: true,\n includeFileHeaders: true\n};\nexport const FILE_HEADERS_ONLY = {\n includeIndex: false,\n includeUnderline: false,\n includeFileHeaders: true\n};\nexport const OMIT_HEADERS = {\n includeIndex: false,\n includeUnderline: false,\n includeFileHeaders: false\n};\nexport function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {\n let optionsObj;\n if (!options) {\n optionsObj = {};\n }\n else if (typeof options === 'function') {\n optionsObj = { callback: options };\n }\n else {\n optionsObj = options;\n }\n if (typeof optionsObj.context === 'undefined') {\n optionsObj.context = 4;\n }\n // We copy this into its own variable to placate TypeScript, which thinks\n // optionsObj.context might be undefined in the callbacks below.\n const context = optionsObj.context;\n // @ts-expect-error (runtime check for something that is correctly a static type error)\n if (optionsObj.newlineIsToken) {\n throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions');\n }\n if (!optionsObj.callback) {\n return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));\n }\n else {\n const { callback } = optionsObj;\n diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {\n const patch = diffLinesResultToPatch(diff);\n // TypeScript is unhappy without the cast because it does not understand that `patch` may\n // be undefined here only if `callback` is StructuredPatchCallbackAbortable:\n callback(patch);\n } }));\n }\n function diffLinesResultToPatch(diff) {\n // STEP 1: Build up the patch with no \"\\" lines and with the arrays\n // of lines containing trailing newline characters. We'll tidy up later...\n if (!diff) {\n return;\n }\n diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier\n function contextLines(lines) {\n return lines.map(function (entry) { return ' ' + entry; });\n }\n const hunks = [];\n let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;\n for (let i = 0; i < diff.length; i++) {\n const current = diff[i], lines = current.lines || splitLines(current.value);\n current.lines = lines;\n if (current.added || current.removed) {\n // If we have previous context, start with that\n if (!oldRangeStart) {\n const prev = diff[i - 1];\n oldRangeStart = oldLine;\n newRangeStart = newLine;\n if (prev) {\n curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];\n oldRangeStart -= curRange.length;\n newRangeStart -= curRange.length;\n }\n }\n // Output our changes\n for (const line of lines) {\n curRange.push((current.added ? '+' : '-') + line);\n }\n // Track the updated file position\n if (current.added) {\n newLine += lines.length;\n }\n else {\n oldLine += lines.length;\n }\n }\n else {\n // Identical context lines. Track line changes\n if (oldRangeStart) {\n // Close out any changes that have been output (or join overlapping)\n if (lines.length <= context * 2 && i < diff.length - 2) {\n // Overlapping\n for (const line of contextLines(lines)) {\n curRange.push(line);\n }\n }\n else {\n // end the range and output\n const contextSize = Math.min(lines.length, context);\n for (const line of contextLines(lines.slice(0, contextSize))) {\n curRange.push(line);\n }\n const hunk = {\n oldStart: oldRangeStart,\n oldLines: (oldLine - oldRangeStart + contextSize),\n newStart: newRangeStart,\n newLines: (newLine - newRangeStart + contextSize),\n lines: curRange\n };\n hunks.push(hunk);\n oldRangeStart = 0;\n newRangeStart = 0;\n curRange = [];\n }\n }\n oldLine += lines.length;\n newLine += lines.length;\n }\n }\n // Step 2: eliminate the trailing `\\n` from each line of each hunk, and, where needed, add\n // \"\\".\n for (const hunk of hunks) {\n for (let i = 0; i < hunk.lines.length; i++) {\n if (hunk.lines[i].endsWith('\\n')) {\n hunk.lines[i] = hunk.lines[i].slice(0, -1);\n }\n else {\n hunk.lines.splice(i + 1, 0, '\\\');\n i++; // Skip the line we just added, then continue iterating\n }\n }\n }\n return {\n oldFileName: oldFileName, newFileName: newFileName,\n oldHeader: oldHeader, newHeader: newHeader,\n hunks: hunks\n };\n }\n}\n/**\n * creates a unified diff patch.\n *\n * @param patch either a single structured patch object (as returned by `structuredPatch`) or an\n * array of them (as returned by `parsePatch`).\n * @param headerOptions behaves the same as the `headerOptions` option of `createTwoFilesPatch`.\n * Ignored for patches where `isGit` is `true`.\n *\n * When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's\n * output: it emits a `diff --git` header, emits Git extended headers as appropriate based on\n * properties like `isRename`, `isCreate`, `newMode`, etc, and will omit `---`/`+++` file\n * headers for patches with no hunks (e.g. renames without content changes).\n */\nexport function formatPatch(patch, headerOptions) {\n var _a, _b, _c, _d, _e, _f;\n if (!headerOptions) {\n headerOptions = INCLUDE_HEADERS;\n }\n if (Array.isArray(patch)) {\n if (patch.length > 1 && !headerOptions.includeFileHeaders && !patch.every(p => p.isGit)) {\n throw new Error('Cannot omit file headers on a multi-file patch. '\n + '(The result would be unparseable; how would a tool trying to apply '\n + 'the patch know which changes are to which file?)');\n }\n return patch.map(p => formatPatch(p, headerOptions)).join('\\n');\n }\n const ret = [];\n // Git patches have a fixed header format (diff --git, extended headers,\n // and ---/+++ when hunks are present), so headerOptions is ignored.\n if (patch.isGit) {\n headerOptions = INCLUDE_HEADERS;\n // Emit Git-style diff --git header and extended headers.\n // Git never puts /dev/null in the \"diff --git\" line; for file\n // creations/deletions it uses the real filename on both sides.\n if (!patch.oldFileName) {\n throw new Error('oldFileName must be specified for Git patches');\n }\n if (!patch.newFileName) {\n throw new Error('newFileName must be specified for Git patches');\n }\n let gitOldName = patch.oldFileName;\n let gitNewName = patch.newFileName;\n if (patch.isCreate && gitOldName === '/dev/null') {\n gitOldName = gitNewName.replace(/^b\\//, 'a/');\n }\n else if (patch.isDelete && gitNewName === '/dev/null') {\n gitNewName = gitOldName.replace(/^a\\//, 'b/');\n }\n ret.push('diff --git ' + quoteFileNameIfNeeded(gitOldName) + ' ' + quoteFileNameIfNeeded(gitNewName));\n if (patch.isDelete) {\n ret.push('deleted file mode ' + ((_a = patch.oldMode) !== null && _a !== void 0 ? _a : '100644'));\n }\n if (patch.isCreate) {\n ret.push('new file mode ' + ((_b = patch.newMode) !== null && _b !== void 0 ? _b : '100644'));\n }\n if (patch.oldMode && patch.newMode && !patch.isDelete && !patch.isCreate) {\n ret.push('old mode ' + patch.oldMode);\n ret.push('new mode ' + patch.newMode);\n }\n if (patch.isRename) {\n ret.push('rename from ' + quoteFileNameIfNeeded(((_c = patch.oldFileName) !== null && _c !== void 0 ? _c : '').replace(/^a\\//, '')));\n ret.push('rename to ' + quoteFileNameIfNeeded(((_d = patch.newFileName) !== null && _d !== void 0 ? _d : '').replace(/^b\\//, '')));\n }\n if (patch.isCopy) {\n ret.push('copy from ' + quoteFileNameIfNeeded(((_e = patch.oldFileName) !== null && _e !== void 0 ? _e : '').replace(/^a\\//, '')));\n ret.push('copy to ' + quoteFileNameIfNeeded(((_f = patch.newFileName) !== null && _f !== void 0 ? _f : '').replace(/^b\\//, '')));\n }\n }\n else {\n if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== undefined) {\n ret.push('Index: ' + patch.oldFileName);\n }\n if (headerOptions.includeUnderline) {\n ret.push('===================================================================');\n }\n }\n // Emit --- / +++ file headers. For Git patches with no hunks (e.g.\n // pure renames, mode-only changes), Git omits these, so we do too.\n const hasHunks = patch.hunks.length > 0;\n if (headerOptions.includeFileHeaders && patch.oldFileName !== undefined && patch.newFileName !== undefined\n && (!patch.isGit || hasHunks)) {\n ret.push('--- ' + quoteFileNameIfNeeded(patch.oldFileName) + (patch.oldHeader ? '\\t' + patch.oldHeader : ''));\n ret.push('+++ ' + quoteFileNameIfNeeded(patch.newFileName) + (patch.newHeader ? '\\t' + patch.newHeader : ''));\n }\n for (let i = 0; i < patch.hunks.length; i++) {\n const hunk = patch.hunks[i];\n // Unified Diff Format quirk: If the chunk size is 0,\n // the first number is one lower than one would expect.\n // https://www.artima.com/weblogs/viewpost.jsp?thread=164293\n const oldStart = hunk.oldLines === 0 ? hunk.oldStart - 1 : hunk.oldStart;\n const newStart = hunk.newLines === 0 ? hunk.newStart - 1 : hunk.newStart;\n ret.push('@@ -' + oldStart + ',' + hunk.oldLines\n + ' +' + newStart + ',' + hunk.newLines\n + ' @@');\n for (const line of hunk.lines) {\n ret.push(line);\n }\n }\n return ret.join('\\n') + '\\n';\n}\nexport function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {\n if (typeof options === 'function') {\n options = { callback: options };\n }\n if (!(options === null || options === void 0 ? void 0 : options.callback)) {\n const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);\n if (!patchObj) {\n return;\n }\n return formatPatch(patchObj, options === null || options === void 0 ? void 0 : options.headerOptions);\n }\n else {\n const { callback } = options;\n structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: patchObj => {\n if (!patchObj) {\n callback(undefined);\n }\n else {\n callback(formatPatch(patchObj, options.headerOptions));\n }\n } }));\n }\n}\nexport function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {\n return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);\n}\n/**\n * Split `text` into an array of lines, including the trailing newline character (where present)\n */\nfunction splitLines(text) {\n const hasTrailingNl = text.endsWith('\\n');\n const result = text.split('\\n').map(line => line + '\\n');\n if (hasTrailingNl) {\n result.pop();\n }\n else {\n result.push(result.pop().slice(0, -1));\n }\n return result;\n}\n", "/**\n * write_remote 工具定义。语义移植自官方 write:全文件写、返回 before 与 after。\n * 恒用 createIfAbsent,只新建,目标已存在即拒绝,绝无覆写分支;diff 卡展示。\n * 无沙箱升级字段,见 DESIGN §7 刻意差异,schema 不声明 escalation 字段。\n */\n\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { DiffCallView, DiffResultView, ToolResult } from '@deepseek-ai/dsh-tools'\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport { computeHunkDiffs, diffsFromMeta, formatWriteOutput } from './render.ts'\n\ninterface WriteInput { filePath: string; content: string }\n\n/** 校验 write_remote 参数:仅需非空 file_path,空 content 合法,可写空文件。 */\nexport function parseWriteArgs(args: { file_path: string; content: string }): WriteInput {\n if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')\n return { filePath: args.file_path, content: args.content }\n}\n\nexport function createWriteRemoteTool(fs: RemoteFileSystem): ReturnType<typeof defineTool> {\n return defineTool({\n name: 'write_remote',\n description: 'Create a new UTF-8 text file on a remote host over ssh. The target must NOT already exist — write_remote only creates new files, never overwrites.',\n parameters: {\n file_path: { type: 'string', required: true, description: 'Remote target and path in one: alias:path, e.g. wsl:~/x.md or myserver:/etc/hosts. Must not already exist.' },\n content: { type: 'string', required: true, description: 'Full UTF-8 text content to write.' },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n path: { type: 'string', required: true },\n operation: { type: 'string', required: true, enum: ['create', 'update'] },\n before: { required: true, oneOf: [{ type: 'string' }, { type: 'null' }] },\n after: { type: 'string', required: true },\n },\n },\n render: (_args, value) => [{ type: 'text', text: formatWriteOutput(value.path, value.operation) }],\n presentationMeta: (args, value) => ({ diffs: value.before === null ? [] : computeHunkDiffs((args as { file_path: string }).file_path, value.before, value.after).map(({ path, oldText, newText }) => ({ path, oldText, newText })) }),\n },\n isConcurrencySafe: () => true,\n async execute(args, exec) {\n const input = parseWriteArgs(args as { file_path: string; content: string })\n if (exec.signal.aborted) throw new FsError('cancelled', 'FS_ABORTED')\n const target = await fs.resolve(input.filePath)\n const outcome = await fs.writeText(target, input.content, { kind: 'createIfAbsent' })\n return { path: target.displayPath, operation: outcome.operation, before: outcome.before, after: outcome.after }\n },\n presentCall(args): DiffCallView {\n const a = args as { file_path: string; content: string }\n return { card: 'diff', title: 'Write ' + a.file_path, diffs: [{ path: a.file_path, oldText: null, newText: a.content }], locations: [{ path: a.file_path }] }\n },\n presentResult(args, result: ToolResult): DiffResultView | undefined {\n if (result.isError) return undefined\n const a = args as { file_path: string; content: string }\n const diffs = diffsFromMeta(result.meta) ?? [{ path: a.file_path, oldText: null, newText: a.content }]\n return { card: 'diff', title: 'Write ' + a.file_path, diffs }\n },\n })\n}", "/**\n * edit_remote 工具定义。\n * 语义移植自官方 edit:字面替换、唯一匹配,多现拒绝即 FS_AMBIGUOUS_EDIT,未命中即 FS_EDIT_NOT_FOUND;拒绝 old_string 与 new_string 相同;replace_all 全替换。\n * 匹配类错误由后端抛原生词表。无沙箱升级字段。\n */\n\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { DiffCallView, DiffResultView, ToolResult } from '@deepseek-ai/dsh-tools'\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport { computeHunkDiffs, diffsFromMeta, formatEditOutput } from './render.ts'\n\ninterface EditInput { filePath: string; oldString: string; newString: string; replaceAll: boolean }\n\n/** 校验 edit_remote 参数。 */\nexport function parseEditArgs(args: { file_path: string; old_string: string; new_string: string; replace_all?: boolean }): EditInput {\n if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')\n if (args.old_string.length === 0) throw new Error('old_string must be a non-empty string')\n if (args.old_string === args.new_string) throw new Error('old_string and new_string must differ')\n return { filePath: args.file_path, oldString: args.old_string, newString: args.new_string, replaceAll: args.replace_all ?? false }\n}\n\nexport function createEditRemoteTool(fs: RemoteFileSystem): ReturnType<typeof defineTool> {\n return defineTool({\n name: 'edit_remote',\n description: 'Edit an existing UTF-8 text file on a remote host over ssh by replacing literal text.',\n parameters: {\n file_path: { type: 'string', required: true, description: 'Remote target and path in one: alias:path, e.g. wsl:~/x.md or myserver:/etc/hosts.' },\n old_string: { type: 'string', required: true, description: 'Literal text to replace. Must match exactly.' },\n new_string: { type: 'string', required: true, description: 'Literal replacement text. Use an empty string to delete the match.' },\n replace_all: { type: 'boolean', description: 'Replace all matches. Defaults to false; when false, old_string must appear exactly once.' },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n path: { type: 'string', required: true },\n before: { type: 'string', required: true },\n after: { type: 'string', required: true },\n },\n },\n render: (args, value) => [{ type: 'text', text: formatEditOutput(value.path, (args as { replace_all?: boolean }).replace_all ?? false) }],\n presentationMeta: (args, value) => ({ diffs: computeHunkDiffs((args as { file_path: string }).file_path, value.before, value.after).map(({ path, oldText, newText }) => ({ path, oldText, newText })) }),\n },\n isConcurrencySafe: () => false,\n async execute(args, exec) {\n const input = parseEditArgs(args as { file_path: string; old_string: string; new_string: string; replace_all?: boolean })\n if (exec.signal.aborted) throw new FsError('cancelled', 'FS_ABORTED')\n const target = await fs.resolve(input.filePath)\n const outcome = await fs.editText(target, { oldString: input.oldString, newString: input.newString, replaceAll: input.replaceAll })\n return { path: target.displayPath, before: outcome.before, after: outcome.after }\n },\n presentCall(args): DiffCallView {\n const a = args as { file_path: string; old_string: string; new_string: string }\n return { card: 'diff', title: 'Edit ' + a.file_path, diffs: [{ path: a.file_path, oldText: a.old_string || null, newText: a.new_string }], locations: [{ path: a.file_path }] }\n },\n presentResult(args, result: ToolResult): DiffResultView | undefined {\n if (result.isError) return undefined\n const a = args as { file_path: string }\n const diffs = diffsFromMeta(result.meta)\n if (diffs === undefined) return undefined\n return { card: 'diff', title: 'Edit ' + a.file_path, diffs }\n },\n })\n}", "/**\n * 在 webServer 上自注册一条 connection 风格的 RPC 通道。\n *\n * 与 dsh-classic-coding 的同名模块同源:dsh 的 connection.rpc.handle 在登记路由时解析 webServer 会抛\n * cannot get property webServer without inject,官方自身也不走该路径。\n * 因此这里直接向 webServer 注册 prefix 路由,复用 connection.requestRejection 的 Host 校验与浏览器鉴权,\n * 并实现 client-request 与 server-response 信封。\n *\n * @module dsh-remote-file-system/rpc-channel\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\n/** RPC 处理器的统一结果信封。 */\nexport type RpcChannelResult =\n | { ok: true; value: unknown }\n | { ok: false; error: { code: string; message: string; details: object } }\n\n/** 通道端点处理器:endpoint 与请求体,返回结果信封。 */\nexport type RpcChannelHandler = (\n endpoint: string,\n payload: unknown,\n signal: AbortSignal,\n) => Promise<RpcChannelResult>\n\n/** webServer 服务的最小面,仅注册 prefix 路由。 */\ninterface WebServerFace {\n register(route: {\n kind: 'prefix'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n }): () => void\n}\n\n/** connection 服务的最小面,仅用请求信任判定。 */\ninterface ConnectionFace {\n requestRejection(request: { headers: IncomingMessage['headers'] }): 401 | 403 | undefined\n}\n\n/** 可注入上下文的最小面。 */\ninterface Injectable {\n inject(deps: string[], callback: (scope: unknown) => void): unknown\n}\n\n/** 注入作用域的最小面。 */\ninterface Scope {\n connection: ConnectionFace\n webServer: WebServerFace\n effect(callback: () => (() => void) | undefined, label: string): unknown\n}\n\n/** 端点路径段的合法字符集,与官方 ENDPOINT_SEGMENT_PATTERN 一致。 */\nconst SEGMENT = /^[A-Za-z0-9_$.-]+$/\n/** 请求体上限,与官方 bridge 默认的 300MiB 对齐。 */\nconst MAX_BODY_BYTES = 300 * 1024 * 1024\n/** 唯一接受的 content-type,与官方信封契约一致。 */\nconst JSON_MIME = 'application/json'\n\n/** 请求体超限的标记错误,由 serve 决定如何回写与断开。 */\nclass PayloadTooLargeError extends Error {}\n\n/**\n * 挂载一条 RPC 通道。webServer 或 connection 缺席时不挂载,其余功能不受影响。\n * @param ctx - 插件上下文。\n * @param channel - 绝对通道前缀,例如 /remote-file-system。\n * @param handler - 端点处理器。\n */\nexport function mountRpcChannel(ctx: Injectable, channel: string, handler: RpcChannelHandler): void {\n ctx.inject(['connection', 'webServer'], (raw) => {\n const scope = raw as Scope\n scope.effect(\n () => scope.webServer.register({\n kind: 'prefix',\n path: channel,\n handler: (req, res) => serve(scope.connection, channel, handler, req, res),\n }),\n channel + ': rpc channel',\n )\n })\n}\n\n/**\n * 处理一次 HTTP 请求:信任与鉴权、端点解析、信封校验、结果回写。\n * @param connection - connection 服务,用其信任判定。\n * @param channel - 本通道前缀。\n * @param handler - 端点处理器。\n * @param req - node 请求。\n * @param res - node 响应。\n */\nasync function serve(\n connection: ConnectionFace,\n channel: string,\n handler: RpcChannelHandler,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const rejection = connection.requestRejection(req)\n if (rejection !== undefined) {\n res.writeHead(rejection)\n res.end(rejection === 401 ? 'unauthorized' : 'forbidden')\n return\n }\n if (req.method !== 'POST') return missing(res)\n const pathname = new URL(req.url ?? '/', 'http://localhost').pathname\n const prefix = channel + '/'\n if (!pathname.startsWith(prefix)) return missing(res)\n const endpoint = pathname.slice(prefix.length)\n if (!endpoint.split('/').every((part) => part !== '' && part !== '.' && part !== '..' && SEGMENT.test(part))) return missing(res)\n if (!isJsonContentType(req)) {\n res.writeHead(415)\n res.end('unsupported media type')\n return\n }\n const declared = Number(req.headers['content-length'])\n if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) return tooLarge(req, res)\n\n let raw: string\n try {\n raw = await readBody(req)\n } catch (error) {\n if (error instanceof PayloadTooLargeError) return tooLarge(req, res)\n res.writeHead(400)\n res.end('body read failed')\n return\n }\n let message: { type?: unknown; rpcId?: unknown; method?: unknown; payload?: unknown }\n try {\n message = JSON.parse(raw) as typeof message\n } catch {\n res.writeHead(400)\n res.end('body is not JSON')\n return\n }\n const rpcId = typeof message.rpcId === 'string' ? message.rpcId : 'invalid-request'\n if (message.type !== 'client-request' || typeof message.method !== 'string') {\n return reply(res, rpcId, { ok: false, error: { code: 'gateway/bad-request', message: 'invalid client-request message', details: {} } })\n }\n if (message.method !== endpoint) {\n return reply(res, rpcId, {\n ok: false,\n error: { code: 'gateway/bad-request', message: 'method ' + JSON.stringify(message.method) + ' does not match endpoint ' + JSON.stringify(endpoint), details: {} },\n })\n }\n const controller = new AbortController()\n const onClose = (): void => { controller.abort(new Error('client disconnected')) }\n res.on('close', onClose)\n let result: RpcChannelResult\n try {\n result = await handler(endpoint, message.payload, controller.signal)\n } catch (error) {\n result = { ok: false, error: { code: 'internal', message: error instanceof Error ? error.message : String(error), details: {} } }\n } finally {\n res.off('close', onClose)\n }\n reply(res, rpcId, result)\n}\n\n/** 路径不匹配或方法不符时统一 404,与官方未认领端点一致。 */\nfunction missing(res: ServerResponse): void {\n res.writeHead(404)\n res.end('not found')\n}\n\n/** 回写一个 server-response 信封。 */\nfunction reply(res: ServerResponse, rpcId: string, result: RpcChannelResult): void {\n res.writeHead(200, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ type: 'server-response', rpcId, result }))\n}\n\n/** 先回 413 再销毁连接:顺序反了 socket 已断,413 永远发不出去。 */\nfunction tooLarge(req: IncomingMessage, res: ServerResponse): void {\n res.writeHead(413, { connection: 'close' })\n res.end('payload too large')\n req.destroy()\n}\n\n/** content-type 必须声明为 application/json,charset 等参数可忽略。 */\nfunction isJsonContentType(req: IncomingMessage): boolean {\n const header = req.headers['content-type']\n if (typeof header !== 'string') return false\n const mime = header.split(';', 1)[0]\n return mime !== undefined && mime.trim().toLowerCase() === JSON_MIME\n}\n\n/** 读取并限长请求体,超限抛 PayloadTooLargeError,由调用方回写 413。 */\nasync function readBody(req: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : chunk as Buffer\n size += buffer.length\n if (size > MAX_BODY_BYTES) throw new PayloadTooLargeError('rpc channel: request body exceeds limit')\n chunks.push(buffer)\n }\n return Buffer.concat(chunks).toString('utf8')\n}\n", "/**\n * /remote-file-system RPC 端点:把 RemoteFileSystem 的读取能力暴露给浏览器半身。\n *\n * 内置右侧栏经 ctx.fs 读主机本地文件,读不到「别名:路径」的远端文件,\n * 因此 client 半身用本通道读取远端文本并自行渲染。\n *\n * @module dsh-remote-file-system/rpc\n */\n\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport type { RpcChannelResult } from './rpc-channel.ts'\n\n/** 单次预览的字符上限,按 UTF-16 代码单元计,超出即截断并标记;bytes 仍回填完整文本的字节数。 */\nconst MAX_PREVIEW_CHARS = 2 * 1024 * 1024\n\n/** 调用方参数不合法的错误,由 handleRemoteRpc 归 bad-request 而非 internal。 */\nclass BadRequestError extends Error {}\n\n/** 把未知 payload 收窄成字符串字段表。 */\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {}\n}\n\n/** 从字符串字段表里取字符串字段,缺失或类型不符返回 undefined。 */\nfunction readString(record: Record<string, unknown>, key: string): string | undefined {\n const value = record[key]\n return typeof value === 'string' ? value : undefined\n}\n\n/** 读取端点:payload 的 target 为「别名:路径」,返回路径、文本、字节数与截断标记。 */\nasync function readRemote(fs: RemoteFileSystem, payload: unknown): Promise<{ path: string; text: string; bytes: number; truncated: boolean }> {\n const target = readString(asRecord(payload), 'target')\n if (target === undefined || target.trim() === '') throw new BadRequestError('read 缺少 target')\n const resolved = await fs.resolve(target)\n const text = await fs.readText(resolved)\n const bytes = Buffer.byteLength(text, 'utf8')\n if (text.length > MAX_PREVIEW_CHARS) {\n return { path: resolved.displayPath, text: text.slice(0, MAX_PREVIEW_CHARS), bytes, truncated: true }\n }\n return { path: resolved.displayPath, text, bytes, truncated: false }\n}\n\n/**\n * 处理一个端点,失败统一归 internal 信封,保留远端中文详情。\n * @param fs - 远程文件后端。\n * @param endpoint - 端点名。\n * @param payload - 载荷。\n */\nexport async function handleRemoteRpc(fs: RemoteFileSystem, endpoint: string, payload: unknown): Promise<RpcChannelResult> {\n try {\n switch (endpoint) {\n case 'read':\n return { ok: true, value: await readRemote(fs, payload) }\n default:\n return { ok: false, error: { code: 'bad-request', message: '未知端点: ' + endpoint, details: {} } }\n }\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e)\n if (e instanceof BadRequestError) return { ok: false, error: { code: 'bad-request', message, details: {} } }\n return { ok: false, error: { code: 'internal', message, details: {} } }\n }\n}\n", "/**\n * dsh-remote-file-system — host 半身入口。\n *\n * 职责:实例化 RemoteFileSystem 即 ssh 后端,注册 read_remote、write_remote、edit_remote 三个模型可见工具,并为各工具注册等价的 systemPrompt 引导节。\n *\n * 加载形态:组合包 bundle,cordis.patch.yml 插进 profile。官方 @deepseek-ai/* 依赖保持 external,与 host 共享同一份 cordis 与 dsh-fs 实例。\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { RemoteFileSystem } from './remote-fs.ts'\nimport { createReadRemoteTool } from './read.ts'\nimport { createWriteRemoteTool } from './write.ts'\nimport { createEditRemoteTool } from './edit.ts'\nimport { mountRpcChannel } from './rpc-channel.ts'\nimport { handleRemoteRpc } from './rpc.ts'\n\n/** 插件名,与 cordis.patch.yml 的 name 一致,loader 诊断用。 */\nexport const name = 'dsh-remote-file-system'\n\n/** 必需宿主服务:tools 工具注册表与 systemPrompt 引导。 */\nexport const inject = ['tools', 'systemPrompt']\n\n/** 插件配置:enabled 总开关。 */\nexport interface Config {\n /** 是否注册远程工具。默认 true。 */\n enabled?: boolean\n}\n\n/**\n * 插件 apply:实例化 RemoteFileSystem,注册三工具并加 systemPrompt 引导节。\n * 三个工具的引导节复用原生 read、write、edit 的顺序位,节名唯一避免冲突。\n * @param ctx - cordis 上下文。\n * @param config - 插件配置。\n */\nexport function apply(ctx: Context, config: Config = {}): void {\n if (config.enabled === false) return\n\n // 构造一次后端实例,三工具共享。普通对象,不注册为 ctx.fs,无副作用。\n const fs = new RemoteFileSystem()\n\n // systemPrompt:为远程工具注册与原生 read、write、edit 等价的使用引导。\n ctx.systemPrompt.section({\n name: 'tool:read_remote',\n order: ctx.systemPrompt.getSectionOrder('TOOL_READ'),\n text: 'Use read_remote (not shell commands) to inspect UTF-8 text files on remote hosts over ssh. file_path carries the target as alias:path (e.g. wsl:~/x.md). Results include line numbers; use offset and limit to continue reading large files.',\n })\n ctx.systemPrompt.section({\n name: 'tool:write_remote',\n order: ctx.systemPrompt.getSectionOrder('TOOL_WRITE'),\n text: 'Use write_remote to create a NEW UTF-8 text file on a remote host. It only creates — the target must not already exist, and it never overwrites.',\n })\n ctx.systemPrompt.section({\n name: 'tool:edit_remote',\n order: ctx.systemPrompt.getSectionOrder('TOOL_EDIT'),\n text: 'Use edit_remote for targeted literal changes to an existing remote text file. It replaces literal old_string with new_string; by default old_string must appear exactly once.',\n })\n\n // 注册三个工具。defineTool 纯函数返回对象,ctx.tools.register 副作用注册。\n ctx.tools.register(createReadRemoteTool(fs))\n ctx.tools.register(createWriteRemoteTool(fs))\n ctx.tools.register(createEditRemoteTool(fs))\n\n // 浏览器半身经这条自注册通道读取远端文本,供右侧栏预览。\n mountRpcChannel(ctx, '/remote-file-system', (endpoint, payload) => handleRemoteRpc(fs, endpoint, payload))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * RemoteFileSystem — 远程文件后端层。\n *\n * 刻意不继承 @deepseek-ai/dsh-fs 的 FileSystem 与 cordis Service。\n * FileSystem 是 Service 子类,构造即注册为 ctx.fs,继承会污染会话级 ctx.fs,实测见 DESIGN §9.2。\n * 这里只借用 dsh-fs 的类型词表与 FsError,方法面同构 FileSystem,仅实现远程读写用到的子集。\n *\n * 实现走 ssh:host 进程内对同一 targetKey 加 per-target 锁,串行本 agent 读改写;远端发布用「同目录临时文件 + mv」近似原子,createIfAbsent 用 no-clobber 近似。\n * 原子性只保证 host 进程内与单条 ssh 命令内,跨连接与跨主机并发覆盖为已接受局限,见 DESIGN §7。\n */\n\nimport {\n FsError,\n FsTargetKey,\n FsVersion,\n} from '@deepseek-ai/dsh-fs'\nimport type {\n FsEditRequest,\n FsEditOutcome,\n FsInfo,\n FsTarget,\n FsVersion as FsVersionType,\n FsWriteIntent,\n FsWriteOutcome,\n} from '@deepseek-ai/dsh-fs'\nimport { createHash } from 'node:crypto'\nimport { b64, PATH_PROLOGUE, DIE_HELPER, B64_HELPER, sshOutcome } from './protocol.ts'\n\n/** 每 targetKey 串行队列,维护每个 key 的尾 Promise。 */\nconst TAILS = new Map<string, Promise<unknown>>()\nasync function withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prev = TAILS.get(key) ?? Promise.resolve()\n const run = prev.then(() => fn())\n TAILS.set(key, run.catch(() => {}))\n return run\n}\n\nconst ALIAS_RE = /^[A-Za-z0-9._-]+$/\n\n/** 生成 Windows 分支的公共前导:解码路径、展开 ~/、统一错误输出。 */\nfunction winPrologue(rawPath: string): string {\n return [\n \"$ErrorActionPreference='Stop'\",\n 'function Die($c,$m){ [Console]::Error.WriteLine(\"__ERR__\"+$c+\"__\"+$m); exit 1 }',\n 'function Decode($s){ [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($s)) }',\n \"$P = Decode('\" + b64(rawPath) + \"')\",\n \"if ($P.StartsWith('~/')) { $P = Join-Path $env:USERPROFILE $P.Substring(2) }\",\n ].join('\\n')\n}\n\n/** 解析「别名:路径」。路径须绝对或 ~/ 开头,裸相对路径拒绝。 */\nexport function splitAliasPath(filePath: string): { alias: string; rawPath: string } {\n const idx = filePath.indexOf(':')\n if (idx <= 0) throw new FsError('file_path 须为「别名:路径」形式,缺少别名前缀:' + filePath, 'FS_IO_ERROR')\n const alias = filePath.slice(0, idx)\n const rawPath = filePath.slice(idx + 1)\n if (!ALIAS_RE.test(alias)) throw new FsError('非法的别名前缀:' + alias, 'FS_IO_ERROR')\n const winAbs = /^[A-Za-z]:[\\\\/]/.test(rawPath)\n if (!(rawPath.startsWith('/') || rawPath.startsWith('~/') || winAbs)) {\n throw new FsError('远程路径只接受绝对路径、~/ 或盘符开头,收到相对路径:' + rawPath, 'FS_IO_ERROR')\n }\n return { alias, rawPath }\n}\n\n/**\n * RemoteFileSystem 后端。普通类,不继承 FileSystem 与 cordis Service,不注册服务。\n */\nexport class RemoteFileSystem {\n /** 单次读取字节上限,超限拒绝且不做流式,见 DESIGN §7 资源上限。 */\n readonly readMaxBytes = 50 * 1024 * 1024\n\n /** 读过的内容指纹,供 editText 判断「读到之后又被改过」。只在 host 进程内有效。 */\n private readonly observed = new Map<string, string>()\n\n private fingerprint(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex')\n }\n\n /** 解析「别名:路径」为稳定 FsTarget。targetKey 含 alias 与 rawPath,displayPath 即原名。 */\n async resolve(path: string): Promise<FsTarget> {\n const { alias, rawPath } = splitAliasPath(path)\n const displayPath = alias + ':' + rawPath\n return { targetKey: FsTargetKey(alias + '::' + rawPath), displayPath }\n }\n\n /** FileSystem 同形占位:无进程内路径,返回 displayPath。 */\n processPath(target: FsTarget): string { return target.displayPath }\n\n /** stat:类型 + size + 版本依据,缺失返回 undefined。版本只作 host 进程内陈旧排序。 */\n async stat(target: FsTarget): Promise<FsInfo | undefined> {\n return withLock(target.targetKey, () => this.statLocked(target))\n }\n\n private async statLocked(target: FsTarget): Promise<FsInfo | undefined> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n const env = { RM_PATH: b64(rawPath) }\n // 存在与否与类型判定在远端一次完成,把结果打进 stdout,失败则 die\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'if [ ! -e \"$P\" ] && [ ! -L \"$P\" ]; then echo ABSENT; exit 0; fi',\n 'if [ -L \"$P\" ]; then echo OTHER; exit 0; fi',\n 'if [ -d \"$P\" ]; then echo DIR; exit 0; fi',\n 'if [ ! -f \"$P\" ]; then echo OTHER; exit 0; fi',\n 'SIZE=$(wc -c < \"$P\" 2>/dev/null | tr -d \" \")',\n 'MT=$(stat -c %Y \"$P\" 2>/dev/null || echo 0)',\n 'echo \"FILE:$SIZE:$MT\"',\n ].join('\\n')\n const winScript = [\n winPrologue(rawPath),\n 'try {',\n ' $i = Get-Item -LiteralPath $P -Force -ErrorAction Stop',\n \" if ($i.PSIsContainer) { 'DIR' }\",\n \" elseif ($i.Attributes -band [IO.FileAttributes]::ReparsePoint) { 'OTHER' }\",\n \" elseif ($i -is [IO.FileInfo]) { 'FILE:' + $i.Length + ':' + ([DateTimeOffset]$i.LastWriteTimeUtc).ToUnixTimeSeconds() }\",\n \" else { 'OTHER' }\",\n \"} catch { 'ABSENT' }\",\n ].join('\\n')\n const out = await sshOutcome(alias, env, action, {}, { script: winScript })\n const s = out.stdout.trim()\n if (s === 'ABSENT') return undefined\n if (s === 'DIR') return { version: FsVersion('dir:' + Date.now()), type: 'directory' }\n if (s === 'OTHER') return { version: FsVersion('other:' + Date.now()), type: 'other' }\n const m = /^FILE:(\\d*):(\\d*)$/.exec(s)\n const size = m && m[1] !== '' ? Number(m[1]) : undefined\n const mtime = m && m[2] !== '' ? m[2] : '0'\n return {\n version: FsVersion('m:' + mtime + (size !== undefined ? ':' + size : '')),\n type: 'file',\n ...(size !== undefined ? { size } : {}),\n }\n }\n\n /** 读整文件 utf8。缺失归 FS_NOT_FOUND,非普通归 FS_NOT_REGULAR_FILE,超限归 FS_TOO_LARGE。 */\n async readText(target: FsTarget): Promise<string> {\n return withLock(target.targetKey, async () => {\n const text = await this.readTextLocked(target)\n this.observed.set(target.targetKey, this.fingerprint(text))\n return text\n })\n }\n\n private async readTextLocked(target: FsTarget): Promise<string> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n const env = { RM_PATH: b64(rawPath), RM_MAX: String(this.readMaxBytes) }\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'if [ ! -e \"$P\" ] && [ ! -L \"$P\" ]; then die FS_NOT_FOUND 文件不存在; exit 0; fi',\n 'if [ -d \"$P\" ]; then die FS_NOT_REGULAR_FILE 目标是一个目录; exit 0; fi',\n 'if [ ! -f \"$P\" ]; then die FS_NOT_REGULAR_FILE 目标不是普通文件; exit 0; fi',\n 'if [ ! -r \"$P\" ]; then die FS_PERMISSION_DENIED 没有读取权限; exit 0; fi',\n 'SIZE=$(wc -c < \"$P\" 2>/dev/null | tr -d \" \")',\n 'if [ -n \"$SIZE\" ] && [ \"$SIZE\" -gt \"$RM_MAX\" ] 2>/dev/null; then die FS_TOO_LARGE 文件超过单次读取上限,不做流式; exit 0; fi',\n 'base64 -w0 < \"$P\" 2>/dev/null || die FS_IO_ERROR 读取内容失败',\n ].join('\\n')\n const winScript = [\n winPrologue(rawPath),\n \"if (-not (Test-Path -LiteralPath $P)) { Die 'FS_NOT_FOUND' '文件不存在'; exit 0 }\",\n \"if (Test-Path -LiteralPath $P -PathType Container) { Die 'FS_NOT_REGULAR_FILE' '目标是目录'; exit 0 }\",\n 'try {',\n ' $b = [IO.File]::ReadAllBytes($P)',\n '} catch [System.UnauthorizedAccessException] {',\n \" Die 'FS_PERMISSION_DENIED' '没有权限'; exit 0\",\n '} catch {',\n \" Die 'FS_IO_ERROR' $_.Exception.Message; exit 0\",\n '}',\n 'if ($b.Length -gt ' + this.readMaxBytes + \") { Die 'FS_TOO_LARGE' '文件超过单次读取上限'; exit 0 }\",\n '[Convert]::ToBase64String($b)',\n ].join('\\n')\n const out = await sshOutcome(alias, env, action, {}, { script: winScript })\n const raw = Buffer.from(out.stdout.replace(/\\s+$/u, ''), 'base64')\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(raw)\n } catch {\n throw new FsError('目标不是有效的 UTF-8 文本', 'FS_NOT_TEXT')\n }\n }\n\n /** readBytes:raw 读取,文本只读,返回 utf8 字节。 */\n async readBytes(target: FsTarget, maxBytes: number): Promise<Uint8Array> {\n const text = await this.readText(target)\n const buf = Buffer.from(text, 'utf8')\n if (buf.byteLength > maxBytes) throw new FsError('超出字节上限', 'FS_TOO_LARGE')\n return buf\n }\n\n /**\n * 全文件写,恒用 createIfAbsent,只新建。目标已存在即 FS_NOT_OBSERVED,绝无覆写。\n * 只接受 createIfAbsent 意图,其他拒绝。\n */\n async writeText(target: FsTarget, content: string, expected?: FsWriteIntent): Promise<FsWriteOutcome> {\n return withLock(target.targetKey, () => this.writeTextLocked(target, content, expected))\n }\n\n private async writeTextLocked(target: FsTarget, content: string, expected?: FsWriteIntent): Promise<FsWriteOutcome> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n if (expected !== undefined && expected.kind !== 'createIfAbsent') {\n throw new FsError('write_remote 只允许新建,仅支持 createIfAbsent 意图', 'FS_NOT_OBSERVED')\n }\n const env = { RM_PATH: b64(rawPath) }\n const data = b64(content)\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'D=$(dirname \"$P\")',\n 'if [ ! -d \"$D\" ]; then die FS_NOT_FOUND 目标目录不存在; exit 0; fi',\n 'if [ ! -w \"$D\" ]; then die FS_PERMISSION_DENIED 目标目录不可写; exit 0; fi',\n 'TMP=$(mktemp \"$P.XXXXXXXX.tmp\" 2>/dev/null)',\n 'if [ -z \"$TMP\" ]; then die FS_IO_ERROR 无法创建临时文件; exit 0; fi',\n `printf %s '${data}' | base64 -d > \"$TMP\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR 写入临时文件失败; exit 0; }`,\n 'if [ -e \"$P\" ] || [ -L \"$P\" ]; then rm -f \"$TMP\"; die FS_NOT_OBSERVED 文件已存在,write_remote 只允许新建; exit 0; fi',\n 'mv -f \"$TMP\" \"$P\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR mv 发布失败; exit 0; }',\n 'echo OK',\n ].join('\\n')\n const winScript = [\n winPrologue(rawPath),\n '$in = [Console]::In.ReadToEnd()',\n '$data = [Convert]::FromBase64String($in.Trim())',\n \"if (Test-Path -LiteralPath $P) { Die 'FS_NOT_OBSERVED' '文件已存在,write_remote 只允许新建'; exit 0 }\",\n '$dir = [IO.Path]::GetDirectoryName($P)',\n \"if (-not $dir) { $dir = '.' }\",\n \"if (-not (Test-Path -LiteralPath $dir)) { Die 'FS_NOT_FOUND' '目标目录不存在'; exit 0 }\",\n '$tmp = Join-Path $dir ([IO.Path]::GetRandomFileName())',\n 'try {',\n ' [IO.File]::WriteAllBytes($tmp, $data)',\n '} catch [System.UnauthorizedAccessException] {',\n \" Die 'FS_PERMISSION_DENIED' '没有权限'; exit 0\",\n '} catch {',\n \" Die 'FS_IO_ERROR' $_.Exception.Message; exit 0\",\n '}',\n 'try {',\n \" if (Test-Path -LiteralPath $P) { throw 'exists' }\",\n ' Move-Item -LiteralPath $tmp -Destination $P -ErrorAction Stop',\n '} catch {',\n ' Remove-Item -LiteralPath $tmp -ErrorAction SilentlyContinue',\n \" Die 'FS_NOT_OBSERVED' '文件已存在,write_remote 只允许新建'; exit 0\",\n '}',\n \"'OK'\",\n ].join('\\n')\n await sshOutcome(alias, env, action, {}, { script: winScript, input: data })\n this.observed.set(target.targetKey, this.fingerprint(content))\n return { operation: 'create', version: FsVersion('w:' + Date.now()), before: null, after: content }\n }\n\n /** 编辑后写回,整块替换。 */\n private async writeReplaced(target: FsTarget, content: string): Promise<string> {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n const env = { RM_PATH: b64(rawPath) }\n const data = b64(content)\n const action = [\n PATH_PROLOGUE,\n DIE_HELPER,\n 'D=$(dirname \"$P\")',\n 'if [ ! -d \"$D\" ]; then die FS_NOT_FOUND 目标目录不存在; exit 0; fi',\n 'TMP=$(mktemp \"$P.XXXXXXXX.tmp\" 2>/dev/null)',\n 'if [ -z \"$TMP\" ]; then die FS_IO_ERROR 无法创建临时文件; exit 0; fi',\n `printf %s '${data}' | base64 -d > \"$TMP\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR 写入临时文件失败; exit 0; }`,\n 'if [ ! -e \"$P\" ]; then rm -f \"$TMP\"; die FS_NOT_FOUND 文件已不存在; exit 0; fi',\n 'if [ ! -w \"$P\" ]; then rm -f \"$TMP\"; die FS_PERMISSION_DENIED 文件不可写; exit 0; fi',\n 'mv -f \"$TMP\" \"$P\" 2>/dev/null || { rm -f \"$TMP\"; die FS_IO_ERROR mv 替换失败; exit 0; }',\n 'echo OK',\n ].join('\\n')\n const winScript = [\n winPrologue(rawPath),\n '$in = [Console]::In.ReadToEnd()',\n '$data = [Convert]::FromBase64String($in.Trim())',\n '$dir = [IO.Path]::GetDirectoryName($P)',\n \"if (-not $dir) { $dir = '.' }\",\n \"if (-not (Test-Path -LiteralPath $dir)) { Die 'FS_NOT_FOUND' '目标目录不存在'; exit 0 }\",\n '$tmp = Join-Path $dir ([IO.Path]::GetRandomFileName())',\n 'try {',\n ' [IO.File]::WriteAllBytes($tmp, $data)',\n '} catch [System.UnauthorizedAccessException] {',\n \" Die 'FS_PERMISSION_DENIED' '没有权限'; exit 0\",\n '} catch {',\n \" Die 'FS_IO_ERROR' $_.Exception.Message; exit 0\",\n '}',\n \"if (-not (Test-Path -LiteralPath $P)) { Remove-Item -LiteralPath $tmp -ErrorAction SilentlyContinue; Die 'FS_NOT_FOUND' '文件已不存在'; exit 0 }\",\n 'try { Move-Item -LiteralPath $tmp -Destination $P -Force -ErrorAction Stop } catch { Remove-Item -LiteralPath $tmp -ErrorAction SilentlyContinue; Die \\'FS_IO_ERROR\\' \\'替换失败\\'; exit 0 }',\n \"'OK'\",\n ].join('\\n')\n await sshOutcome(alias, env, action, {}, { script: winScript, input: data })\n return content\n }\n\n /**\n * 字面编辑:读原文 → host 内唯一匹配或全替换 → 写回。匹配类错误走原生词表,即 FS_AMBIGUOUS_EDIT 与 FS_EDIT_NOT_FOUND;远端失败归远程失败类。\n * 整块读改写,保留远端换行。\n */\n async editText(\n target: FsTarget,\n edit: FsEditRequest,\n _expected?: { version: FsVersionType } | undefined,\n ): Promise<FsEditOutcome> {\n return withLock(target.targetKey, async () => {\n const { alias, rawPath } = splitAliasPath(target.displayPath)\n // 先读原文,锁内读改写临界区,缺失即 FS_NOT_FOUND\n const readTarget: FsTarget = { targetKey: target.targetKey, displayPath: alias + ':' + rawPath }\n const before = await this.readTextLocked(readTarget)\n const seen = this.observed.get(target.targetKey)\n if (seen !== undefined && seen !== this.fingerprint(before)) {\n throw new FsError('文件在读取后已被修改,请先重新读取再编辑', 'FS_STALE_VERSION')\n }\n const matches = this.countMatches(before, edit.oldString)\n if (matches === 0) throw new FsError('old_string 未命中任何匹配', 'FS_EDIT_NOT_FOUND')\n if (!edit.replaceAll && matches > 1) {\n throw new FsError('old_string 出现 ' + matches + ' 次,需更精确或设 replace_all', 'FS_AMBIGUOUS_EDIT')\n }\n const after = edit.replaceAll\n ? before.split(edit.oldString).join(edit.newString)\n : before.replace(edit.oldString, edit.newString)\n await this.writeReplaced(readTarget, after)\n this.observed.set(target.targetKey, this.fingerprint(after))\n return { version: FsVersion('e:' + Date.now()), before, after }\n })\n }\n\n private countMatches(text: string, oldString: string): number {\n if (oldString === '') return 0\n let count = 0\n let idx = text.indexOf(oldString)\n while (idx !== -1) { count++; idx = text.indexOf(oldString, idx + oldString.length) }\n return count\n }\n}\n", "/**\n * 远端执行协议:host 进程内用子进程 ssh 对远程主机执行一条 bash 脚本。\n *\n * 路径 base64 后经环境变量传给远端;写内容 base64 后内联进脚本,经 stdin 传给远端,避开命令行长度上限。\n * base64 只含 [A-Za-z0-9+/=],不夹带单引号或换行,从根上避开 shell 引号转义与注入。\n * 远端统一用 bash -s 从 stdin 读脚本,字节经 base64 传输,不受本地 shell 方言约束。\n *\n * 结果约定:脚本成功写 stdout,内容是 base64 或留空;失败向 stderr 打一行 __ERR__<code>__<message> 并 exit 1。\n * 本地据此归 FsError,保留远端中文详情。\n */\n\nimport { spawn } from 'node:child_process'\nimport { FsError } from '@deepseek-ai/dsh-fs'\n\nconst ERR_PREFIX = '__ERR__'\n\n/** base64 编码。 */\nexport function b64(input: string): string { return Buffer.from(input, 'utf8').toString('base64') }\n/** base64 解码为 utf8。 */\nexport function b64d(input: string): string { return Buffer.from(input, 'base64').toString('utf8') }\n\n/** 远端平台。POSIX 走 bash -s,Windows 走 powershell -Command -。 */\nexport type RemotePlatform = 'posix' | 'windows'\nconst PLATFORM_CACHE = new Map<string, RemotePlatform>()\n/** Windows 分支的调用:脚本经 -EncodedCommand 传,数据经 stdin 传。 */\nexport interface WinInvocation { script: string; input?: string }\n\n/** 把多行 PowerShell 脚本编码成 -EncodedCommand,避免 stdin 被逐行解析拆坏 try 块。 */\nexport function windowsCommand(script: string): string {\n return 'powershell -NoProfile -NonInteractive -EncodedCommand ' + Buffer.from(script, 'utf16le').toString('base64')\n}\n\n/** 探测远端平台,结果按别名缓存。cmd 能跑通就是 Windows。 */\nexport async function detectPlatform(alias: string): Promise<RemotePlatform> {\n const hit = PLATFORM_CACHE.get(alias)\n if (hit) return hit\n const raw = await sshExec(alias, {}, '', { timeoutMs: 15_000 }, 'cmd /c echo Windows_NT')\n const platform: RemotePlatform = /Windows_NT/.test(raw.stdout) ? 'windows' : 'posix'\n PLATFORM_CACHE.set(alias, platform)\n return platform\n}\n\n/**\n * 在远端 bash 里解码 RM_PATH 环境变量并做 ~/ 展开。返回一段可拼进脚本前部的代码。\n * 注:不直接内嵌复杂 bash,只用最简 case,~/ 展开交给远端 $HOME。\n */\nexport const PATH_PROLOGUE = [\n 'set +e',\n 'b64d() { printf %s \"$1\" | base64 -d 2>/dev/null; }',\n 'P=$(b64d \"$RM_PATH\")',\n 'case \"$P\" in \"~\"*) eval \"P=\\\"$P\\\"\" ;; esac',\n].join('\\n')\n\n/** 拼出远端命令行的 env 前缀:VAR='<b64>' VAR2=... bash -s。 */\nexport function envPrefix(env: Record<string, string>): string {\n return Object.entries(env)\n .map(([k, v]) => k + \"='\" + v + \"'\")\n .join(' ') + ' bash -s'\n}\n\n/** 一次 ssh 调用的输出。 */\nexport interface SshRawResult { exitCode: number | null; stdout: string; stderr: string }\n\n/**\n * 执行远端脚本。env 提供 base64 数据变量,脚本主体写进 stdin。\n * 返回原始 ssh 输出,未做成功与失败判定。\n */\nexport async function sshExec(\n alias: string,\n env: Record<string, string>,\n scriptBody: string,\n opts: { timeoutMs?: number } = {},\n remoteCommand?: string,\n): Promise<SshRawResult> {\n const timeoutMs = opts.timeoutMs ?? 30_000\n return await new Promise<SshRawResult>((resolve, reject) => {\n const child = spawn('ssh', [\n '-o', 'BatchMode=yes',\n '-o', 'ConnectTimeout=' + Math.max(1, Math.floor(timeoutMs / 1000)),\n alias,\n remoteCommand ?? envPrefix(env),\n ], { stdio: ['pipe', 'pipe', 'pipe'] })\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let settled = false\n const timer = setTimeout(() => { timedOut = true; child.kill('SIGKILL') }, timeoutMs)\n child.stdout.setEncoding('utf8')\n child.stderr.setEncoding('utf8')\n child.stdout.on('data', (d: string) => { stdout += d })\n child.stderr.on('data', (d: string) => { stderr += d })\n child.on('error', (err) => {\n if (settled) return; settled = true; clearTimeout(timer)\n reject(new Error('ssh 连接失败:' + err.message))\n })\n child.on('close', (code) => {\n if (settled) return; settled = true; clearTimeout(timer)\n resolve({ exitCode: timedOut ? -1 : code, stdout, stderr })\n })\n child.stdin.write(scriptBody)\n child.stdin.end()\n })\n}\n\n/**\n * 执行并把结果翻译成本地约定:exit 0 视为成功,返回 stdout 文本;否则解析 stderr 首行 __ERR__<code>__<message> 归对应 FsError,找不到则统一 FS_IO_ERROR 并带详情。\n * 超时即 exit -1,归 FS_IO_ERROR。\n */\nexport async function sshOutcome(\n alias: string,\n env: Record<string, string>,\n scriptBody: string,\n opts: { timeoutMs?: number } = {},\n win?: WinInvocation,\n): Promise<{ code: string; stdout: string }> {\n const platform = await detectPlatform(alias)\n let body = scriptBody\n let remoteCommand: string | undefined\n if (platform === 'windows') {\n if (!win) throw new FsError('该操作缺少 Windows 版本的远端脚本', 'FS_IO_ERROR')\n body = win.input ?? ''\n remoteCommand = windowsCommand(win.script)\n }\n const raw = await sshExec(alias, env, body, opts, remoteCommand).catch((e: Error) => {\n throw new FsError(e.message, 'FS_IO_ERROR')\n })\n if (raw.exitCode === -1) throw new FsError('远程执行超时', 'FS_IO_ERROR')\n if (raw.exitCode === 0) return { code: 'OK', stdout: raw.stdout }\n // 解析 stderr 的 __ERR__ 行\n const line = raw.stderr.split('\\n').find((l: string) => l.startsWith(ERR_PREFIX))\n if (line) {\n const rest = line.slice(ERR_PREFIX.length)\n const sep = rest.indexOf('__')\n const code = sep === -1 ? 'FS_IO_ERROR' : rest.slice(0, sep)\n const message = sep === -1 ? rest : rest.slice(sep + 2)\n throw new FsError(message || '远程执行失败', code === 'FS_IO_ERROR' ? 'FS_IO_ERROR' : (code as never))\n }\n const detail = raw.stderr.trim() || '远程执行失败且无输出'\n throw new FsError(detail, 'FS_IO_ERROR')\n}\n\n/** 远端 bash 失败打一行 __ERR__ 并退出的辅助片段,拼进脚本。 */\nexport const DIE_HELPER = [\n 'die() { local c=\"$1\"; shift; local m=\"$*\"; echo \"__ERR__${c}__${m}\" >&2; exit 1; }',\n].join('\\n')\n\n/** 远端 bash 输出 base64 的辅助。echo_b64() { printf %s \"$1\" | base64 -w0; } */\nexport const B64_HELPER = [\n 'b64out() { printf %s \"$1\" | base64 -w0 2>/dev/null; }',\n].join('\\n')\n", "/**\n * read_remote 工具定义。语义逐字移植自官方 read 与 read-render:行号分页、offset、limit、totalLines、幻影空行处理。\n * 唯一差异是走 RemoteFileSystem,即 ssh,且 file_path 支持「别名:路径」前缀。\n */\n\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { GenericCallView, ReadResultView, ToolResult } from '@deepseek-ai/dsh-tools'\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport {\n buildWindow,\n formatReadOutput,\n langFromPath,\n readMetaFromMeta,\n} from './render.ts'\n\n/** 读行上限、单行字符上限与输出字节上限,与原生 read 默认一致。 */\nexport const READ_LIMIT = 2000\nexport const READ_MAX_LINE_LENGTH = 2000\nexport const READ_MAX_BYTES = 50 * 1024\n\nfunction parsePositiveInteger(value: number, name: string): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {\n throw new Error(name + ' must be a positive integer')\n }\n return value\n}\n\ninterface ReadInput { filePath: string; offset: number; limit: number }\n\n/** 校验 read_remote 参数:空 file_path 拒绝,offset 与 limit 须为正整数。 */\nexport function parseReadArgs(args: { file_path: string; offset?: number; limit?: number }): ReadInput {\n if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')\n const offset = args.offset === undefined ? 1 : parsePositiveInteger(args.offset, 'offset')\n const limit = args.limit === undefined ? READ_LIMIT : parsePositiveInteger(args.limit, 'limit')\n if (limit > READ_LIMIT) throw new Error('limit must be less than or equal to ' + READ_LIMIT)\n return { filePath: args.file_path, offset, limit }\n}\n\n/** 构建 read_remote 的 defineTool 对象。 */\nexport function createReadRemoteTool(fs: RemoteFileSystem): ReturnType<typeof defineTool> {\n return defineTool({\n name: 'read_remote',\n description: 'Read a UTF-8 text file on a remote host over ssh and return line-numbered content.',\n parameters: {\n file_path: { type: 'string', required: true, description: 'Remote target and path in one: alias:path, e.g. wsl:~/x.md or myserver:/etc/hosts. Alias comes from your ssh config.' },\n offset: { type: 'number', description: '1-based first line to return. Defaults to 1.' },\n limit: { type: 'number', description: 'Maximum number of lines to return. Defaults to ' + READ_LIMIT + '.' },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n path: { type: 'string', required: true },\n offset: { type: 'integer', required: true },\n lines: {\n type: 'array',\n required: true,\n items: { type: 'object', additionalProperties: false, properties: { number: { type: 'integer', required: true }, text: { type: 'string', required: true } } },\n },\n totalLines: { type: 'integer', required: true },\n },\n },\n render: (args, value) => {\n const input = parseReadArgs(args as { file_path: string; offset?: number; limit?: number })\n const endLine = value.lines.at(-1)?.number ?? Math.max(0, value.offset - 1)\n const truncatedByBytes = value.lines.length < input.limit && endLine < value.totalLines\n return [{ type: 'text', text: formatReadOutput(value.path, { offset: value.offset, lines: value.lines, totalLines: value.totalLines, ...(truncatedByBytes ? { truncatedByBytes: true } : {}) }) }]\n },\n presentationMeta: (_args, value) => {\n const lang = langFromPath(value.path)\n return { path: value.path, offset: value.offset, lines: value.lines.map(({ number, text }) => ({ number, text })), totalLines: value.totalLines, ...(lang === undefined ? {} : { lang }) }\n },\n },\n isConcurrencySafe: () => true,\n async execute(args, exec) {\n const input = parseReadArgs(args as { file_path: string; offset?: number; limit?: number })\n if (exec.signal.aborted) throw new FsError('cancelled', 'FS_ABORTED')\n const target = await fs.resolve(input.filePath)\n const text = await fs.readText(target)\n const window = buildWindow(text, { offset: input.offset, limit: input.limit, maxLineLength: READ_MAX_LINE_LENGTH, maxBytes: READ_MAX_BYTES }, target.displayPath)\n return { path: target.displayPath, offset: input.offset, lines: window.lines, totalLines: window.totalLines }\n },\n presentResult(_args, result: ToolResult): ReadResultView | undefined {\n if (result.isError) return undefined\n const meta = readMetaFromMeta(result.meta)\n if (meta === undefined) return undefined\n const only = result.content.length === 1 ? result.content[0] : undefined\n const text = only?.type === 'text' ? only.text : undefined\n if (text === undefined) return undefined\n const body = /^<path>[^\\n]*<\\/path>\\n<type>file<\\/type>\\n<content>\\n([\\s\\S]*)\\n<\\/content>$/u.exec(text)?.[1]\n if (body === undefined) return undefined\n return { card: 'read', path: meta.path, offset: meta.offset, lines: meta.lines, totalLines: meta.totalLines, ...(meta.lang === undefined ? {} : { lang: meta.lang }), content: [{ type: 'text', text: body }] }\n },\n presentCall(args): GenericCallView {\n const a = args as { file_path: string; offset?: number; limit?: number }\n const windowText = a.limit !== undefined && a.limit > 0\n ? ' (' + (a.offset ?? 1) + ' - ' + ((a.offset ?? 1) + a.limit - 1) + ')'\n : a.offset !== undefined ? ' (from line ' + a.offset + ')' : ''\n return { card: 'generic', title: 'Read ' + a.file_path + windowText, kind: 'read', locations: [{ path: a.file_path, line: a.offset ?? 1 }] }\n },\n })\n}", "/**\n * 远程工具的纯渲染辅助:读窗口、模型文本信封、diff hunk 卡片与 read 卡 meta。\n * 语义逐字移植自官方 @deepseek-ai/dsh-tool-fs,覆盖 read-render、diff、read、write、edit;不引该包,因其不导出这些,只为保持与原生卡片能力一致。\n */\n\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { FileDiff } from '@deepseek-ai/dsh-tools'\nimport { structuredPatch } from 'diff'\n\n/** 单个 read 行。 */\nexport interface FileTextLine { number: number; text: string }\n/** read 窗口请求。 */\nexport interface ReadWindowRequest {\n offset: number;\n limit: number;\n maxLineLength: number;\n maxBytes: number;\n}\n/** 读窗口结果。 */\nexport interface WindowResult {\n lines: FileTextLine[];\n totalLines: number;\n truncatedByBytes: boolean;\n}\n\nfunction truncateLine(line: string, maxLineLength: number): string {\n return line.length > maxLineLength\n ? line.substring(0, maxLineLength) + '... (line truncated to ' + maxLineLength + ' chars)'\n : line\n}\nfunction stripCarriageReturn(line: string): string {\n return line.endsWith('\\r') ? line.slice(0, -1) : line\n}\n\n/**\n * 从整文件文本切出带行号的窗口。幻影空行与末尾无换行按原生语义处理,offset 越界抛 FS_NOT_FOUND。\n */\nexport function buildWindow(text: string, request: ReadWindowRequest, displayPath: string): WindowResult {\n const linesOut: FileTextLine[] = []\n let totalLines = 0\n let truncatedByBytes = false\n let outputBytes = 0\n\n // 逐行扫描:先精确统计 totalLines,同时收集窗口。\n // 解析规则:按 \\n 切分,文件以换行结尾时不追加尾随空行,空文件为 0 行;\n // 无换行的残余也计为一行。与原生逐 chunk 扫描等价。\n const parts = text.split('\\n')\n const hasTrailingNewline = text.endsWith('\\n')\n const meaningful = hasTrailingNewline ? parts.slice(0, -1) : parts\n // 空文件:split 得空串且无末尾换行,故为 0 行\n if (text.length === 0) {\n return { lines: [], totalLines: 0, truncatedByBytes: false }\n }\n totalLines = meaningful.length\n for (let i = 0; i < meaningful.length; i += 1) {\n const raw = meaningful[i]\n if (truncatedByBytes) break\n const lineNumber = i + 1\n if (lineNumber < request.offset || linesOut.length >= request.limit) continue\n const shown = truncateLine(stripCarriageReturn(raw), request.maxLineLength)\n const bytes = Buffer.byteLength(shown, 'utf8') + (linesOut.length > 0 ? 1 : 0)\n if (outputBytes + bytes > request.maxBytes) { truncatedByBytes = true; break }\n outputBytes += bytes\n linesOut.push({ number: lineNumber, text: shown })\n }\n if (!truncatedByBytes && request.offset > totalLines && !(totalLines === 0 && request.offset === 1)) {\n throw new FsError('offset ' + request.offset + ' is out of range for \"' + displayPath + '\" (' + totalLines + ' lines)', 'FS_NOT_FOUND')\n }\n return { lines: linesOut, totalLines, truncatedByBytes }\n}\n/** 读结果模型信封正文。 */\nexport interface FileReadOutcome {\n offset: number;\n lines: FileTextLine[];\n totalLines: number;\n truncatedByBytes?: true;\n}\n/** 组装 read 的模型可见信封文本,含行号与结尾说明。 */\nexport function formatReadOutput(displayPath: string, outcome: FileReadOutcome): string {\n const endLine = outcome.lines.at(-1)?.number ?? Math.max(0, outcome.offset - 1)\n let footer: string\n if (outcome.truncatedByBytes) {\n footer = '(Output capped. Showing lines ' + outcome.offset + '-' + endLine + '. Use offset=' + (endLine + 1) + ' to continue.)'\n } else if (endLine < outcome.totalLines) {\n footer = '(Showing lines ' + outcome.offset + '-' + endLine + ' of ' + outcome.totalLines + '. Use offset=' + (endLine + 1) + ' to continue.)'\n } else {\n footer = '(End of file - total ' + outcome.totalLines + ' lines)'\n }\n const body = outcome.lines.length > 0\n ? outcome.lines.map((line) => line.number + ': ' + line.text).join('\\n') + '\\n\\n' + footer\n : footer\n return '<path>' + displayPath + '</path>\\n<type>file</type>\\n<content>\\n' + body + '\\n</content>'\n}\n\n/** 从路径推断高亮语言。 */\nconst LANG_BY_EXTENSION: Record<string, string> = {\n ts:'ts', tsx:'tsx', mts:'ts', cts:'ts', js:'js', jsx:'jsx', mjs:'js', cjs:'js',\n json:'json', jsonc:'json', py:'py', rb:'rb', go:'go', rs:'rs', java:'java',\n c:'c', h:'c', cc:'cpp', cpp:'cpp', hpp:'cpp', cxx:'cpp', cs:'cs', kt:'kotlin',\n swift:'swift', php:'php', sh:'sh', bash:'sh', zsh:'sh', yaml:'yaml', yml:'yaml',\n toml:'toml', ini:'ini', md:'md', markdown:'md', mdx:'mdx', html:'html', htm:'html',\n css:'css', scss:'scss', less:'less', sql:'sql', xml:'xml', lua:'lua',\n}\n/** 由路径扩展名取语言提示。 */\nexport function langFromPath(path: string): string | undefined {\n const base = path.slice(Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\\\')) + 1)\n const dot = base.lastIndexOf('.')\n if (dot <= 0) return undefined\n const ext = base.slice(dot + 1).toLowerCase()\n return Object.hasOwn(LANG_BY_EXTENSION, ext) ? LANG_BY_EXTENSION[ext] : undefined\n}\n\n/** read 卡的可回放 meta。 */\nexport interface FsReadMeta {\n path: string;\n offset: number;\n lines: FileTextLine[];\n totalLines: number;\n lang?: string;\n}\n/** 从回放 meta 收紧为合法 read 窗口,不合法返回 undefined 走通用卡。 */\nexport function readMetaFromMeta(meta: unknown): FsReadMeta | undefined {\n if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return undefined\n const m = meta as Record<string, unknown>\n const { path, offset, lines, totalLines, lang } = m\n if (typeof path !== 'string' || typeof totalLines !== 'number' || typeof offset !== 'number') return undefined\n if (!Number.isInteger(offset) || offset < 1) return undefined\n if (!Number.isInteger(totalLines) || totalLines < 0) return undefined\n if (!Array.isArray(lines) || !lines.every((l) => l !== null && typeof l === 'object'\n && !Array.isArray(l) && typeof (l as { number?: unknown }).number === 'number'\n && Number.isInteger((l as { number: number }).number) && (l as { number: number }).number >= 1\n && typeof (l as { text?: unknown }).text === 'string')) return undefined\n if (lang !== undefined && typeof lang !== 'string') return undefined\n const typed = lines as FileTextLine[]\n let prev = offset - 1\n for (const { number } of typed) { if (number <= prev || number > totalLines) return undefined; prev = number }\n return { path, offset, lines: typed, totalLines, ...(lang === undefined ? {} : { lang }) }\n}\n\n/** write 与 edit 结果卡 meta,diffs 数组。 */\nexport type FsDiffMeta = { diffs: FileDiff[] }\n\n/** 计算 before 与 after 的 hunk diff,3 行上下文,供结果卡展示。 */\nexport function computeHunkDiffs(path: string, before: string, after: string): FileDiff[] {\n const patch = structuredPatch('', '', before, after, undefined, undefined, { context: 3 })\n const diffs: FileDiff[] = []\n for (const hunk of patch.hunks) {\n const oldLines: string[] = []\n const newLines: string[] = []\n for (const line of hunk.lines) {\n if (line.startsWith('\\\\')) continue\n const text = line.slice(1)\n if (line.startsWith('-')) oldLines.push(text)\n else if (line.startsWith('+')) newLines.push(text)\n else { oldLines.push(text); newLines.push(text) }\n }\n diffs.push({ path, oldText: oldLines.length > 0 ? oldLines.join('\\n') : null, newText: newLines.join('\\n') })\n }\n return diffs\n}\n\n/** 从回放 meta 收紧为合法 diffs,不合法返回 undefined。 */\nexport function diffsFromMeta(meta: unknown): FileDiff[] | undefined {\n if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return undefined\n const diffs = (meta as Record<string, unknown>).diffs\n if (!Array.isArray(diffs) || diffs.length === 0) return undefined\n const valid = diffs.every((d) => d !== null && typeof d === 'object' && !Array.isArray(d)\n && typeof (d as { path?: unknown }).path === 'string'\n && ((d as { oldText?: unknown }).oldText === null || typeof (d as { oldText?: unknown }).oldText === 'string')\n && typeof (d as { newText?: unknown }).newText === 'string')\n return valid ? diffs as FileDiff[] : undefined\n}\n\n/** write 成功信封。 */\nexport function formatWriteOutput(displayPath: string, operation: 'create' | 'update'): string {\n const verb = operation === 'create' ? 'Created' : 'Updated'\n return '<path>' + displayPath + '</path>\\n<type>file</type>\\n<content>\\n' + verb + ' file\\n</content>'\n}\n\n/** edit 成功信封。 */\nexport function formatEditOutput(displayPath: string, replaceAll: boolean): string {\n return replaceAll\n ? 'The file ' + displayPath + ' has been updated. All occurrences were successfully replaced.'\n : 'The file ' + displayPath + ' has been updated successfully.'\n}\n", "export default class Diff {\n diff(oldStr, newStr, \n // Type below is not accurate/complete - see above for full possibilities - but it compiles\n options = {}) {\n let callback;\n if (typeof options === 'function') {\n callback = options;\n options = {};\n }\n else if ('callback' in options) {\n callback = options.callback;\n }\n // Allow subclasses to massage the input prior to running\n const oldString = this.castInput(oldStr, options);\n const newString = this.castInput(newStr, options);\n const oldTokens = this.removeEmpty(this.tokenize(oldString, options));\n const newTokens = this.removeEmpty(this.tokenize(newString, options));\n return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);\n }\n diffWithOptionsObj(oldTokens, newTokens, options, callback) {\n var _a;\n const done = (value) => {\n value = this.postProcess(value, options);\n if (callback) {\n setTimeout(function () { callback(value); }, 0);\n return undefined;\n }\n else {\n return value;\n }\n };\n const newLen = newTokens.length, oldLen = oldTokens.length;\n let editLength = 1;\n let maxEditLength = newLen + oldLen;\n if (options.maxEditLength != null) {\n maxEditLength = Math.min(maxEditLength, options.maxEditLength);\n }\n const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;\n const abortAfterTimestamp = Date.now() + maxExecutionTime;\n const bestPath = [{ oldPos: -1, lastComponent: undefined }];\n // Seed editLength = 0, i.e. the content starts with the same values\n let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);\n if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {\n // Identity per the equality and tokenizer\n return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));\n }\n // Once we hit the right edge of the edit graph on some diagonal k, we can\n // definitely reach the end of the edit graph in no more than k edits, so\n // there's no point in considering any moves to diagonal k+1 any more (from\n // which we're guaranteed to need at least k+1 more edits).\n // Similarly, once we've reached the bottom of the edit graph, there's no\n // point considering moves to lower diagonals.\n // We record this fact by setting minDiagonalToConsider and\n // maxDiagonalToConsider to some finite value once we've hit the edge of\n // the edit graph.\n // This optimization is not faithful to the original algorithm presented in\n // Myers's paper, which instead pointlessly extends D-paths off the end of\n // the edit graph - see page 7 of Myers's paper which notes this point\n // explicitly and illustrates it with a diagram. This has major performance\n // implications for some common scenarios. For instance, to compute a diff\n // where the new text simply appends d characters on the end of the\n // original text of length n, the true Myers algorithm will take O(n+d^2)\n // time while this optimization needs only O(n+d) time.\n let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;\n // Main worker method. checks all permutations of a given edit length for acceptance.\n const execEditLength = () => {\n for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {\n let basePath;\n const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];\n if (removePath) {\n // No one else is going to attempt to use this value, clear it\n // @ts-expect-error - perf optimisation. This type-violating value will never be read.\n bestPath[diagonalPath - 1] = undefined;\n }\n let canAdd = false;\n if (addPath) {\n // what newPos will be after we do an insertion:\n const addPathNewPos = addPath.oldPos - diagonalPath;\n canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;\n }\n const canRemove = removePath && removePath.oldPos + 1 < oldLen;\n if (!canAdd && !canRemove) {\n // If this path is a terminal then prune\n // @ts-expect-error - perf optimisation. This type-violating value will never be read.\n bestPath[diagonalPath] = undefined;\n continue;\n }\n // Select the diagonal that we want to branch from. We select the prior\n // path whose position in the old string is the farthest from the origin\n // and does not pass the bounds of the diff graph\n if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {\n basePath = this.addToPath(addPath, true, false, 0, options);\n }\n else {\n basePath = this.addToPath(removePath, false, true, 1, options);\n }\n newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);\n if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {\n // If we have hit the end of both strings, then we are done\n return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;\n }\n else {\n bestPath[diagonalPath] = basePath;\n if (basePath.oldPos + 1 >= oldLen) {\n maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);\n }\n if (newPos + 1 >= newLen) {\n minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);\n }\n }\n }\n editLength++;\n };\n // Performs the length of edit iteration. Is a bit fugly as this has to support the\n // sync and async mode which is never fun. Loops over execEditLength until a value\n // is produced, or until the edit length exceeds options.maxEditLength (if given),\n // in which case it will return undefined.\n if (callback) {\n (function exec() {\n setTimeout(function () {\n if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {\n return callback(undefined);\n }\n if (!execEditLength()) {\n exec();\n }\n }, 0);\n }());\n }\n else {\n while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {\n const ret = execEditLength();\n if (ret) {\n return ret;\n }\n }\n }\n }\n addToPath(path, added, removed, oldPosInc, options) {\n const last = path.lastComponent;\n if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {\n return {\n oldPos: path.oldPos + oldPosInc,\n lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }\n };\n }\n else {\n return {\n oldPos: path.oldPos + oldPosInc,\n lastComponent: { count: 1, added: added, removed: removed, previousComponent: last }\n };\n }\n }\n extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {\n const newLen = newTokens.length, oldLen = oldTokens.length;\n let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;\n while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {\n newPos++;\n oldPos++;\n commonCount++;\n if (options.oneChangePerToken) {\n basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };\n }\n }\n if (commonCount && !options.oneChangePerToken) {\n basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };\n }\n basePath.oldPos = oldPos;\n return newPos;\n }\n equals(left, right, options) {\n if (options.comparator) {\n return options.comparator(left, right);\n }\n else {\n return left === right\n || (!!options.ignoreCase && left.toLowerCase() === right.toLowerCase());\n }\n }\n removeEmpty(array) {\n const ret = [];\n for (let i = 0; i < array.length; i++) {\n if (array[i]) {\n ret.push(array[i]);\n }\n }\n return ret;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n castInput(value, options) {\n return value;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n tokenize(value, options) {\n return Array.from(value);\n }\n join(chars) {\n // Assumes ValueT is string, which is the case for most subclasses.\n // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op)\n // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF\n // assume tokens and values are strings, but not completely - is weird and janky.\n return chars.join('');\n }\n postProcess(changeObjects, \n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n options) {\n return changeObjects;\n }\n get useLongestToken() {\n return false;\n }\n buildValues(lastComponent, newTokens, oldTokens) {\n // First we convert our linked list of components in reverse order to an\n // array in the right order:\n const components = [];\n let nextComponent;\n while (lastComponent) {\n components.push(lastComponent);\n nextComponent = lastComponent.previousComponent;\n delete lastComponent.previousComponent;\n lastComponent = nextComponent;\n }\n components.reverse();\n const componentLen = components.length;\n let componentPos = 0, newPos = 0, oldPos = 0;\n for (; componentPos < componentLen; componentPos++) {\n const component = components[componentPos];\n if (!component.removed) {\n if (!component.added && this.useLongestToken) {\n let value = newTokens.slice(newPos, newPos + component.count);\n value = value.map(function (value, i) {\n const oldValue = oldTokens[oldPos + i];\n return oldValue.length > value.length ? oldValue : value;\n });\n component.value = this.join(value);\n }\n else {\n component.value = this.join(newTokens.slice(newPos, newPos + component.count));\n }\n newPos += component.count;\n // Common case\n if (!component.added) {\n oldPos += component.count;\n }\n }\n else {\n component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));\n oldPos += component.count;\n }\n }\n return components;\n }\n}\n", "import Diff from './base.js';\nimport { generateOptions } from '../util/params.js';\nclass LineDiff extends Diff {\n constructor() {\n super(...arguments);\n this.tokenize = tokenize;\n }\n equals(left, right, options) {\n // If we're ignoring whitespace, we need to normalise lines by stripping\n // whitespace before checking equality. (This has an annoying interaction\n // with newlineIsToken that requires special handling: if newlines get their\n // own token, then we DON'T want to trim the *newline* tokens down to empty\n // strings, since this would cause us to treat whitespace-only line content\n // as equal to a separator between lines, which would be weird and\n // inconsistent with the documented behavior of the options.)\n if (options.ignoreWhitespace) {\n if (!options.newlineIsToken || !left.includes('\\n')) {\n left = left.trim();\n }\n if (!options.newlineIsToken || !right.includes('\\n')) {\n right = right.trim();\n }\n }\n else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {\n if (left.endsWith('\\n')) {\n left = left.slice(0, -1);\n }\n if (right.endsWith('\\n')) {\n right = right.slice(0, -1);\n }\n }\n return super.equals(left, right, options);\n }\n}\nexport const lineDiff = new LineDiff();\nexport function diffLines(oldStr, newStr, options) {\n return lineDiff.diff(oldStr, newStr, options);\n}\nexport function diffTrimmedLines(oldStr, newStr, options) {\n options = generateOptions(options, { ignoreWhitespace: true });\n return lineDiff.diff(oldStr, newStr, options);\n}\n// Exported standalone so it can be used from jsonDiff too.\nexport function tokenize(value, options) {\n if (options.stripTrailingCr) {\n // remove one \\r before \\n to match GNU diff's --strip-trailing-cr behavior\n value = value.replace(/\\r\\n/g, '\\n');\n }\n const retLines = [], linesAndNewlines = value.split(/(\\n|\\r\\n)/);\n // Ignore the final empty token that occurs if the string ends with a new line\n if (!linesAndNewlines[linesAndNewlines.length - 1]) {\n linesAndNewlines.pop();\n }\n // Merge the content and line separators into single tokens\n for (let i = 0; i < linesAndNewlines.length; i++) {\n const line = linesAndNewlines[i];\n if (i % 2 && !options.newlineIsToken) {\n retLines[retLines.length - 1] += line;\n }\n else {\n retLines.push(line);\n }\n }\n return retLines;\n}\n", "import { diffLines } from '../diff/line.js';\n/**\n * Returns true if the filename contains characters that require C-style\n * quoting (as used by Git and GNU diffutils in diff output).\n */\nfunction needsQuoting(s) {\n for (let i = 0; i < s.length; i++) {\n if (s[i] < '\\x20' || s[i] > '\\x7e' || s[i] === '\"' || s[i] === '\\\\') {\n return true;\n }\n }\n return false;\n}\n/**\n * C-style quotes a filename, encoding special characters as escape sequences\n * and non-ASCII bytes as octal escapes. This is the inverse of\n * `parseQuotedFileName` in parse.ts.\n *\n * Non-ASCII bytes are encoded as UTF-8 before being emitted as octal escapes.\n * This matches the behaviour of both Git and GNU diffutils, which always emit\n * UTF-8 octal escapes regardless of the underlying filesystem encoding (e.g.\n * Git for Windows converts from NTFS's UTF-16 to UTF-8 internally).\n *\n * If the filename doesn't need quoting, returns it as-is.\n */\nfunction quoteFileNameIfNeeded(s) {\n if (!needsQuoting(s)) {\n return s;\n }\n let result = '\"';\n const bytes = new TextEncoder().encode(s);\n let i = 0;\n while (i < bytes.length) {\n const b = bytes[i];\n // See https://en.wikipedia.org/wiki/Escape_sequences_in_C#Escape_sequences\n if (b === 0x07) {\n result += '\\\\a';\n }\n else if (b === 0x08) {\n result += '\\\\b';\n }\n else if (b === 0x09) {\n result += '\\\\t';\n }\n else if (b === 0x0a) {\n result += '\\\\n';\n }\n else if (b === 0x0b) {\n result += '\\\\v';\n }\n else if (b === 0x0c) {\n result += '\\\\f';\n }\n else if (b === 0x0d) {\n result += '\\\\r';\n }\n else if (b === 0x22) {\n result += '\\\\\"';\n }\n else if (b === 0x5c) {\n result += '\\\\\\\\';\n }\n else if (b >= 0x20 && b <= 0x7e) {\n // Just a printable ASCII character that is neither a double quote nor a\n // backslash; no need to escape it.\n result += String.fromCharCode(b);\n }\n else {\n // Either part of a non-ASCII character or a control character without a\n // special escape sequence; needs escaping as a 3-digit octal escape\n result += '\\\\' + b.toString(8).padStart(3, '0');\n }\n i++;\n }\n result += '\"';\n return result;\n}\nexport const INCLUDE_HEADERS = {\n includeIndex: true,\n includeUnderline: true,\n includeFileHeaders: true\n};\nexport const FILE_HEADERS_ONLY = {\n includeIndex: false,\n includeUnderline: false,\n includeFileHeaders: true\n};\nexport const OMIT_HEADERS = {\n includeIndex: false,\n includeUnderline: false,\n includeFileHeaders: false\n};\nexport function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {\n let optionsObj;\n if (!options) {\n optionsObj = {};\n }\n else if (typeof options === 'function') {\n optionsObj = { callback: options };\n }\n else {\n optionsObj = options;\n }\n if (typeof optionsObj.context === 'undefined') {\n optionsObj.context = 4;\n }\n // We copy this into its own variable to placate TypeScript, which thinks\n // optionsObj.context might be undefined in the callbacks below.\n const context = optionsObj.context;\n // @ts-expect-error (runtime check for something that is correctly a static type error)\n if (optionsObj.newlineIsToken) {\n throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions');\n }\n if (!optionsObj.callback) {\n return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));\n }\n else {\n const { callback } = optionsObj;\n diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {\n const patch = diffLinesResultToPatch(diff);\n // TypeScript is unhappy without the cast because it does not understand that `patch` may\n // be undefined here only if `callback` is StructuredPatchCallbackAbortable:\n callback(patch);\n } }));\n }\n function diffLinesResultToPatch(diff) {\n // STEP 1: Build up the patch with no \"\\" lines and with the arrays\n // of lines containing trailing newline characters. We'll tidy up later...\n if (!diff) {\n return;\n }\n diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier\n function contextLines(lines) {\n return lines.map(function (entry) { return ' ' + entry; });\n }\n const hunks = [];\n let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;\n for (let i = 0; i < diff.length; i++) {\n const current = diff[i], lines = current.lines || splitLines(current.value);\n current.lines = lines;\n if (current.added || current.removed) {\n // If we have previous context, start with that\n if (!oldRangeStart) {\n const prev = diff[i - 1];\n oldRangeStart = oldLine;\n newRangeStart = newLine;\n if (prev) {\n curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];\n oldRangeStart -= curRange.length;\n newRangeStart -= curRange.length;\n }\n }\n // Output our changes\n for (const line of lines) {\n curRange.push((current.added ? '+' : '-') + line);\n }\n // Track the updated file position\n if (current.added) {\n newLine += lines.length;\n }\n else {\n oldLine += lines.length;\n }\n }\n else {\n // Identical context lines. Track line changes\n if (oldRangeStart) {\n // Close out any changes that have been output (or join overlapping)\n if (lines.length <= context * 2 && i < diff.length - 2) {\n // Overlapping\n for (const line of contextLines(lines)) {\n curRange.push(line);\n }\n }\n else {\n // end the range and output\n const contextSize = Math.min(lines.length, context);\n for (const line of contextLines(lines.slice(0, contextSize))) {\n curRange.push(line);\n }\n const hunk = {\n oldStart: oldRangeStart,\n oldLines: (oldLine - oldRangeStart + contextSize),\n newStart: newRangeStart,\n newLines: (newLine - newRangeStart + contextSize),\n lines: curRange\n };\n hunks.push(hunk);\n oldRangeStart = 0;\n newRangeStart = 0;\n curRange = [];\n }\n }\n oldLine += lines.length;\n newLine += lines.length;\n }\n }\n // Step 2: eliminate the trailing `\\n` from each line of each hunk, and, where needed, add\n // \"\\".\n for (const hunk of hunks) {\n for (let i = 0; i < hunk.lines.length; i++) {\n if (hunk.lines[i].endsWith('\\n')) {\n hunk.lines[i] = hunk.lines[i].slice(0, -1);\n }\n else {\n hunk.lines.splice(i + 1, 0, '\\\');\n i++; // Skip the line we just added, then continue iterating\n }\n }\n }\n return {\n oldFileName: oldFileName, newFileName: newFileName,\n oldHeader: oldHeader, newHeader: newHeader,\n hunks: hunks\n };\n }\n}\n/**\n * creates a unified diff patch.\n *\n * @param patch either a single structured patch object (as returned by `structuredPatch`) or an\n * array of them (as returned by `parsePatch`).\n * @param headerOptions behaves the same as the `headerOptions` option of `createTwoFilesPatch`.\n * Ignored for patches where `isGit` is `true`.\n *\n * When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's\n * output: it emits a `diff --git` header, emits Git extended headers as appropriate based on\n * properties like `isRename`, `isCreate`, `newMode`, etc, and will omit `---`/`+++` file\n * headers for patches with no hunks (e.g. renames without content changes).\n */\nexport function formatPatch(patch, headerOptions) {\n var _a, _b, _c, _d, _e, _f;\n if (!headerOptions) {\n headerOptions = INCLUDE_HEADERS;\n }\n if (Array.isArray(patch)) {\n if (patch.length > 1 && !headerOptions.includeFileHeaders && !patch.every(p => p.isGit)) {\n throw new Error('Cannot omit file headers on a multi-file patch. '\n + '(The result would be unparseable; how would a tool trying to apply '\n + 'the patch know which changes are to which file?)');\n }\n return patch.map(p => formatPatch(p, headerOptions)).join('\\n');\n }\n const ret = [];\n // Git patches have a fixed header format (diff --git, extended headers,\n // and ---/+++ when hunks are present), so headerOptions is ignored.\n if (patch.isGit) {\n headerOptions = INCLUDE_HEADERS;\n // Emit Git-style diff --git header and extended headers.\n // Git never puts /dev/null in the \"diff --git\" line; for file\n // creations/deletions it uses the real filename on both sides.\n if (!patch.oldFileName) {\n throw new Error('oldFileName must be specified for Git patches');\n }\n if (!patch.newFileName) {\n throw new Error('newFileName must be specified for Git patches');\n }\n let gitOldName = patch.oldFileName;\n let gitNewName = patch.newFileName;\n if (patch.isCreate && gitOldName === '/dev/null') {\n gitOldName = gitNewName.replace(/^b\\//, 'a/');\n }\n else if (patch.isDelete && gitNewName === '/dev/null') {\n gitNewName = gitOldName.replace(/^a\\//, 'b/');\n }\n ret.push('diff --git ' + quoteFileNameIfNeeded(gitOldName) + ' ' + quoteFileNameIfNeeded(gitNewName));\n if (patch.isDelete) {\n ret.push('deleted file mode ' + ((_a = patch.oldMode) !== null && _a !== void 0 ? _a : '100644'));\n }\n if (patch.isCreate) {\n ret.push('new file mode ' + ((_b = patch.newMode) !== null && _b !== void 0 ? _b : '100644'));\n }\n if (patch.oldMode && patch.newMode && !patch.isDelete && !patch.isCreate) {\n ret.push('old mode ' + patch.oldMode);\n ret.push('new mode ' + patch.newMode);\n }\n if (patch.isRename) {\n ret.push('rename from ' + quoteFileNameIfNeeded(((_c = patch.oldFileName) !== null && _c !== void 0 ? _c : '').replace(/^a\\//, '')));\n ret.push('rename to ' + quoteFileNameIfNeeded(((_d = patch.newFileName) !== null && _d !== void 0 ? _d : '').replace(/^b\\//, '')));\n }\n if (patch.isCopy) {\n ret.push('copy from ' + quoteFileNameIfNeeded(((_e = patch.oldFileName) !== null && _e !== void 0 ? _e : '').replace(/^a\\//, '')));\n ret.push('copy to ' + quoteFileNameIfNeeded(((_f = patch.newFileName) !== null && _f !== void 0 ? _f : '').replace(/^b\\//, '')));\n }\n }\n else {\n if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== undefined) {\n ret.push('Index: ' + patch.oldFileName);\n }\n if (headerOptions.includeUnderline) {\n ret.push('===================================================================');\n }\n }\n // Emit --- / +++ file headers. For Git patches with no hunks (e.g.\n // pure renames, mode-only changes), Git omits these, so we do too.\n const hasHunks = patch.hunks.length > 0;\n if (headerOptions.includeFileHeaders && patch.oldFileName !== undefined && patch.newFileName !== undefined\n && (!patch.isGit || hasHunks)) {\n ret.push('--- ' + quoteFileNameIfNeeded(patch.oldFileName) + (patch.oldHeader ? '\\t' + patch.oldHeader : ''));\n ret.push('+++ ' + quoteFileNameIfNeeded(patch.newFileName) + (patch.newHeader ? '\\t' + patch.newHeader : ''));\n }\n for (let i = 0; i < patch.hunks.length; i++) {\n const hunk = patch.hunks[i];\n // Unified Diff Format quirk: If the chunk size is 0,\n // the first number is one lower than one would expect.\n // https://www.artima.com/weblogs/viewpost.jsp?thread=164293\n const oldStart = hunk.oldLines === 0 ? hunk.oldStart - 1 : hunk.oldStart;\n const newStart = hunk.newLines === 0 ? hunk.newStart - 1 : hunk.newStart;\n ret.push('@@ -' + oldStart + ',' + hunk.oldLines\n + ' +' + newStart + ',' + hunk.newLines\n + ' @@');\n for (const line of hunk.lines) {\n ret.push(line);\n }\n }\n return ret.join('\\n') + '\\n';\n}\nexport function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {\n if (typeof options === 'function') {\n options = { callback: options };\n }\n if (!(options === null || options === void 0 ? void 0 : options.callback)) {\n const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);\n if (!patchObj) {\n return;\n }\n return formatPatch(patchObj, options === null || options === void 0 ? void 0 : options.headerOptions);\n }\n else {\n const { callback } = options;\n structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: patchObj => {\n if (!patchObj) {\n callback(undefined);\n }\n else {\n callback(formatPatch(patchObj, options.headerOptions));\n }\n } }));\n }\n}\nexport function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {\n return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);\n}\n/**\n * Split `text` into an array of lines, including the trailing newline character (where present)\n */\nfunction splitLines(text) {\n const hasTrailingNl = text.endsWith('\\n');\n const result = text.split('\\n').map(line => line + '\\n');\n if (hasTrailingNl) {\n result.pop();\n }\n else {\n result.push(result.pop().slice(0, -1));\n }\n return result;\n}\n", "/**\n * write_remote 工具定义。语义移植自官方 write:全文件写、返回 before 与 after。\n * 恒用 createIfAbsent,只新建,目标已存在即拒绝,绝无覆写分支;diff 卡展示。\n * 无沙箱升级字段,见 DESIGN §7 刻意差异,schema 不声明 escalation 字段。\n */\n\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { DiffCallView, DiffResultView, ToolResult } from '@deepseek-ai/dsh-tools'\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport { computeHunkDiffs, diffsFromMeta, formatWriteOutput } from './render.ts'\n\ninterface WriteInput { filePath: string; content: string }\n\n/** 校验 write_remote 参数:仅需非空 file_path,空 content 合法,可写空文件。 */\nexport function parseWriteArgs(args: { file_path: string; content: string }): WriteInput {\n if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')\n return { filePath: args.file_path, content: args.content }\n}\n\nexport function createWriteRemoteTool(fs: RemoteFileSystem): ReturnType<typeof defineTool> {\n return defineTool({\n name: 'write_remote',\n description: 'Create a new UTF-8 text file on a remote host over ssh. The target must NOT already exist — write_remote only creates new files, never overwrites.',\n parameters: {\n file_path: { type: 'string', required: true, description: 'Remote target and path in one: alias:path, e.g. wsl:~/x.md or myserver:/etc/hosts. Must not already exist.' },\n content: { type: 'string', required: true, description: 'Full UTF-8 text content to write.' },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n path: { type: 'string', required: true },\n operation: { type: 'string', required: true, enum: ['create', 'update'] },\n before: { required: true, oneOf: [{ type: 'string' }, { type: 'null' }] },\n after: { type: 'string', required: true },\n },\n },\n render: (_args, value) => [{ type: 'text', text: formatWriteOutput(value.path, value.operation) }],\n presentationMeta: (args, value) => ({ diffs: value.before === null ? [] : computeHunkDiffs((args as { file_path: string }).file_path, value.before, value.after).map(({ path, oldText, newText }) => ({ path, oldText, newText })) }),\n },\n isConcurrencySafe: () => true,\n async execute(args, exec) {\n const input = parseWriteArgs(args as { file_path: string; content: string })\n if (exec.signal.aborted) throw new FsError('cancelled', 'FS_ABORTED')\n const target = await fs.resolve(input.filePath)\n const outcome = await fs.writeText(target, input.content, { kind: 'createIfAbsent' })\n return { path: target.displayPath, operation: outcome.operation, before: outcome.before, after: outcome.after }\n },\n presentCall(args): DiffCallView {\n const a = args as { file_path: string; content: string }\n return { card: 'diff', title: 'Write ' + a.file_path, diffs: [{ path: a.file_path, oldText: null, newText: a.content }], locations: [{ path: a.file_path }] }\n },\n presentResult(args, result: ToolResult): DiffResultView | undefined {\n if (result.isError) return undefined\n const a = args as { file_path: string; content: string }\n const diffs = diffsFromMeta(result.meta) ?? [{ path: a.file_path, oldText: null, newText: a.content }]\n return { card: 'diff', title: 'Write ' + a.file_path, diffs }\n },\n })\n}", "/**\n * edit_remote 工具定义。\n * 语义移植自官方 edit:字面替换、唯一匹配,多现拒绝即 FS_AMBIGUOUS_EDIT,未命中即 FS_EDIT_NOT_FOUND;拒绝 old_string 与 new_string 相同;replace_all 全替换。\n * 匹配类错误由后端抛原生词表。无沙箱升级字段。\n */\n\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { DiffCallView, DiffResultView, ToolResult } from '@deepseek-ai/dsh-tools'\nimport { FsError } from '@deepseek-ai/dsh-fs'\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport { computeHunkDiffs, diffsFromMeta, formatEditOutput } from './render.ts'\n\ninterface EditInput { filePath: string; oldString: string; newString: string; replaceAll: boolean }\n\n/** 校验 edit_remote 参数。 */\nexport function parseEditArgs(args: { file_path: string; old_string: string; new_string: string; replace_all?: boolean }): EditInput {\n if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')\n if (args.old_string.length === 0) throw new Error('old_string must be a non-empty string')\n if (args.old_string === args.new_string) throw new Error('old_string and new_string must differ')\n return { filePath: args.file_path, oldString: args.old_string, newString: args.new_string, replaceAll: args.replace_all ?? false }\n}\n\nexport function createEditRemoteTool(fs: RemoteFileSystem): ReturnType<typeof defineTool> {\n return defineTool({\n name: 'edit_remote',\n description: 'Edit an existing UTF-8 text file on a remote host over ssh by replacing literal text.',\n parameters: {\n file_path: { type: 'string', required: true, description: 'Remote target and path in one: alias:path, e.g. wsl:~/x.md or myserver:/etc/hosts.' },\n old_string: { type: 'string', required: true, description: 'Literal text to replace. Must match exactly.' },\n new_string: { type: 'string', required: true, description: 'Literal replacement text. Use an empty string to delete the match.' },\n replace_all: { type: 'boolean', description: 'Replace all matches. Defaults to false; when false, old_string must appear exactly once.' },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n path: { type: 'string', required: true },\n before: { type: 'string', required: true },\n after: { type: 'string', required: true },\n },\n },\n render: (args, value) => [{ type: 'text', text: formatEditOutput(value.path, (args as { replace_all?: boolean }).replace_all ?? false) }],\n presentationMeta: (args, value) => ({ diffs: computeHunkDiffs((args as { file_path: string }).file_path, value.before, value.after).map(({ path, oldText, newText }) => ({ path, oldText, newText })) }),\n },\n isConcurrencySafe: () => false,\n async execute(args, exec) {\n const input = parseEditArgs(args as { file_path: string; old_string: string; new_string: string; replace_all?: boolean })\n if (exec.signal.aborted) throw new FsError('cancelled', 'FS_ABORTED')\n const target = await fs.resolve(input.filePath)\n const outcome = await fs.editText(target, { oldString: input.oldString, newString: input.newString, replaceAll: input.replaceAll })\n return { path: target.displayPath, before: outcome.before, after: outcome.after }\n },\n presentCall(args): DiffCallView {\n const a = args as { file_path: string; old_string: string; new_string: string }\n return { card: 'diff', title: 'Edit ' + a.file_path, diffs: [{ path: a.file_path, oldText: a.old_string || null, newText: a.new_string }], locations: [{ path: a.file_path }] }\n },\n presentResult(args, result: ToolResult): DiffResultView | undefined {\n if (result.isError) return undefined\n const a = args as { file_path: string }\n const diffs = diffsFromMeta(result.meta)\n if (diffs === undefined) return undefined\n return { card: 'diff', title: 'Edit ' + a.file_path, diffs }\n },\n })\n}", "/**\n * 在 webServer 上自注册一条 connection 风格的 RPC 通道。\n *\n * 与 dsh-classic-coding 的同名模块同源:dsh 的 connection.rpc.handle 在登记路由时解析 webServer 会抛\n * cannot get property webServer without inject,官方自身也不走该路径。\n * 因此这里直接向 webServer 注册 prefix 路由,复用 connection.requestRejection 的 Host 校验与浏览器鉴权,\n * 并实现 client-request 与 server-response 信封。\n *\n * @module dsh-remote-file-system/rpc-channel\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\n/** RPC 处理器的统一结果信封。 */\nexport type RpcChannelResult =\n | { ok: true; value: unknown }\n | { ok: false; error: { code: string; message: string; details: object } }\n\n/** 通道端点处理器:endpoint 与请求体,返回结果信封。 */\nexport type RpcChannelHandler = (\n endpoint: string,\n payload: unknown,\n signal: AbortSignal,\n) => Promise<RpcChannelResult>\n\n/** webServer 服务的最小面,仅注册 prefix 路由。 */\ninterface WebServerFace {\n register(route: {\n kind: 'prefix'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n }): () => void\n}\n\n/** connection 服务的最小面,仅用请求信任判定。 */\ninterface ConnectionFace {\n requestRejection(request: { headers: IncomingMessage['headers'] }): 401 | 403 | undefined\n}\n\n/** 可注入上下文的最小面。 */\ninterface Injectable {\n inject(deps: string[], callback: (scope: unknown) => void): unknown\n}\n\n/** 注入作用域的最小面。 */\ninterface Scope {\n connection: ConnectionFace\n webServer: WebServerFace\n effect(callback: () => (() => void) | undefined, label: string): unknown\n}\n\n/** 端点路径段的合法字符集,与官方 ENDPOINT_SEGMENT_PATTERN 一致。 */\nconst SEGMENT = /^[A-Za-z0-9_$.-]+$/\n/** 请求体上限,与官方 bridge 默认的 300MiB 对齐。 */\nconst MAX_BODY_BYTES = 300 * 1024 * 1024\n/** 唯一接受的 content-type,与官方信封契约一致。 */\nconst JSON_MIME = 'application/json'\n\n/** 请求体超限的标记错误,由 serve 决定如何回写与断开。 */\nclass PayloadTooLargeError extends Error {}\n\n/**\n * 挂载一条 RPC 通道。webServer 或 connection 缺席时不挂载,其余功能不受影响。\n * @param ctx - 插件上下文。\n * @param channel - 绝对通道前缀,例如 /remote-file-system。\n * @param handler - 端点处理器。\n */\nexport function mountRpcChannel(ctx: Injectable, channel: string, handler: RpcChannelHandler): void {\n ctx.inject(['connection', 'webServer'], (raw) => {\n const scope = raw as Scope\n scope.effect(\n () => scope.webServer.register({\n kind: 'prefix',\n path: channel,\n handler: (req, res) => serve(scope.connection, channel, handler, req, res),\n }),\n channel + ': rpc channel',\n )\n })\n}\n\n/**\n * 处理一次 HTTP 请求:信任与鉴权、端点解析、信封校验、结果回写。\n * @param connection - connection 服务,用其信任判定。\n * @param channel - 本通道前缀。\n * @param handler - 端点处理器。\n * @param req - node 请求。\n * @param res - node 响应。\n */\nasync function serve(\n connection: ConnectionFace,\n channel: string,\n handler: RpcChannelHandler,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const rejection = connection.requestRejection(req)\n if (rejection !== undefined) {\n res.writeHead(rejection)\n res.end(rejection === 401 ? 'unauthorized' : 'forbidden')\n return\n }\n if (req.method !== 'POST') return missing(res)\n const pathname = new URL(req.url ?? '/', 'http://localhost').pathname\n const prefix = channel + '/'\n if (!pathname.startsWith(prefix)) return missing(res)\n const endpoint = pathname.slice(prefix.length)\n if (!endpoint.split('/').every((part) => part !== '' && part !== '.' && part !== '..' && SEGMENT.test(part))) return missing(res)\n if (!isJsonContentType(req)) {\n res.writeHead(415)\n res.end('unsupported media type')\n return\n }\n const declared = Number(req.headers['content-length'])\n if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) return tooLarge(req, res)\n\n let raw: string\n try {\n raw = await readBody(req)\n } catch (error) {\n if (error instanceof PayloadTooLargeError) return tooLarge(req, res)\n res.writeHead(400)\n res.end('body read failed')\n return\n }\n let message: { type?: unknown; rpcId?: unknown; method?: unknown; payload?: unknown }\n try {\n message = JSON.parse(raw) as typeof message\n } catch {\n res.writeHead(400)\n res.end('body is not JSON')\n return\n }\n const rpcId = typeof message.rpcId === 'string' ? message.rpcId : 'invalid-request'\n if (message.type !== 'client-request' || typeof message.method !== 'string') {\n return reply(res, rpcId, { ok: false, error: { code: 'gateway/bad-request', message: 'invalid client-request message', details: {} } })\n }\n if (message.method !== endpoint) {\n return reply(res, rpcId, {\n ok: false,\n error: { code: 'gateway/bad-request', message: 'method ' + JSON.stringify(message.method) + ' does not match endpoint ' + JSON.stringify(endpoint), details: {} },\n })\n }\n const controller = new AbortController()\n const onClose = (): void => { controller.abort(new Error('client disconnected')) }\n res.on('close', onClose)\n let result: RpcChannelResult\n try {\n result = await handler(endpoint, message.payload, controller.signal)\n } catch (error) {\n result = { ok: false, error: { code: 'internal', message: error instanceof Error ? error.message : String(error), details: {} } }\n } finally {\n res.off('close', onClose)\n }\n reply(res, rpcId, result)\n}\n\n/** 路径不匹配或方法不符时统一 404,与官方未认领端点一致。 */\nfunction missing(res: ServerResponse): void {\n res.writeHead(404)\n res.end('not found')\n}\n\n/** 回写一个 server-response 信封。 */\nfunction reply(res: ServerResponse, rpcId: string, result: RpcChannelResult): void {\n res.writeHead(200, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ type: 'server-response', rpcId, result }))\n}\n\n/** 先回 413 再销毁连接:顺序反了 socket 已断,413 永远发不出去。 */\nfunction tooLarge(req: IncomingMessage, res: ServerResponse): void {\n res.writeHead(413, { connection: 'close' })\n res.end('payload too large')\n req.destroy()\n}\n\n/** content-type 必须声明为 application/json,charset 等参数可忽略。 */\nfunction isJsonContentType(req: IncomingMessage): boolean {\n const header = req.headers['content-type']\n if (typeof header !== 'string') return false\n const mime = header.split(';', 1)[0]\n return mime !== undefined && mime.trim().toLowerCase() === JSON_MIME\n}\n\n/** 读取并限长请求体,超限抛 PayloadTooLargeError,由调用方回写 413。 */\nasync function readBody(req: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : chunk as Buffer\n size += buffer.length\n if (size > MAX_BODY_BYTES) throw new PayloadTooLargeError('rpc channel: request body exceeds limit')\n chunks.push(buffer)\n }\n return Buffer.concat(chunks).toString('utf8')\n}\n", "/**\n * /remote-file-system RPC 端点:把 RemoteFileSystem 的读取能力暴露给浏览器半身。\n *\n * 内置右侧栏经 ctx.fs 读主机本地文件,读不到「别名:路径」的远端文件,\n * 因此 client 半身用本通道读取远端文本并自行渲染。\n *\n * @module dsh-remote-file-system/rpc\n */\n\nimport type { RemoteFileSystem } from './remote-fs.ts'\nimport type { RpcChannelResult } from './rpc-channel.ts'\n\n/** 单次预览的字符上限,按 UTF-16 代码单元计,超出即截断并标记;bytes 仍回填完整文本的字节数。 */\nconst MAX_PREVIEW_CHARS = 2 * 1024 * 1024\n\n/** 调用方参数不合法的错误,由 handleRemoteRpc 归 bad-request 而非 internal。 */\nclass BadRequestError extends Error {}\n\n/** 把未知 payload 收窄成字符串字段表。 */\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {}\n}\n\n/** 从字符串字段表里取字符串字段,缺失或类型不符返回 undefined。 */\nfunction readString(record: Record<string, unknown>, key: string): string | undefined {\n const value = record[key]\n return typeof value === 'string' ? value : undefined\n}\n\n/** 读取端点:payload 的 target 为「别名:路径」,返回路径、文本、字节数与截断标记。 */\nasync function readRemote(fs: RemoteFileSystem, payload: unknown): Promise<{ path: string; text: string; bytes: number; truncated: boolean }> {\n const target = readString(asRecord(payload), 'target')\n if (target === undefined || target.trim() === '') throw new BadRequestError('read 缺少 target')\n const resolved = await fs.resolve(target)\n const text = await fs.readText(resolved)\n const bytes = Buffer.byteLength(text, 'utf8')\n if (text.length > MAX_PREVIEW_CHARS) {\n return { path: resolved.displayPath, text: text.slice(0, MAX_PREVIEW_CHARS), bytes, truncated: true }\n }\n return { path: resolved.displayPath, text, bytes, truncated: false }\n}\n\n/**\n * 处理一个端点,失败统一归 internal 信封,保留远端中文详情。\n * @param fs - 远程文件后端。\n * @param endpoint - 端点名。\n * @param payload - 载荷。\n */\nexport async function handleRemoteRpc(fs: RemoteFileSystem, endpoint: string, payload: unknown): Promise<RpcChannelResult> {\n try {\n switch (endpoint) {\n case 'read':\n return { ok: true, value: await readRemote(fs, payload) }\n default:\n return { ok: false, error: { code: 'bad-request', message: '未知端点: ' + endpoint, details: {} } }\n }\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e)\n if (e instanceof BadRequestError) return { ok: false, error: { code: 'bad-request', message, details: {} } }\n return { ok: false, error: { code: 'internal', message, details: {} } }\n }\n}\n", "/**\n * dsh-remote-file-system — host 半身入口。\n *\n * 职责:实例化 RemoteFileSystem 即 ssh 后端,注册 read_remote、write_remote、edit_remote 三个模型可见工具,并为各工具注册等价的 systemPrompt 引导节。\n *\n * 加载形态:组合包 bundle,cordis.patch.yml 插进 profile。官方 @deepseek-ai/* 依赖保持 external,与 host 共享同一份 cordis 与 dsh-fs 实例。\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { RemoteFileSystem } from './remote-fs.ts'\nimport { createReadRemoteTool } from './read.ts'\nimport { createWriteRemoteTool } from './write.ts'\nimport { createEditRemoteTool } from './edit.ts'\nimport { mountRpcChannel } from './rpc-channel.ts'\nimport { handleRemoteRpc } from './rpc.ts'\n\n/** 插件名,与 cordis.patch.yml 的 name 一致,loader 诊断用。 */\nexport const name = 'dsh-remote-file-system'\n\n/** 必需宿主服务:tools 工具注册表与 systemPrompt 引导。 */\nexport const inject = ['tools', 'systemPrompt']\n\n/** 插件配置:enabled 总开关。 */\nexport interface Config {\n /** 是否注册远程工具。默认 true。 */\n enabled?: boolean\n}\n\n/**\n * 插件 apply:实例化 RemoteFileSystem,注册三工具并加 systemPrompt 引导节。\n * 三个工具的引导节复用原生 read、write、edit 的顺序位,节名唯一避免冲突。\n * @param ctx - cordis 上下文。\n * @param config - 插件配置。\n */\nexport function apply(ctx: Context, config: Config = {}): void {\n if (config.enabled === false) return\n\n // 构造一次后端实例,三工具共享。普通对象,不注册为 ctx.fs,无副作用。\n const fs = new RemoteFileSystem()\n\n // systemPrompt:为远程工具注册与原生 read、write、edit 等价的使用引导。\n ctx.systemPrompt.section({\n name: 'tool:read_remote',\n order: ctx.systemPrompt.getSectionOrder('TOOL_READ'),\n text: 'Use read_remote (not shell commands) to inspect UTF-8 text files on remote hosts over ssh. file_path carries the target as alias:path (e.g. wsl:~/x.md). Results include line numbers; use offset and limit to continue reading large files.',\n })\n ctx.systemPrompt.section({\n name: 'tool:write_remote',\n order: ctx.systemPrompt.getSectionOrder('TOOL_WRITE'),\n text: 'Use write_remote to create a NEW UTF-8 text file on a remote host. It only creates — the target must not already exist, and it never overwrites.',\n })\n ctx.systemPrompt.section({\n name: 'tool:edit_remote',\n order: ctx.systemPrompt.getSectionOrder('TOOL_EDIT'),\n text: 'Use edit_remote for targeted literal changes to an existing remote text file. It replaces literal old_string with new_string; by default old_string must appear exactly once.',\n })\n\n // 注册三个工具。defineTool 纯函数返回对象,ctx.tools.register 副作用注册。\n ctx.tools.register(createReadRemoteTool(fs))\n ctx.tools.register(createWriteRemoteTool(fs))\n ctx.tools.register(createEditRemoteTool(fs))\n\n // 浏览器半身经这条自注册通道读取远端文本,供右侧栏预览。\n mountRpcChannel(ctx, '/remote-file-system', (endpoint, payload) => handleRemoteRpc(fs, endpoint, payload))\n}\n"],
5
+ "mappings": ";AAWA;AAAA,EACE,WAAAA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,SAAS,kBAAkB;;;ACd3B,SAAS,aAAa;AACtB,SAAS,eAAe;AAExB,IAAM,aAAa;AAGZ,SAAS,IAAI,OAAuB;AAAE,SAAO,OAAO,KAAK,OAAO,MAAM,EAAE,SAAS,QAAQ;AAAE;AAMlG,IAAM,iBAAiB,oBAAI,IAA4B;AAKhD,SAAS,eAAe,QAAwB;AACrD,SAAO,2DAA2D,OAAO,KAAK,QAAQ,SAAS,EAAE,SAAS,QAAQ;AACpH;AAGA,eAAsB,eAAe,OAAwC;AAC3E,QAAM,MAAM,eAAe,IAAI,KAAK;AACpC,MAAI,IAAK,QAAO;AAChB,QAAM,MAAM,MAAM,QAAQ,OAAO,CAAC,GAAG,IAAI,EAAE,WAAW,KAAO,GAAG,wBAAwB;AACxF,QAAM,WAA2B,aAAa,KAAK,IAAI,MAAM,IAAI,YAAY;AAC7E,iBAAe,IAAI,OAAO,QAAQ;AAClC,SAAO;AACT;AAMO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAGJ,SAAS,UAAU,KAAqC;AAC7D,SAAO,OAAO,QAAQ,GAAG,EACtB,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,IAAI,GAAG,EAClC,KAAK,GAAG,IAAI;AACjB;AASA,eAAsB,QACpB,OACA,KACA,YACA,OAA+B,CAAC,GAChC,eACuB;AACvB,QAAM,YAAY,KAAK,aAAa;AACpC,SAAO,MAAM,IAAI,QAAsB,CAAC,SAAS,WAAW;AAC1D,UAAM,QAAQ,MAAM,OAAO;AAAA,MACzB;AAAA,MAAM;AAAA,MACN;AAAA,MAAM,oBAAoB,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,GAAI,CAAC;AAAA,MAClE;AAAA,MACA,iBAAiB,UAAU,GAAG;AAAA,IAChC,GAAG,EAAE,OAAO,CAAC,QAAQ,QAAQ,MAAM,EAAE,CAAC;AACtC,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,WAAW;AACf,QAAI,UAAU;AACd,UAAM,QAAQ,WAAW,MAAM;AAAE,iBAAW;AAAM,YAAM,KAAK,SAAS;AAAA,IAAE,GAAG,SAAS;AACpF,UAAM,OAAO,YAAY,MAAM;AAC/B,UAAM,OAAO,YAAY,MAAM;AAC/B,UAAM,OAAO,GAAG,QAAQ,CAAC,MAAc;AAAE,gBAAU;AAAA,IAAE,CAAC;AACtD,UAAM,OAAO,GAAG,QAAQ,CAAC,MAAc;AAAE,gBAAU;AAAA,IAAE,CAAC;AACtD,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,UAAI,QAAS;AAAQ,gBAAU;AAAM,mBAAa,KAAK;AACvD,aAAO,IAAI,MAAM,cAAc,IAAI,OAAO,CAAC;AAAA,IAC7C,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,QAAS;AAAQ,gBAAU;AAAM,mBAAa,KAAK;AACvD,cAAQ,EAAE,UAAU,WAAW,KAAK,MAAM,QAAQ,OAAO,CAAC;AAAA,IAC5D,CAAC;AACD,UAAM,MAAM,MAAM,UAAU;AAC5B,UAAM,MAAM,IAAI;AAAA,EAClB,CAAC;AACH;AAMA,eAAsB,WACpB,OACA,KACA,YACA,OAA+B,CAAC,GAChC,KAC2C;AAC3C,QAAM,WAAW,MAAM,eAAe,KAAK;AAC3C,MAAI,OAAO;AACX,MAAI;AACJ,MAAI,aAAa,WAAW;AAC1B,QAAI,CAAC,IAAK,OAAM,IAAI,QAAQ,yBAAyB,aAAa;AAClE,WAAO,IAAI,SAAS;AACpB,oBAAgB,eAAe,IAAI,MAAM;AAAA,EAC3C;AACA,QAAM,MAAM,MAAM,QAAQ,OAAO,KAAK,MAAM,MAAM,aAAa,EAAE,MAAM,CAAC,MAAa;AACnF,UAAM,IAAI,QAAQ,EAAE,SAAS,aAAa;AAAA,EAC5C,CAAC;AACD,MAAI,IAAI,aAAa,GAAI,OAAM,IAAI,QAAQ,UAAU,aAAa;AAClE,MAAI,IAAI,aAAa,EAAG,QAAO,EAAE,MAAM,MAAM,QAAQ,IAAI,OAAO;AAEhE,QAAM,OAAO,IAAI,OAAO,MAAM,IAAI,EAAE,KAAK,CAAC,MAAc,EAAE,WAAW,UAAU,CAAC;AAChF,MAAI,MAAM;AACR,UAAM,OAAO,KAAK,MAAM,WAAW,MAAM;AACzC,UAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,UAAM,OAAO,QAAQ,KAAK,gBAAgB,KAAK,MAAM,GAAG,GAAG;AAC3D,UAAM,UAAU,QAAQ,KAAK,OAAO,KAAK,MAAM,MAAM,CAAC;AACtD,UAAM,IAAI,QAAQ,WAAW,UAAU,SAAS,gBAAgB,gBAAiB,IAAc;AAAA,EACjG;AACA,QAAM,SAAS,IAAI,OAAO,KAAK,KAAK;AACpC,QAAM,IAAI,QAAQ,QAAQ,aAAa;AACzC;AAGO,IAAM,aAAa;AAAA,EACxB;AACF,EAAE,KAAK,IAAI;AAGJ,IAAM,aAAa;AAAA,EACxB;AACF,EAAE,KAAK,IAAI;;;ADxHX,IAAM,QAAQ,oBAAI,IAA8B;AAChD,eAAe,SAAY,KAAa,IAAkC;AACxE,QAAM,OAAO,MAAM,IAAI,GAAG,KAAK,QAAQ,QAAQ;AAC/C,QAAM,MAAM,KAAK,KAAK,MAAM,GAAG,CAAC;AAChC,QAAM,IAAI,KAAK,IAAI,MAAM,MAAM;AAAA,EAAC,CAAC,CAAC;AAClC,SAAO;AACT;AAEA,IAAM,WAAW;AAGjB,SAAS,YAAY,SAAyB;AAC5C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB,IAAI,OAAO,IAAI;AAAA,IACjC;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAGO,SAAS,eAAe,UAAsD;AACnF,QAAM,MAAM,SAAS,QAAQ,GAAG;AAChC,MAAI,OAAO,EAAG,OAAM,IAAIC,SAAQ,kCAAkC,UAAU,aAAa;AACzF,QAAM,QAAQ,SAAS,MAAM,GAAG,GAAG;AACnC,QAAM,UAAU,SAAS,MAAM,MAAM,CAAC;AACtC,MAAI,CAAC,SAAS,KAAK,KAAK,EAAG,OAAM,IAAIA,SAAQ,aAAa,OAAO,aAAa;AAC9E,QAAM,SAAS,kBAAkB,KAAK,OAAO;AAC7C,MAAI,EAAE,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,IAAI,KAAK,SAAS;AACpE,UAAM,IAAIA,SAAQ,iCAAiC,SAAS,aAAa;AAAA,EAC3E;AACA,SAAO,EAAE,OAAO,QAAQ;AAC1B;AAKO,IAAM,mBAAN,MAAuB;AAAA;AAAA,EAEnB,eAAe,KAAK,OAAO;AAAA;AAAA,EAGnB,WAAW,oBAAI,IAAoB;AAAA,EAE5C,YAAY,MAAsB;AACxC,WAAO,WAAW,QAAQ,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAM,QAAQ,MAAiC;AAC7C,UAAM,EAAE,OAAO,QAAQ,IAAI,eAAe,IAAI;AAC9C,UAAM,cAAc,QAAQ,MAAM;AAClC,WAAO,EAAE,WAAW,YAAY,QAAQ,OAAO,OAAO,GAAG,YAAY;AAAA,EACvE;AAAA;AAAA,EAGA,YAAY,QAA0B;AAAE,WAAO,OAAO;AAAA,EAAY;AAAA;AAAA,EAGlE,MAAM,KAAK,QAA+C;AACxD,WAAO,SAAS,OAAO,WAAW,MAAM,KAAK,WAAW,MAAM,CAAC;AAAA,EACjE;AAAA,EAEA,MAAc,WAAW,QAA+C;AACtE,UAAM,EAAE,OAAO,QAAQ,IAAI,eAAe,OAAO,WAAW;AAC5D,UAAM,MAAM,EAAE,SAAS,IAAI,OAAO,EAAE;AAEpC,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,YAAY;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,MAAM,MAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,EAAE,QAAQ,UAAU,CAAC;AAC1E,UAAM,IAAI,IAAI,OAAO,KAAK;AAC1B,QAAI,MAAM,SAAU,QAAO;AAC3B,QAAI,MAAM,MAAO,QAAO,EAAE,SAAS,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,MAAM,YAAY;AACrF,QAAI,MAAM,QAAS,QAAO,EAAE,SAAS,UAAU,WAAW,KAAK,IAAI,CAAC,GAAG,MAAM,QAAQ;AACrF,UAAM,IAAI,qBAAqB,KAAK,CAAC;AACrC,UAAM,OAAO,KAAK,EAAE,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,IAAI;AAC/C,UAAM,QAAQ,KAAK,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,IAAI;AACxC,WAAO;AAAA,MACL,SAAS,UAAU,OAAO,SAAS,SAAS,SAAY,MAAM,OAAO,GAAG;AAAA,MACxE,MAAM;AAAA,MACN,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IACvC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,SAAS,QAAmC;AAChD,WAAO,SAAS,OAAO,WAAW,YAAY;AAC5C,YAAM,OAAO,MAAM,KAAK,eAAe,MAAM;AAC7C,WAAK,SAAS,IAAI,OAAO,WAAW,KAAK,YAAY,IAAI,CAAC;AAC1D,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,eAAe,QAAmC;AAC9D,UAAM,EAAE,OAAO,QAAQ,IAAI,eAAe,OAAO,WAAW;AAC5D,UAAM,MAAM,EAAE,SAAS,IAAI,OAAO,GAAG,QAAQ,OAAO,KAAK,YAAY,EAAE;AACvE,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,YAAY;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,uBAAuB,KAAK,eAAe;AAAA,MAC3C;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,MAAM,MAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,EAAE,QAAQ,UAAU,CAAC;AAC1E,UAAM,MAAM,OAAO,KAAK,IAAI,OAAO,QAAQ,SAAS,EAAE,GAAG,QAAQ;AACjE,QAAI;AACF,aAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,GAAG;AAAA,IAC7D,QAAQ;AACN,YAAM,IAAIA,SAAQ,oBAAoB,aAAa;AAAA,IACrD;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,UAAU,QAAkB,UAAuC;AACvE,UAAM,OAAO,MAAM,KAAK,SAAS,MAAM;AACvC,UAAM,MAAM,OAAO,KAAK,MAAM,MAAM;AACpC,QAAI,IAAI,aAAa,SAAU,OAAM,IAAIA,SAAQ,UAAU,cAAc;AACzE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,QAAkB,SAAiB,UAAmD;AACpG,WAAO,SAAS,OAAO,WAAW,MAAM,KAAK,gBAAgB,QAAQ,SAAS,QAAQ,CAAC;AAAA,EACzF;AAAA,EAEA,MAAc,gBAAgB,QAAkB,SAAiB,UAAmD;AAClH,UAAM,EAAE,OAAO,QAAQ,IAAI,eAAe,OAAO,WAAW;AAC5D,QAAI,aAAa,UAAa,SAAS,SAAS,kBAAkB;AAChE,YAAM,IAAIA,SAAQ,4CAA4C,iBAAiB;AAAA,IACjF;AACA,UAAM,MAAM,EAAE,SAAS,IAAI,OAAO,EAAE;AACpC,UAAM,OAAO,IAAI,OAAO;AACxB,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,IAAI;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,YAAY;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,EAAE,QAAQ,WAAW,OAAO,KAAK,CAAC;AAC3E,SAAK,SAAS,IAAI,OAAO,WAAW,KAAK,YAAY,OAAO,CAAC;AAC7D,WAAO,EAAE,WAAW,UAAU,SAAS,UAAU,OAAO,KAAK,IAAI,CAAC,GAAG,QAAQ,MAAM,OAAO,QAAQ;AAAA,EACpG;AAAA;AAAA,EAGA,MAAc,cAAc,QAAkB,SAAkC;AAC9E,UAAM,EAAE,OAAO,QAAQ,IAAI,eAAe,OAAO,WAAW;AAC5D,UAAM,MAAM,EAAE,SAAS,IAAI,OAAO,EAAE;AACpC,UAAM,OAAO,IAAI,OAAO;AACxB,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,IAAI;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,YAAY;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,EAAE,QAAQ,WAAW,OAAO,KAAK,CAAC;AAC3E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SACJ,QACA,MACA,WACwB;AACxB,WAAO,SAAS,OAAO,WAAW,YAAY;AAC5C,YAAM,EAAE,OAAO,QAAQ,IAAI,eAAe,OAAO,WAAW;AAE5D,YAAM,aAAuB,EAAE,WAAW,OAAO,WAAW,aAAa,QAAQ,MAAM,QAAQ;AAC/F,YAAM,SAAS,MAAM,KAAK,eAAe,UAAU;AACnD,YAAM,OAAO,KAAK,SAAS,IAAI,OAAO,SAAS;AAC/C,UAAI,SAAS,UAAa,SAAS,KAAK,YAAY,MAAM,GAAG;AAC3D,cAAM,IAAIA,SAAQ,wBAAwB,kBAAkB;AAAA,MAC9D;AACA,YAAM,UAAU,KAAK,aAAa,QAAQ,KAAK,SAAS;AACxD,UAAI,YAAY,EAAG,OAAM,IAAIA,SAAQ,sBAAsB,mBAAmB;AAC9E,UAAI,CAAC,KAAK,cAAc,UAAU,GAAG;AACnC,cAAM,IAAIA,SAAQ,mBAAmB,UAAU,yBAAyB,mBAAmB;AAAA,MAC7F;AACA,YAAM,QAAQ,KAAK,aACf,OAAO,MAAM,KAAK,SAAS,EAAE,KAAK,KAAK,SAAS,IAChD,OAAO,QAAQ,KAAK,WAAW,KAAK,SAAS;AACjD,YAAM,KAAK,cAAc,YAAY,KAAK;AAC1C,WAAK,SAAS,IAAI,OAAO,WAAW,KAAK,YAAY,KAAK,CAAC;AAC3D,aAAO,EAAE,SAAS,UAAU,OAAO,KAAK,IAAI,CAAC,GAAG,QAAQ,MAAM;AAAA,IAChE,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,MAAc,WAA2B;AAC5D,QAAI,cAAc,GAAI,QAAO;AAC7B,QAAI,QAAQ;AACZ,QAAI,MAAM,KAAK,QAAQ,SAAS;AAChC,WAAO,QAAQ,IAAI;AAAE;AAAS,YAAM,KAAK,QAAQ,WAAW,MAAM,UAAU,MAAM;AAAA,IAAE;AACpF,WAAO;AAAA,EACT;AACF;;;AEhUA,SAAS,kBAAkB;AAE3B,SAAS,WAAAC,gBAAe;;;ACFxB,SAAS,WAAAC,gBAAe;;;ACLxB,IAAqB,OAArB,MAA0B;AAAA,EACtB,KAAK,QAAQ,QAEb,UAAU,CAAC,GAAG;AACV,QAAI;AACJ,QAAI,OAAO,YAAY,YAAY;AAC/B,iBAAW;AACX,gBAAU,CAAC;AAAA,IACf,WACS,cAAc,SAAS;AAC5B,iBAAW,QAAQ;AAAA,IACvB;AAEA,UAAM,YAAY,KAAK,UAAU,QAAQ,OAAO;AAChD,UAAM,YAAY,KAAK,UAAU,QAAQ,OAAO;AAChD,UAAM,YAAY,KAAK,YAAY,KAAK,SAAS,WAAW,OAAO,CAAC;AACpE,UAAM,YAAY,KAAK,YAAY,KAAK,SAAS,WAAW,OAAO,CAAC;AACpE,WAAO,KAAK,mBAAmB,WAAW,WAAW,SAAS,QAAQ;AAAA,EAC1E;AAAA,EACA,mBAAmB,WAAW,WAAW,SAAS,UAAU;AACxD,QAAI;AACJ,UAAM,OAAO,CAAC,UAAU;AACpB,cAAQ,KAAK,YAAY,OAAO,OAAO;AACvC,UAAI,UAAU;AACV,mBAAW,WAAY;AAAE,mBAAS,KAAK;AAAA,QAAG,GAAG,CAAC;AAC9C,eAAO;AAAA,MACX,OACK;AACD,eAAO;AAAA,MACX;AAAA,IACJ;AACA,UAAM,SAAS,UAAU,QAAQ,SAAS,UAAU;AACpD,QAAI,aAAa;AACjB,QAAI,gBAAgB,SAAS;AAC7B,QAAI,QAAQ,iBAAiB,MAAM;AAC/B,sBAAgB,KAAK,IAAI,eAAe,QAAQ,aAAa;AAAA,IACjE;AACA,UAAM,oBAAoB,KAAK,QAAQ,aAAa,QAAQ,OAAO,SAAS,KAAK;AACjF,UAAM,sBAAsB,KAAK,IAAI,IAAI;AACzC,UAAM,WAAW,CAAC,EAAE,QAAQ,IAAI,eAAe,OAAU,CAAC;AAE1D,QAAI,SAAS,KAAK,cAAc,SAAS,CAAC,GAAG,WAAW,WAAW,GAAG,OAAO;AAC7E,QAAI,SAAS,CAAC,EAAE,SAAS,KAAK,UAAU,SAAS,KAAK,QAAQ;AAE1D,aAAO,KAAK,KAAK,YAAY,SAAS,CAAC,EAAE,eAAe,WAAW,SAAS,CAAC;AAAA,IACjF;AAkBA,QAAI,wBAAwB,WAAW,wBAAwB;AAE/D,UAAM,iBAAiB,MAAM;AACzB,eAAS,eAAe,KAAK,IAAI,uBAAuB,CAAC,UAAU,GAAG,gBAAgB,KAAK,IAAI,uBAAuB,UAAU,GAAG,gBAAgB,GAAG;AAClJ,YAAI;AACJ,cAAM,aAAa,SAAS,eAAe,CAAC,GAAG,UAAU,SAAS,eAAe,CAAC;AAClF,YAAI,YAAY;AAGZ,mBAAS,eAAe,CAAC,IAAI;AAAA,QACjC;AACA,YAAI,SAAS;AACb,YAAI,SAAS;AAET,gBAAM,gBAAgB,QAAQ,SAAS;AACvC,mBAAS,WAAW,KAAK,iBAAiB,gBAAgB;AAAA,QAC9D;AACA,cAAM,YAAY,cAAc,WAAW,SAAS,IAAI;AACxD,YAAI,CAAC,UAAU,CAAC,WAAW;AAGvB,mBAAS,YAAY,IAAI;AACzB;AAAA,QACJ;AAIA,YAAI,CAAC,aAAc,UAAU,WAAW,SAAS,QAAQ,QAAS;AAC9D,qBAAW,KAAK,UAAU,SAAS,MAAM,OAAO,GAAG,OAAO;AAAA,QAC9D,OACK;AACD,qBAAW,KAAK,UAAU,YAAY,OAAO,MAAM,GAAG,OAAO;AAAA,QACjE;AACA,iBAAS,KAAK,cAAc,UAAU,WAAW,WAAW,cAAc,OAAO;AACjF,YAAI,SAAS,SAAS,KAAK,UAAU,SAAS,KAAK,QAAQ;AAEvD,iBAAO,KAAK,KAAK,YAAY,SAAS,eAAe,WAAW,SAAS,CAAC,KAAK;AAAA,QACnF,OACK;AACD,mBAAS,YAAY,IAAI;AACzB,cAAI,SAAS,SAAS,KAAK,QAAQ;AAC/B,oCAAwB,KAAK,IAAI,uBAAuB,eAAe,CAAC;AAAA,UAC5E;AACA,cAAI,SAAS,KAAK,QAAQ;AACtB,oCAAwB,KAAK,IAAI,uBAAuB,eAAe,CAAC;AAAA,UAC5E;AAAA,QACJ;AAAA,MACJ;AACA;AAAA,IACJ;AAKA,QAAI,UAAU;AACV,OAAC,SAAS,OAAO;AACb,mBAAW,WAAY;AACnB,cAAI,aAAa,iBAAiB,KAAK,IAAI,IAAI,qBAAqB;AAChE,mBAAO,SAAS,MAAS;AAAA,UAC7B;AACA,cAAI,CAAC,eAAe,GAAG;AACnB,iBAAK;AAAA,UACT;AAAA,QACJ,GAAG,CAAC;AAAA,MACR,GAAE;AAAA,IACN,OACK;AACD,aAAO,cAAc,iBAAiB,KAAK,IAAI,KAAK,qBAAqB;AACrE,cAAM,MAAM,eAAe;AAC3B,YAAI,KAAK;AACL,iBAAO;AAAA,QACX;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA,EACA,UAAU,MAAM,OAAO,SAAS,WAAW,SAAS;AAChD,UAAM,OAAO,KAAK;AAClB,QAAI,QAAQ,CAAC,QAAQ,qBAAqB,KAAK,UAAU,SAAS,KAAK,YAAY,SAAS;AACxF,aAAO;AAAA,QACH,QAAQ,KAAK,SAAS;AAAA,QACtB,eAAe,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAc,SAAkB,mBAAmB,KAAK,kBAAkB;AAAA,MACtH;AAAA,IACJ,OACK;AACD,aAAO;AAAA,QACH,QAAQ,KAAK,SAAS;AAAA,QACtB,eAAe,EAAE,OAAO,GAAG,OAAc,SAAkB,mBAAmB,KAAK;AAAA,MACvF;AAAA,IACJ;AAAA,EACJ;AAAA,EACA,cAAc,UAAU,WAAW,WAAW,cAAc,SAAS;AACjE,UAAM,SAAS,UAAU,QAAQ,SAAS,UAAU;AACpD,QAAI,SAAS,SAAS,QAAQ,SAAS,SAAS,cAAc,cAAc;AAC5E,WAAO,SAAS,IAAI,UAAU,SAAS,IAAI,UAAU,KAAK,OAAO,UAAU,SAAS,CAAC,GAAG,UAAU,SAAS,CAAC,GAAG,OAAO,GAAG;AACrH;AACA;AACA;AACA,UAAI,QAAQ,mBAAmB;AAC3B,iBAAS,gBAAgB,EAAE,OAAO,GAAG,mBAAmB,SAAS,eAAe,OAAO,OAAO,SAAS,MAAM;AAAA,MACjH;AAAA,IACJ;AACA,QAAI,eAAe,CAAC,QAAQ,mBAAmB;AAC3C,eAAS,gBAAgB,EAAE,OAAO,aAAa,mBAAmB,SAAS,eAAe,OAAO,OAAO,SAAS,MAAM;AAAA,IAC3H;AACA,aAAS,SAAS;AAClB,WAAO;AAAA,EACX;AAAA,EACA,OAAO,MAAM,OAAO,SAAS;AACzB,QAAI,QAAQ,YAAY;AACpB,aAAO,QAAQ,WAAW,MAAM,KAAK;AAAA,IACzC,OACK;AACD,aAAO,SAAS,SACR,CAAC,CAAC,QAAQ,cAAc,KAAK,YAAY,MAAM,MAAM,YAAY;AAAA,IAC7E;AAAA,EACJ;AAAA,EACA,YAAY,OAAO;AACf,UAAM,MAAM,CAAC;AACb,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACnC,UAAI,MAAM,CAAC,GAAG;AACV,YAAI,KAAK,MAAM,CAAC,CAAC;AAAA,MACrB;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA;AAAA,EAEA,UAAU,OAAO,SAAS;AACtB,WAAO;AAAA,EACX;AAAA;AAAA,EAEA,SAAS,OAAO,SAAS;AACrB,WAAO,MAAM,KAAK,KAAK;AAAA,EAC3B;AAAA,EACA,KAAK,OAAO;AAKR,WAAO,MAAM,KAAK,EAAE;AAAA,EACxB;AAAA,EACA,YAAY,eAEZ,SAAS;AACL,WAAO;AAAA,EACX;AAAA,EACA,IAAI,kBAAkB;AAClB,WAAO;AAAA,EACX;AAAA,EACA,YAAY,eAAe,WAAW,WAAW;AAG7C,UAAM,aAAa,CAAC;AACpB,QAAI;AACJ,WAAO,eAAe;AAClB,iBAAW,KAAK,aAAa;AAC7B,sBAAgB,cAAc;AAC9B,aAAO,cAAc;AACrB,sBAAgB;AAAA,IACpB;AACA,eAAW,QAAQ;AACnB,UAAM,eAAe,WAAW;AAChC,QAAI,eAAe,GAAG,SAAS,GAAG,SAAS;AAC3C,WAAO,eAAe,cAAc,gBAAgB;AAChD,YAAM,YAAY,WAAW,YAAY;AACzC,UAAI,CAAC,UAAU,SAAS;AACpB,YAAI,CAAC,UAAU,SAAS,KAAK,iBAAiB;AAC1C,cAAI,QAAQ,UAAU,MAAM,QAAQ,SAAS,UAAU,KAAK;AAC5D,kBAAQ,MAAM,IAAI,SAAUC,QAAO,GAAG;AAClC,kBAAM,WAAW,UAAU,SAAS,CAAC;AACrC,mBAAO,SAAS,SAASA,OAAM,SAAS,WAAWA;AAAA,UACvD,CAAC;AACD,oBAAU,QAAQ,KAAK,KAAK,KAAK;AAAA,QACrC,OACK;AACD,oBAAU,QAAQ,KAAK,KAAK,UAAU,MAAM,QAAQ,SAAS,UAAU,KAAK,CAAC;AAAA,QACjF;AACA,kBAAU,UAAU;AAEpB,YAAI,CAAC,UAAU,OAAO;AAClB,oBAAU,UAAU;AAAA,QACxB;AAAA,MACJ,OACK;AACD,kBAAU,QAAQ,KAAK,KAAK,UAAU,MAAM,QAAQ,SAAS,UAAU,KAAK,CAAC;AAC7E,kBAAU,UAAU;AAAA,MACxB;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AACJ;;;AC1PA,IAAM,WAAN,cAAuB,KAAK;AAAA,EACxB,cAAc;AACV,UAAM,GAAG,SAAS;AAClB,SAAK,WAAW;AAAA,EACpB;AAAA,EACA,OAAO,MAAM,OAAO,SAAS;AAQzB,QAAI,QAAQ,kBAAkB;AAC1B,UAAI,CAAC,QAAQ,kBAAkB,CAAC,KAAK,SAAS,IAAI,GAAG;AACjD,eAAO,KAAK,KAAK;AAAA,MACrB;AACA,UAAI,CAAC,QAAQ,kBAAkB,CAAC,MAAM,SAAS,IAAI,GAAG;AAClD,gBAAQ,MAAM,KAAK;AAAA,MACvB;AAAA,IACJ,WACS,QAAQ,sBAAsB,CAAC,QAAQ,gBAAgB;AAC5D,UAAI,KAAK,SAAS,IAAI,GAAG;AACrB,eAAO,KAAK,MAAM,GAAG,EAAE;AAAA,MAC3B;AACA,UAAI,MAAM,SAAS,IAAI,GAAG;AACtB,gBAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,MAC7B;AAAA,IACJ;AACA,WAAO,MAAM,OAAO,MAAM,OAAO,OAAO;AAAA,EAC5C;AACJ;AACO,IAAM,WAAW,IAAI,SAAS;AAC9B,SAAS,UAAU,QAAQ,QAAQ,SAAS;AAC/C,SAAO,SAAS,KAAK,QAAQ,QAAQ,OAAO;AAChD;AAMO,SAAS,SAAS,OAAO,SAAS;AACrC,MAAI,QAAQ,iBAAiB;AAEzB,YAAQ,MAAM,QAAQ,SAAS,IAAI;AAAA,EACvC;AACA,QAAM,WAAW,CAAC,GAAG,mBAAmB,MAAM,MAAM,WAAW;AAE/D,MAAI,CAAC,iBAAiB,iBAAiB,SAAS,CAAC,GAAG;AAChD,qBAAiB,IAAI;AAAA,EACzB;AAEA,WAAS,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;AAC9C,UAAM,OAAO,iBAAiB,CAAC;AAC/B,QAAI,IAAI,KAAK,CAAC,QAAQ,gBAAgB;AAClC,eAAS,SAAS,SAAS,CAAC,KAAK;AAAA,IACrC,OACK;AACD,eAAS,KAAK,IAAI;AAAA,IACtB;AAAA,EACJ;AACA,SAAO;AACX;;;AC4BO,SAAS,gBAAgB,aAAa,aAAa,QAAQ,QAAQ,WAAW,WAAW,SAAS;AACrG,MAAI;AACJ,MAAI,CAAC,SAAS;AACV,iBAAa,CAAC;AAAA,EAClB,WACS,OAAO,YAAY,YAAY;AACpC,iBAAa,EAAE,UAAU,QAAQ;AAAA,EACrC,OACK;AACD,iBAAa;AAAA,EACjB;AACA,MAAI,OAAO,WAAW,YAAY,aAAa;AAC3C,eAAW,UAAU;AAAA,EACzB;AAGA,QAAM,UAAU,WAAW;AAE3B,MAAI,WAAW,gBAAgB;AAC3B,UAAM,IAAI,MAAM,6FAA6F;AAAA,EACjH;AACA,MAAI,CAAC,WAAW,UAAU;AACtB,WAAO,uBAAuB,UAAU,QAAQ,QAAQ,UAAU,CAAC;AAAA,EACvE,OACK;AACD,UAAM,EAAE,SAAS,IAAI;AACrB,cAAU,QAAQ,QAAQ,OAAO,OAAO,OAAO,OAAO,CAAC,GAAG,UAAU,GAAG,EAAE,UAAU,CAAC,SAAS;AACrF,YAAM,QAAQ,uBAAuB,IAAI;AAGzC,eAAS,KAAK;AAAA,IAClB,EAAE,CAAC,CAAC;AAAA,EACZ;AACA,WAAS,uBAAuB,MAAM;AAGlC,QAAI,CAAC,MAAM;AACP;AAAA,IACJ;AACA,SAAK,KAAK,EAAE,OAAO,IAAI,OAAO,CAAC,EAAE,CAAC;AAClC,aAAS,aAAa,OAAO;AACzB,aAAO,MAAM,IAAI,SAAU,OAAO;AAAE,eAAO,MAAM;AAAA,MAAO,CAAC;AAAA,IAC7D;AACA,UAAM,QAAQ,CAAC;AACf,QAAI,gBAAgB,GAAG,gBAAgB,GAAG,WAAW,CAAC,GAAG,UAAU,GAAG,UAAU;AAChF,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAClC,YAAM,UAAU,KAAK,CAAC,GAAG,QAAQ,QAAQ,SAAS,WAAW,QAAQ,KAAK;AAC1E,cAAQ,QAAQ;AAChB,UAAI,QAAQ,SAAS,QAAQ,SAAS;AAElC,YAAI,CAAC,eAAe;AAChB,gBAAM,OAAO,KAAK,IAAI,CAAC;AACvB,0BAAgB;AAChB,0BAAgB;AAChB,cAAI,MAAM;AACN,uBAAW,UAAU,IAAI,aAAa,KAAK,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;AACrE,6BAAiB,SAAS;AAC1B,6BAAiB,SAAS;AAAA,UAC9B;AAAA,QACJ;AAEA,mBAAW,QAAQ,OAAO;AACtB,mBAAS,MAAM,QAAQ,QAAQ,MAAM,OAAO,IAAI;AAAA,QACpD;AAEA,YAAI,QAAQ,OAAO;AACf,qBAAW,MAAM;AAAA,QACrB,OACK;AACD,qBAAW,MAAM;AAAA,QACrB;AAAA,MACJ,OACK;AAED,YAAI,eAAe;AAEf,cAAI,MAAM,UAAU,UAAU,KAAK,IAAI,KAAK,SAAS,GAAG;AAEpD,uBAAW,QAAQ,aAAa,KAAK,GAAG;AACpC,uBAAS,KAAK,IAAI;AAAA,YACtB;AAAA,UACJ,OACK;AAED,kBAAM,cAAc,KAAK,IAAI,MAAM,QAAQ,OAAO;AAClD,uBAAW,QAAQ,aAAa,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG;AAC1D,uBAAS,KAAK,IAAI;AAAA,YACtB;AACA,kBAAM,OAAO;AAAA,cACT,UAAU;AAAA,cACV,UAAW,UAAU,gBAAgB;AAAA,cACrC,UAAU;AAAA,cACV,UAAW,UAAU,gBAAgB;AAAA,cACrC,OAAO;AAAA,YACX;AACA,kBAAM,KAAK,IAAI;AACf,4BAAgB;AAChB,4BAAgB;AAChB,uBAAW,CAAC;AAAA,UAChB;AAAA,QACJ;AACA,mBAAW,MAAM;AACjB,mBAAW,MAAM;AAAA,MACrB;AAAA,IACJ;AAGA,eAAW,QAAQ,OAAO;AACtB,eAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AACxC,YAAI,KAAK,MAAM,CAAC,EAAE,SAAS,IAAI,GAAG;AAC9B,eAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE;AAAA,QAC7C,OACK;AACD,eAAK,MAAM,OAAO,IAAI,GAAG,GAAG,8BAA8B;AAC1D;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AACA,WAAO;AAAA,MACH;AAAA,MAA0B;AAAA,MAC1B;AAAA,MAAsB;AAAA,MACtB;AAAA,IACJ;AAAA,EACJ;AACJ;AAkIA,SAAS,WAAW,MAAM;AACtB,QAAM,gBAAgB,KAAK,SAAS,IAAI;AACxC,QAAM,SAAS,KAAK,MAAM,IAAI,EAAE,IAAI,UAAQ,OAAO,IAAI;AACvD,MAAI,eAAe;AACf,WAAO,IAAI;AAAA,EACf,OACK;AACD,WAAO,KAAK,OAAO,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,EACzC;AACA,SAAO;AACX;;;AH3UA,SAAS,aAAa,MAAc,eAA+B;AACjE,SAAO,KAAK,SAAS,gBACjB,KAAK,UAAU,GAAG,aAAa,IAAI,4BAA4B,gBAAgB,YAC/E;AACN;AACA,SAAS,oBAAoB,MAAsB;AACjD,SAAO,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACnD;AAKO,SAAS,YAAY,MAAc,SAA4B,aAAmC;AACvG,QAAM,WAA2B,CAAC;AAClC,MAAI,aAAa;AACjB,MAAI,mBAAmB;AACvB,MAAI,cAAc;AAKlB,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAM,qBAAqB,KAAK,SAAS,IAAI;AAC7C,QAAM,aAAa,qBAAqB,MAAM,MAAM,GAAG,EAAE,IAAI;AAE7D,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,kBAAkB,MAAM;AAAA,EAC7D;AACA,eAAa,WAAW;AACxB,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,GAAG;AAC7C,UAAM,MAAM,WAAW,CAAC;AACxB,QAAI,iBAAkB;AACtB,UAAM,aAAa,IAAI;AACvB,QAAI,aAAa,QAAQ,UAAU,SAAS,UAAU,QAAQ,MAAO;AACrE,UAAM,QAAQ,aAAa,oBAAoB,GAAG,GAAG,QAAQ,aAAa;AAC1E,UAAM,QAAQ,OAAO,WAAW,OAAO,MAAM,KAAK,SAAS,SAAS,IAAI,IAAI;AAC5E,QAAI,cAAc,QAAQ,QAAQ,UAAU;AAAE,yBAAmB;AAAM;AAAA,IAAM;AAC7E,mBAAe;AACf,aAAS,KAAK,EAAE,QAAQ,YAAY,MAAM,MAAM,CAAC;AAAA,EACnD;AACA,MAAI,CAAC,oBAAoB,QAAQ,SAAS,cAAc,EAAE,eAAe,KAAK,QAAQ,WAAW,IAAI;AACnG,UAAM,IAAIC,SAAQ,YAAY,QAAQ,SAAS,2BAA2B,cAAc,QAAQ,aAAa,WAAW,cAAc;AAAA,EACxI;AACA,SAAO,EAAE,OAAO,UAAU,YAAY,iBAAiB;AACzD;AASO,SAAS,iBAAiB,aAAqB,SAAkC;AACtF,QAAM,UAAU,QAAQ,MAAM,GAAG,EAAE,GAAG,UAAU,KAAK,IAAI,GAAG,QAAQ,SAAS,CAAC;AAC9E,MAAI;AACJ,MAAI,QAAQ,kBAAkB;AAC5B,aAAS,mCAAmC,QAAQ,SAAS,MAAM,UAAU,mBAAmB,UAAU,KAAK;AAAA,EACjH,WAAW,UAAU,QAAQ,YAAY;AACvC,aAAS,oBAAoB,QAAQ,SAAS,MAAM,UAAU,SAAS,QAAQ,aAAa,mBAAmB,UAAU,KAAK;AAAA,EAChI,OAAO;AACL,aAAS,0BAA0B,QAAQ,aAAa;AAAA,EAC1D;AACA,QAAM,OAAO,QAAQ,MAAM,SAAS,IAChC,QAAQ,MAAM,IAAI,CAAC,SAAS,KAAK,SAAS,OAAO,KAAK,IAAI,EAAE,KAAK,IAAI,IAAI,SAAS,SAClF;AACJ,SAAO,WAAW,cAAc,4CAA4C,OAAO;AACrF;AAGA,IAAM,oBAA4C;AAAA,EAChD,IAAG;AAAA,EAAM,KAAI;AAAA,EAAO,KAAI;AAAA,EAAM,KAAI;AAAA,EAAM,IAAG;AAAA,EAAM,KAAI;AAAA,EAAO,KAAI;AAAA,EAAM,KAAI;AAAA,EAC1E,MAAK;AAAA,EAAQ,OAAM;AAAA,EAAQ,IAAG;AAAA,EAAM,IAAG;AAAA,EAAM,IAAG;AAAA,EAAM,IAAG;AAAA,EAAM,MAAK;AAAA,EACpE,GAAE;AAAA,EAAK,GAAE;AAAA,EAAK,IAAG;AAAA,EAAO,KAAI;AAAA,EAAO,KAAI;AAAA,EAAO,KAAI;AAAA,EAAO,IAAG;AAAA,EAAM,IAAG;AAAA,EACrE,OAAM;AAAA,EAAS,KAAI;AAAA,EAAO,IAAG;AAAA,EAAM,MAAK;AAAA,EAAM,KAAI;AAAA,EAAM,MAAK;AAAA,EAAQ,KAAI;AAAA,EACzE,MAAK;AAAA,EAAQ,KAAI;AAAA,EAAO,IAAG;AAAA,EAAM,UAAS;AAAA,EAAM,KAAI;AAAA,EAAO,MAAK;AAAA,EAAQ,KAAI;AAAA,EAC5E,KAAI;AAAA,EAAO,MAAK;AAAA,EAAQ,MAAK;AAAA,EAAQ,KAAI;AAAA,EAAO,KAAI;AAAA,EAAO,KAAI;AACjE;AAEO,SAAS,aAAa,MAAkC;AAC7D,QAAM,OAAO,KAAK,MAAM,KAAK,IAAI,KAAK,YAAY,GAAG,GAAG,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC;AACnF,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,MAAM,KAAK,MAAM,MAAM,CAAC,EAAE,YAAY;AAC5C,SAAO,OAAO,OAAO,mBAAmB,GAAG,IAAI,kBAAkB,GAAG,IAAI;AAC1E;AAWO,SAAS,iBAAiB,MAAuC;AACtE,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAG,QAAO;AAC7E,QAAM,IAAI;AACV,QAAM,EAAE,MAAM,QAAQ,OAAO,YAAY,KAAK,IAAI;AAClD,MAAI,OAAO,SAAS,YAAY,OAAO,eAAe,YAAY,OAAO,WAAW,SAAU,QAAO;AACrG,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,EAAG,QAAO;AACpD,MAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,EAAG,QAAO;AAC5D,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,YACvE,CAAC,MAAM,QAAQ,CAAC,KAAK,OAAQ,EAA2B,WAAW,YACnE,OAAO,UAAW,EAAyB,MAAM,KAAM,EAAyB,UAAU,KAC1F,OAAQ,EAAyB,SAAS,QAAQ,EAAG,QAAO;AACjE,MAAI,SAAS,UAAa,OAAO,SAAS,SAAU,QAAO;AAC3D,QAAM,QAAQ;AACd,MAAI,OAAO,SAAS;AACpB,aAAW,EAAE,OAAO,KAAK,OAAO;AAAE,QAAI,UAAU,QAAQ,SAAS,WAAY,QAAO;AAAW,WAAO;AAAA,EAAO;AAC7G,SAAO,EAAE,MAAM,QAAQ,OAAO,OAAO,YAAY,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,KAAK,EAAG;AAC3F;AAMO,SAAS,iBAAiB,MAAc,QAAgB,OAA2B;AACxF,QAAM,QAAQ,gBAAgB,IAAI,IAAI,QAAQ,OAAO,QAAW,QAAW,EAAE,SAAS,EAAE,CAAC;AACzF,QAAM,QAAoB,CAAC;AAC3B,aAAW,QAAQ,MAAM,OAAO;AAC9B,UAAM,WAAqB,CAAC;AAC5B,UAAM,WAAqB,CAAC;AAC5B,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,KAAK,WAAW,IAAI,EAAG;AAC3B,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,KAAK,WAAW,GAAG,EAAG,UAAS,KAAK,IAAI;AAAA,eACnC,KAAK,WAAW,GAAG,EAAG,UAAS,KAAK,IAAI;AAAA,WAC5C;AAAE,iBAAS,KAAK,IAAI;AAAG,iBAAS,KAAK,IAAI;AAAA,MAAE;AAAA,IAClD;AACA,UAAM,KAAK,EAAE,MAAM,SAAS,SAAS,SAAS,IAAI,SAAS,KAAK,IAAI,IAAI,MAAM,SAAS,SAAS,KAAK,IAAI,EAAE,CAAC;AAAA,EAC9G;AACA,SAAO;AACT;AAGO,SAAS,cAAc,MAAuC;AACnE,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAG,QAAO;AAC7E,QAAM,QAAS,KAAiC;AAChD,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO;AACxD,QAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,KACnF,OAAQ,EAAyB,SAAS,aACxC,EAA4B,YAAY,QAAQ,OAAQ,EAA4B,YAAY,aAClG,OAAQ,EAA4B,YAAY,QAAQ;AAC7D,SAAO,QAAQ,QAAsB;AACvC;AAGO,SAAS,kBAAkB,aAAqB,WAAwC;AAC7F,QAAM,OAAO,cAAc,WAAW,YAAY;AAClD,SAAO,WAAW,cAAc,4CAA4C,OAAO;AACrF;AAGO,SAAS,iBAAiB,aAAqB,YAA6B;AACjF,SAAO,aACH,cAAc,cAAc,mEAC5B,cAAc,cAAc;AAClC;;;ADvKO,IAAM,aAAa;AACnB,IAAM,uBAAuB;AAC7B,IAAM,iBAAiB,KAAK;AAEnC,SAAS,qBAAqB,OAAeC,OAAsB;AACjE,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AACpE,UAAM,IAAI,MAAMA,QAAO,6BAA6B;AAAA,EACtD;AACA,SAAO;AACT;AAKO,SAAS,cAAc,MAAyE;AACrG,MAAI,KAAK,UAAU,KAAK,EAAE,WAAW,EAAG,OAAM,IAAI,MAAM,sCAAsC;AAC9F,QAAM,SAAS,KAAK,WAAW,SAAY,IAAI,qBAAqB,KAAK,QAAQ,QAAQ;AACzF,QAAM,QAAQ,KAAK,UAAU,SAAY,aAAa,qBAAqB,KAAK,OAAO,OAAO;AAC9F,MAAI,QAAQ,WAAY,OAAM,IAAI,MAAM,yCAAyC,UAAU;AAC3F,SAAO,EAAE,UAAU,KAAK,WAAW,QAAQ,MAAM;AACnD;AAGO,SAAS,qBAAqB,IAAqD;AACxF,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,WAAW,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,uHAAuH;AAAA,MACjL,QAAQ,EAAE,MAAM,UAAU,aAAa,+CAA+C;AAAA,MACtF,OAAO,EAAE,MAAM,UAAU,aAAa,oDAAoD,aAAa,IAAI;AAAA,IAC7G;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,sBAAsB;AAAA,QACtB,YAAY;AAAA,UACV,MAAM,EAAE,MAAM,UAAU,UAAU,KAAK;AAAA,UACvC,QAAQ,EAAE,MAAM,WAAW,UAAU,KAAK;AAAA,UAC1C,OAAO;AAAA,YACL,MAAM;AAAA,YACN,UAAU;AAAA,YACV,OAAO,EAAE,MAAM,UAAU,sBAAsB,OAAO,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,UAAU,KAAK,GAAG,MAAM,EAAE,MAAM,UAAU,UAAU,KAAK,EAAE,EAAE;AAAA,UAC9J;AAAA,UACA,YAAY,EAAE,MAAM,WAAW,UAAU,KAAK;AAAA,QAChD;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,MAAM,UAAU;AACvB,cAAM,QAAQ,cAAc,IAA8D;AAC1F,cAAM,UAAU,MAAM,MAAM,GAAG,EAAE,GAAG,UAAU,KAAK,IAAI,GAAG,MAAM,SAAS,CAAC;AAC1E,cAAM,mBAAmB,MAAM,MAAM,SAAS,MAAM,SAAS,UAAU,MAAM;AAC7E,eAAO,CAAC,EAAE,MAAM,QAAQ,MAAM,iBAAiB,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ,OAAO,MAAM,OAAO,YAAY,MAAM,YAAY,GAAI,mBAAmB,EAAE,kBAAkB,KAAK,IAAI,CAAC,EAAG,CAAC,EAAE,CAAC;AAAA,MACnM;AAAA,MACA,kBAAkB,CAAC,OAAO,UAAU;AAClC,cAAM,OAAO,aAAa,MAAM,IAAI;AACpC,eAAO,EAAE,MAAM,MAAM,MAAM,QAAQ,MAAM,QAAQ,OAAO,MAAM,MAAM,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO,EAAE,QAAQ,KAAK,EAAE,GAAG,YAAY,MAAM,YAAY,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,KAAK,EAAG;AAAA,MAC3L;AAAA,IACF;AAAA,IACA,mBAAmB,MAAM;AAAA,IACzB,MAAM,QAAQ,MAAM,MAAM;AACxB,YAAM,QAAQ,cAAc,IAA8D;AAC1F,UAAI,KAAK,OAAO,QAAS,OAAM,IAAIC,SAAQ,aAAa,YAAY;AACpE,YAAM,SAAS,MAAM,GAAG,QAAQ,MAAM,QAAQ;AAC9C,YAAM,OAAO,MAAM,GAAG,SAAS,MAAM;AACrC,YAAM,SAAS,YAAY,MAAM,EAAE,QAAQ,MAAM,QAAQ,OAAO,MAAM,OAAO,eAAe,sBAAsB,UAAU,eAAe,GAAG,OAAO,WAAW;AAChK,aAAO,EAAE,MAAM,OAAO,aAAa,QAAQ,MAAM,QAAQ,OAAO,OAAO,OAAO,YAAY,OAAO,WAAW;AAAA,IAC9G;AAAA,IACA,cAAc,OAAO,QAAgD;AACnE,UAAI,OAAO,QAAS,QAAO;AAC3B,YAAM,OAAO,iBAAiB,OAAO,IAAI;AACzC,UAAI,SAAS,OAAW,QAAO;AAC/B,YAAM,OAAO,OAAO,QAAQ,WAAW,IAAI,OAAO,QAAQ,CAAC,IAAI;AAC/D,YAAM,OAAO,MAAM,SAAS,SAAS,KAAK,OAAO;AACjD,UAAI,SAAS,OAAW,QAAO;AAC/B,YAAM,OAAO,iFAAiF,KAAK,IAAI,IAAI,CAAC;AAC5G,UAAI,SAAS,OAAW,QAAO;AAC/B,aAAO,EAAE,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,QAAQ,OAAO,KAAK,OAAO,YAAY,KAAK,YAAY,GAAI,KAAK,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,GAAI,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,CAAC,EAAE;AAAA,IAChN;AAAA,IACA,YAAY,MAAuB;AACjC,YAAM,IAAI;AACV,YAAM,aAAa,EAAE,UAAU,UAAa,EAAE,QAAQ,IAClD,QAAQ,EAAE,UAAU,KAAK,UAAU,EAAE,UAAU,KAAK,EAAE,QAAQ,KAAK,MACnE,EAAE,WAAW,SAAY,iBAAiB,EAAE,SAAS,MAAM;AAC/D,aAAO,EAAE,MAAM,WAAW,OAAO,UAAU,EAAE,YAAY,YAAY,MAAM,QAAQ,WAAW,CAAC,EAAE,MAAM,EAAE,WAAW,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE;AAAA,IAC7I;AAAA,EACF,CAAC;AACH;;;AKjGA,SAAS,cAAAC,mBAAkB;AAE3B,SAAS,WAAAC,gBAAe;AAOjB,SAAS,eAAe,MAA0D;AACvF,MAAI,KAAK,UAAU,KAAK,EAAE,WAAW,EAAG,OAAM,IAAI,MAAM,sCAAsC;AAC9F,SAAO,EAAE,UAAU,KAAK,WAAW,SAAS,KAAK,QAAQ;AAC3D;AAEO,SAAS,sBAAsB,IAAqD;AACzF,SAAOC,YAAW;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,WAAW,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,6GAA6G;AAAA,MACvK,SAAS,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,oCAAoC;AAAA,IAC9F;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,sBAAsB;AAAA,QACtB,YAAY;AAAA,UACV,MAAM,EAAE,MAAM,UAAU,UAAU,KAAK;AAAA,UACvC,WAAW,EAAE,MAAM,UAAU,UAAU,MAAM,MAAM,CAAC,UAAU,QAAQ,EAAE;AAAA,UACxE,QAAQ,EAAE,UAAU,MAAM,OAAO,CAAC,EAAE,MAAM,SAAS,GAAG,EAAE,MAAM,OAAO,CAAC,EAAE;AAAA,UACxE,OAAO,EAAE,MAAM,UAAU,UAAU,KAAK;AAAA,QAC1C;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,OAAO,UAAU,CAAC,EAAE,MAAM,QAAQ,MAAM,kBAAkB,MAAM,MAAM,MAAM,SAAS,EAAE,CAAC;AAAA,MACjG,kBAAkB,CAAC,MAAM,WAAW,EAAE,OAAO,MAAM,WAAW,OAAO,CAAC,IAAI,iBAAkB,KAA+B,WAAW,MAAM,QAAQ,MAAM,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,SAAS,QAAQ,OAAO,EAAE,MAAM,SAAS,QAAQ,EAAE,EAAE;AAAA,IACrO;AAAA,IACA,mBAAmB,MAAM;AAAA,IACzB,MAAM,QAAQ,MAAM,MAAM;AACxB,YAAM,QAAQ,eAAe,IAA8C;AAC3E,UAAI,KAAK,OAAO,QAAS,OAAM,IAAIC,SAAQ,aAAa,YAAY;AACpE,YAAM,SAAS,MAAM,GAAG,QAAQ,MAAM,QAAQ;AAC9C,YAAM,UAAU,MAAM,GAAG,UAAU,QAAQ,MAAM,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpF,aAAO,EAAE,MAAM,OAAO,aAAa,WAAW,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,IAChH;AAAA,IACA,YAAY,MAAoB;AAC9B,YAAM,IAAI;AACV,aAAO,EAAE,MAAM,QAAQ,OAAO,WAAW,EAAE,WAAW,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,SAAS,MAAM,SAAS,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE;AAAA,IAC9J;AAAA,IACA,cAAc,MAAM,QAAgD;AAClE,UAAI,OAAO,QAAS,QAAO;AAC3B,YAAM,IAAI;AACV,YAAM,QAAQ,cAAc,OAAO,IAAI,KAAK,CAAC,EAAE,MAAM,EAAE,WAAW,SAAS,MAAM,SAAS,EAAE,QAAQ,CAAC;AACrG,aAAO,EAAE,MAAM,QAAQ,OAAO,WAAW,EAAE,WAAW,MAAM;AAAA,IAC9D;AAAA,EACF,CAAC;AACH;;;ACvDA,SAAS,cAAAC,mBAAkB;AAE3B,SAAS,WAAAC,gBAAe;AAOjB,SAAS,cAAc,MAAuG;AACnI,MAAI,KAAK,UAAU,KAAK,EAAE,WAAW,EAAG,OAAM,IAAI,MAAM,sCAAsC;AAC9F,MAAI,KAAK,WAAW,WAAW,EAAG,OAAM,IAAI,MAAM,uCAAuC;AACzF,MAAI,KAAK,eAAe,KAAK,WAAY,OAAM,IAAI,MAAM,uCAAuC;AAChG,SAAO,EAAE,UAAU,KAAK,WAAW,WAAW,KAAK,YAAY,WAAW,KAAK,YAAY,YAAY,KAAK,eAAe,MAAM;AACnI;AAEO,SAAS,qBAAqB,IAAqD;AACxF,SAAOC,YAAW;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,WAAW,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,qFAAqF;AAAA,MAC/I,YAAY,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,+CAA+C;AAAA,MAC1G,YAAY,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,qEAAqE;AAAA,MAChI,aAAa,EAAE,MAAM,WAAW,aAAa,2FAA2F;AAAA,IAC1I;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,sBAAsB;AAAA,QACtB,YAAY;AAAA,UACV,MAAM,EAAE,MAAM,UAAU,UAAU,KAAK;AAAA,UACvC,QAAQ,EAAE,MAAM,UAAU,UAAU,KAAK;AAAA,UACzC,OAAO,EAAE,MAAM,UAAU,UAAU,KAAK;AAAA,QAC1C;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAE,MAAM,QAAQ,MAAM,iBAAiB,MAAM,MAAO,KAAmC,eAAe,KAAK,EAAE,CAAC;AAAA,MACxI,kBAAkB,CAAC,MAAM,WAAW,EAAE,OAAO,iBAAkB,KAA+B,WAAW,MAAM,QAAQ,MAAM,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,SAAS,QAAQ,OAAO,EAAE,MAAM,SAAS,QAAQ,EAAE,EAAE;AAAA,IACxM;AAAA,IACA,mBAAmB,MAAM;AAAA,IACzB,MAAM,QAAQ,MAAM,MAAM;AACxB,YAAM,QAAQ,cAAc,IAA4F;AACxH,UAAI,KAAK,OAAO,QAAS,OAAM,IAAIC,SAAQ,aAAa,YAAY;AACpE,YAAM,SAAS,MAAM,GAAG,QAAQ,MAAM,QAAQ;AAC9C,YAAM,UAAU,MAAM,GAAG,SAAS,QAAQ,EAAE,WAAW,MAAM,WAAW,WAAW,MAAM,WAAW,YAAY,MAAM,WAAW,CAAC;AAClI,aAAO,EAAE,MAAM,OAAO,aAAa,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,IAClF;AAAA,IACA,YAAY,MAAoB;AAC9B,YAAM,IAAI;AACV,aAAO,EAAE,MAAM,QAAQ,OAAO,UAAU,EAAE,WAAW,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,SAAS,EAAE,cAAc,MAAM,SAAS,EAAE,WAAW,CAAC,GAAG,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE;AAAA,IAChL;AAAA,IACA,cAAc,MAAM,QAAgD;AAClE,UAAI,OAAO,QAAS,QAAO;AAC3B,YAAM,IAAI;AACV,YAAM,QAAQ,cAAc,OAAO,IAAI;AACvC,UAAI,UAAU,OAAW,QAAO;AAChC,aAAO,EAAE,MAAM,QAAQ,OAAO,UAAU,EAAE,WAAW,MAAM;AAAA,IAC7D;AAAA,EACF,CAAC;AACH;;;ACbA,IAAM,UAAU;AAEhB,IAAM,iBAAiB,MAAM,OAAO;AAEpC,IAAM,YAAY;AAGlB,IAAM,uBAAN,cAAmC,MAAM;AAAC;AAQnC,SAAS,gBAAgB,KAAiB,SAAiB,SAAkC;AAClG,MAAI,OAAO,CAAC,cAAc,WAAW,GAAG,CAAC,QAAQ;AAC/C,UAAM,QAAQ;AACd,UAAM;AAAA,MACJ,MAAM,MAAM,UAAU,SAAS;AAAA,QAC7B,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,CAAC,KAAK,QAAQ,MAAM,MAAM,YAAY,SAAS,SAAS,KAAK,GAAG;AAAA,MAC3E,CAAC;AAAA,MACD,UAAU;AAAA,IACZ;AAAA,EACF,CAAC;AACH;AAUA,eAAe,MACb,YACA,SACA,SACA,KACA,KACe;AACf,QAAM,YAAY,WAAW,iBAAiB,GAAG;AACjD,MAAI,cAAc,QAAW;AAC3B,QAAI,UAAU,SAAS;AACvB,QAAI,IAAI,cAAc,MAAM,iBAAiB,WAAW;AACxD;AAAA,EACF;AACA,MAAI,IAAI,WAAW,OAAQ,QAAO,QAAQ,GAAG;AAC7C,QAAM,WAAW,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB,EAAE;AAC7D,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,SAAS,WAAW,MAAM,EAAG,QAAO,QAAQ,GAAG;AACpD,QAAM,WAAW,SAAS,MAAM,OAAO,MAAM;AAC7C,MAAI,CAAC,SAAS,MAAM,GAAG,EAAE,MAAM,CAAC,SAAS,SAAS,MAAM,SAAS,OAAO,SAAS,QAAQ,QAAQ,KAAK,IAAI,CAAC,EAAG,QAAO,QAAQ,GAAG;AAChI,MAAI,CAAC,kBAAkB,GAAG,GAAG;AAC3B,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,wBAAwB;AAChC;AAAA,EACF;AACA,QAAM,WAAW,OAAO,IAAI,QAAQ,gBAAgB,CAAC;AACrD,MAAI,OAAO,SAAS,QAAQ,KAAK,WAAW,eAAgB,QAAO,SAAS,KAAK,GAAG;AAEpF,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,GAAG;AAAA,EAC1B,SAAS,OAAO;AACd,QAAI,iBAAiB,qBAAsB,QAAO,SAAS,KAAK,GAAG;AACnE,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,kBAAkB;AAC1B;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,cAAU,KAAK,MAAM,GAAG;AAAA,EAC1B,QAAQ;AACN,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,kBAAkB;AAC1B;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;AAClE,MAAI,QAAQ,SAAS,oBAAoB,OAAO,QAAQ,WAAW,UAAU;AAC3E,WAAO,MAAM,KAAK,OAAO,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,uBAAuB,SAAS,kCAAkC,SAAS,CAAC,EAAE,EAAE,CAAC;AAAA,EACxI;AACA,MAAI,QAAQ,WAAW,UAAU;AAC/B,WAAO,MAAM,KAAK,OAAO;AAAA,MACvB,IAAI;AAAA,MACJ,OAAO,EAAE,MAAM,uBAAuB,SAAS,YAAY,KAAK,UAAU,QAAQ,MAAM,IAAI,8BAA8B,KAAK,UAAU,QAAQ,GAAG,SAAS,CAAC,EAAE;AAAA,IAClK,CAAC;AAAA,EACH;AACA,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,UAAU,MAAY;AAAE,eAAW,MAAM,IAAI,MAAM,qBAAqB,CAAC;AAAA,EAAE;AACjF,MAAI,GAAG,SAAS,OAAO;AACvB,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,QAAQ,UAAU,QAAQ,SAAS,WAAW,MAAM;AAAA,EACrE,SAAS,OAAO;AACd,aAAS,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,YAAY,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,SAAS,CAAC,EAAE,EAAE;AAAA,EAClI,UAAE;AACA,QAAI,IAAI,SAAS,OAAO;AAAA,EAC1B;AACA,QAAM,KAAK,OAAO,MAAM;AAC1B;AAGA,SAAS,QAAQ,KAA2B;AAC1C,MAAI,UAAU,GAAG;AACjB,MAAI,IAAI,WAAW;AACrB;AAGA,SAAS,MAAM,KAAqB,OAAe,QAAgC;AACjF,MAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,MAAI,IAAI,KAAK,UAAU,EAAE,MAAM,mBAAmB,OAAO,OAAO,CAAC,CAAC;AACpE;AAGA,SAAS,SAAS,KAAsB,KAA2B;AACjE,MAAI,UAAU,KAAK,EAAE,YAAY,QAAQ,CAAC;AAC1C,MAAI,IAAI,mBAAmB;AAC3B,MAAI,QAAQ;AACd;AAGA,SAAS,kBAAkB,KAA+B;AACxD,QAAM,SAAS,IAAI,QAAQ,cAAc;AACzC,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,QAAM,OAAO,OAAO,MAAM,KAAK,CAAC,EAAE,CAAC;AACnC,SAAO,SAAS,UAAa,KAAK,KAAK,EAAE,YAAY,MAAM;AAC7D;AAGA,eAAe,SAAS,KAAuC;AAC7D,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,mBAAiB,SAAS,KAAK;AAC7B,UAAM,SAAS,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI;AAChE,YAAQ,OAAO;AACf,QAAI,OAAO,eAAgB,OAAM,IAAI,qBAAqB,yCAAyC;AACnG,WAAO,KAAK,MAAM;AAAA,EACpB;AACA,SAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAC9C;;;ACtLA,IAAM,oBAAoB,IAAI,OAAO;AAGrC,IAAM,kBAAN,cAA8B,MAAM;AAAC;AAGrC,SAAS,SAAS,OAAyC;AACzD,SAAO,OAAO,UAAU,YAAY,UAAU,OAAO,QAAmC,CAAC;AAC3F;AAGA,SAAS,WAAW,QAAiC,KAAiC;AACpF,QAAM,QAAQ,OAAO,GAAG;AACxB,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAGA,eAAe,WAAW,IAAsB,SAA8F;AAC5I,QAAM,SAAS,WAAW,SAAS,OAAO,GAAG,QAAQ;AACrD,MAAI,WAAW,UAAa,OAAO,KAAK,MAAM,GAAI,OAAM,IAAI,gBAAgB,gBAAgB;AAC5F,QAAM,WAAW,MAAM,GAAG,QAAQ,MAAM;AACxC,QAAM,OAAO,MAAM,GAAG,SAAS,QAAQ;AACvC,QAAM,QAAQ,OAAO,WAAW,MAAM,MAAM;AAC5C,MAAI,KAAK,SAAS,mBAAmB;AACnC,WAAO,EAAE,MAAM,SAAS,aAAa,MAAM,KAAK,MAAM,GAAG,iBAAiB,GAAG,OAAO,WAAW,KAAK;AAAA,EACtG;AACA,SAAO,EAAE,MAAM,SAAS,aAAa,MAAM,OAAO,WAAW,MAAM;AACrE;AAQA,eAAsB,gBAAgB,IAAsB,UAAkB,SAA6C;AACzH,MAAI;AACF,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO,EAAE,IAAI,MAAM,OAAO,MAAM,WAAW,IAAI,OAAO,EAAE;AAAA,MAC1D;AACE,eAAO,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,eAAe,SAAS,WAAW,UAAU,SAAS,CAAC,EAAE,EAAE;AAAA,IAClG;AAAA,EACF,SAAS,GAAG;AACV,UAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACzD,QAAI,aAAa,gBAAiB,QAAO,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,eAAe,SAAS,SAAS,CAAC,EAAE,EAAE;AAC3G,WAAO,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,YAAY,SAAS,SAAS,CAAC,EAAE,EAAE;AAAA,EACxE;AACF;;;AC5CO,IAAM,OAAO;AAGb,IAAM,SAAS,CAAC,SAAS,cAAc;AAcvC,SAAS,MAAM,KAAc,SAAiB,CAAC,GAAS;AAC7D,MAAI,OAAO,YAAY,MAAO;AAG9B,QAAM,KAAK,IAAI,iBAAiB;AAGhC,MAAI,aAAa,QAAQ;AAAA,IACvB,MAAM;AAAA,IACN,OAAO,IAAI,aAAa,gBAAgB,WAAW;AAAA,IACnD,MAAM;AAAA,EACR,CAAC;AACD,MAAI,aAAa,QAAQ;AAAA,IACvB,MAAM;AAAA,IACN,OAAO,IAAI,aAAa,gBAAgB,YAAY;AAAA,IACpD,MAAM;AAAA,EACR,CAAC;AACD,MAAI,aAAa,QAAQ;AAAA,IACvB,MAAM;AAAA,IACN,OAAO,IAAI,aAAa,gBAAgB,WAAW;AAAA,IACnD,MAAM;AAAA,EACR,CAAC;AAGD,MAAI,MAAM,SAAS,qBAAqB,EAAE,CAAC;AAC3C,MAAI,MAAM,SAAS,sBAAsB,EAAE,CAAC;AAC5C,MAAI,MAAM,SAAS,qBAAqB,EAAE,CAAC;AAG3C,kBAAgB,KAAK,uBAAuB,CAAC,UAAU,YAAY,gBAAgB,IAAI,UAAU,OAAO,CAAC;AAC3G;",
6
6
  "names": ["FsError", "FsError", "FsError", "FsError", "value", "FsError", "name", "FsError", "defineTool", "FsError", "defineTool", "FsError", "defineTool", "FsError", "defineTool", "FsError"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-file-system",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "「DSH·远程文件系统」:提供模型可见的远程文件工具 read_remote、write_remote、edit_remote,经 ssh 读写远程主机文件;并在 Web 右侧栏以 remote tab 预览远程文本,补上内置查看器只认本地文件的缺口。write_remote 只允许新建,绝无覆写分支;文件后端 RemoteFileSystem 不继承 FileSystem,仅借用 @deepseek-ai/dsh-fs 的类型词表与 FsError。标准可安装的 dsh host 与 client 插件。",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -61,9 +61,6 @@
61
61
  "typecheck": "tsc --noEmit",
62
62
  "smoke:rpc": "node smoke/rpc-smoke.mjs"
63
63
  },
64
- "dependencies": {
65
- "diff": "^9.0.0"
66
- },
67
64
  "peerDependencies": {
68
65
  "@deepseek-ai/cordis": "^4.0.2",
69
66
  "@deepseek-ai/dsh": "^0.2.0-rc.2",
@@ -84,6 +81,7 @@
84
81
  "@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
85
82
  "@types/node": "^22.0.0",
86
83
  "@types/react": "~18.3.1",
84
+ "diff": "^9.0.0",
87
85
  "esbuild": "^0.25.0",
88
86
  "typescript": "^5.5.0"
89
87
  }