@zhangfengshun/dsh-remote-ssh 2.4.12 → 2.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  本文件的版本号与 `package.json` 的 `version` 保持一致。每个版本对应一个 Cordis Package 快照(`pkg-N`)。
4
4
 
5
+ ## [2.4.13] — `remote_ssh_push` / `remote_ssh_sync` 成功却报 `returned invalid output`(issue #14)
6
+ ### 修复
7
+ - **推送成功却被判为工具输出非法**(感谢 @Linhaojing 的根因定位与复现):`sync` / `push` 共用的 output schema 把 `error` 声明为**必填**且 `additionalProperties: false`,而两条成功路径返回 `{ ok: true, mirrorPath }` / `{ ok: true, remotePath }` —— 成功时既缺 `error` 又带未声明字段,必然被 output 校验拒掉;**失败路径反而合法**,于是症状是「**只有成功会报错**」:模型看到 `Error: tool "remote_ssh_push" returned invalid output: missing required property "value.error"; "value.remotePath" is not a declared property`,但远端文件其实已经写好了,只能靠再跑一次 `ls`/`sha256sum` 确认,很容易误判成需要重试。而这条链路正是远程工作区里 agent `write`/`edit` 落回远端的唯一通道,成功信号不可信影响面不小。
8
+ - `syncOutput` 现在声明 `remotePath` / `mirrorPath`(成功路径本来就会带),`error` 改为**可选**;
9
+ - 两条成功路径补上 `error: ""`,与同文件其它工具(`normExec()`、`remote_ssh_ls` / `cat` / `grep` / `glob`)的风格一致;
10
+ - **并把全文件 6 处 output schema 的 `error` 一律改为可选**(通用护栏:`error` 按定义在成功时不存在,标必填就是定时炸弹)。失败路径的 `{ ok: false, error }` 不受影响。
11
+
12
+ ### 测试
13
+ - 新增 `tests/tool-output-schema.test.mjs`(**17 条断言**):用**真实的 `remoteSyncUp` / `remoteSyncDown`**(桩掉 `subprocess`)取实际返回对象,再用**真实的 `syncOutput.schema`** 跑一个模拟宿主校验器的 mini-validator(必填齐全 + 无未声明字段 + 类型正确)——成功路径、参数缺失失败路径、非零退出码失败路径全部通过校验;另有静态护栏断言(**任何** output schema 都不得把 `error` 标成必填、`ok` 仍必填、两个路径字段已声明)与**反向自检**(把历史的坏 schema 喂给校验器,逐字复现报告者看到的 `missing required property "value.error"; "value.mirrorPath" is not a declared property`)。
14
+
5
15
  ## [2.4.12] — 公开产物脱敏(无功能改动)
6
16
  ### 变更
7
17
  - **移除文档、代码注释与测试夹具中的真实标识**:此前 CHANGELOG / README / 代码注释 / issue 回复里出现了真实的项目名、HPC 主机名、内网 IP、真实远程路径与镜像目录 ID。现已全部替换为通用占位符(`proj-a` / `my-project` / `project-b`、`~/proj`、`hpc-a.example.com`、`192.0.2.10`(TEST-NET-1 文档网段)、`wmirror1`…、`solver`),并在 `MAINTENANCE.md` 写入硬规则:**公开产物一律用占位符**,发布前跑一次私有标识扫描。README 七夕段落里刻意保留的署名不受影响。
package/README.md CHANGED
@@ -46,7 +46,7 @@
46
46
  **一条命令安装**(无需 token、API Key 或额外配置):
47
47
 
48
48
  ```bash
49
- dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.12
49
+ dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.13
50
50
  ```
51
51
 
52
52
  安装后**重启 DSH**。`@zhangfengshun/dsh-remote-ssh` 必须在 bundles 列表中排在 `dsh-better-sidebar` **之后**。
@@ -207,6 +207,7 @@ dsh plugin --profile <name> remove @zhangfengshun/dsh-remote-ssh
207
207
  | 远程工作区里「按文件名搜索」一直转圈(大工作区) | 2.4.11 起已修复:改为浅层优先(`-maxdepth 3`,实测冷 0.68s / 热 0.11s)且**有命中就立即返回**(深挖转后台预热缓存,浅层零命中才同步等深挖 `-maxdepth 8`),遍历前剪噪声目录、去掉会阻塞短路的 `sort`,并加远端墙钟预算——到点返回**已收集的部分结果**并标记不完整。实测某大型远程项目工作区:旧实现 5 分钟零输出 → 现在 **0.96s 返回 43 条** |
208
208
  | 远程会话里 `@文件名` 没有候选,但单独输入 `@` 有 | 2.4.11 起已修复:模糊查询依赖索引,而索引首选 `git ls-files --cached --others`(`--others` 要遍历整棵工作树,巨型项目上跑不完 → 索引为空)。现在三级降级(完整 git 6s → 仅索引 git 3s → 有界 `find` `maxdepth 3` + 5s),并在索引未就绪时用有界 find 即时兜底(实测 0.65s),不再出现「全空」 |
209
209
  | 安装时提示 `minimumReleaseAge` 或「No matching version」(刚发布) | npm 供应链新鲜度策略,等 1–5 分钟后重试即可 |
210
+ | `remote_ssh_push` / `remote_ssh_sync` 明明推送成功却报 `returned invalid output` | 2.4.13 起已修复:这两个工具共用的 output schema 把 `error` 标成必填、成功路径又返回未声明的 `remotePath`/`mirrorPath`,于是**只有成功会报错**(失败路径反而合法)。现在 schema 声明两个路径字段、`error` 改为可选,成功路径也带 `error: ""`;全文件所有 output schema 的 `error` 一并改为可选 |
210
211
  | 命令卡住不返回 | 默认 120s 超时后自动丢弃会话;长时任务用 `timeoutMs: 0`,随时可用 `remote_ssh_kill` 强杀 |
211
212
  | 大文件读取被截断 | 单文件读取上限 4MB、下载池化路径约 6.29MB(更大自动回落一次性连接);用 `remote_ssh_exec` + `head`/`tail` 分段处理 |
212
213
 
package/README_EN.md CHANGED
@@ -46,7 +46,7 @@ A **DSH** plugin like **VSCode Remote-SSH**: connect to remote HPC / servers via
46
46
  **One command** (no token, API key or extra configuration needed):
47
47
 
48
48
  ```bash
49
- dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.12
49
+ dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.13
50
50
  ```
51
51
 
52
52
  **Restart DSH** after installation. `@zhangfengshun/dsh-remote-ssh` must come **after** `dsh-better-sidebar` in the bundles list.
@@ -207,6 +207,7 @@ The plugin never patches DSH sources or injects into the profile dependency tree
207
207
  | "Search by file name" spins forever in a remote workspace (large trees) | Fixed in 2.4.11: shallow-first (`-maxdepth 3`, measured 0.68s cold / 0.11s warm) **returning as soon as anything matches** (the deeper pass becomes a background cache warm-up and only runs synchronously when the shallow pass finds nothing), noise directories pruned before traversal, the short-circuit-blocking `sort` removed, and a remote wall-clock budget that returns the **partial results collected so far** and flags them as incomplete. Measured on an 大型项目 workspace: 5 minutes with zero output before → **43 matches in 0.96s** now |
208
208
  | In a remote session `@filename` shows no candidates, while a bare `@` works | Fixed in 2.4.11: fuzzy queries rely on the index, and the index preferred `git ls-files --cached --others` (`--others` must walk the whole working tree and never finishes on huge projects → empty index). It now degrades in three budgeted steps (full git 6s → index-only git 3s → bounded `find` `maxdepth 3` + 5s) and falls back to a bounded `find` (measured 0.65s) whenever the index is not ready yet, so candidates are never empty |
209
209
  | Install fails with `minimumReleaseAge` or "No matching version" right after a release | npm supply-chain freshness policy — retry after 1–5 minutes |
210
+ | `remote_ssh_push` / `remote_ssh_sync` report `returned invalid output` even though the push succeeded | Fixed in 2.4.13: their shared output schema marked `error` as required while the success path returned an undeclared `remotePath`/`mirrorPath`, so **only success failed** (failures validated fine). The schema now declares both path fields, `error` is optional, the success paths carry `error: ""`, and every output schema in the file treats `error` as optional |
210
211
  | A command hangs forever | The 120s timeout discards the pooled session automatically; use `timeoutMs: 0` for long jobs and `remote_ssh_kill` at any time |
211
212
  | Large files are truncated | 4MB per read, ≈6.29MB on the pooled download path (larger files fall back to a one-shot connection); use `remote_ssh_exec` with `head`/`tail` to page through |
212
213
 
package/lib/index.js CHANGED
@@ -1087,7 +1087,7 @@ async function remoteSyncDown(subprocess, p, remotePath, mirrorPath) {
1087
1087
  const sshErr = readCollected(sshH, "stderr");
1088
1088
  const tarErr = readCollected(tarH, "stderr");
1089
1089
  // ssh 正常退出且 tar 正常退出才算成功
1090
- if (sshOut.exitCode === 0 && tarOut.exitCode === 0) return { ok: true, mirrorPath: mirrorPath };
1090
+ if (sshOut.exitCode === 0 && tarOut.exitCode === 0) return { ok: true, error: "", mirrorPath: mirrorPath };
1091
1091
  const parts = [];
1092
1092
  if (pipeErr) parts.push("管道: " + pipeErr);
1093
1093
  if (sshOut.exitCode !== 0) parts.push("ssh 退出码 " + sshOut.exitCode + (sshErr ? ": " + sshErr.slice(0, 500) : ""));
@@ -1125,7 +1125,7 @@ async function remoteSyncUp(subprocess, p, remotePath, mirrorPath) {
1125
1125
  if (tarOut.exitCode === 0 && sshOut.exitCode === 0) {
1126
1126
  // 批量覆盖远端文件:清空该 profile 的读/列举缓存(findings §4.3a)
1127
1127
  clearCachesForProfile(p);
1128
- return { ok: true, remotePath: remotePath };
1128
+ return { ok: true, error: "", remotePath: remotePath };
1129
1129
  }
1130
1130
  const parts = [];
1131
1131
  if (pipeErr) parts.push("管道: " + pipeErr);
@@ -1278,7 +1278,7 @@ function execSchema() {
1278
1278
  exitCode: { type: "number", required: true },
1279
1279
  stdout: { type: "string", required: true },
1280
1280
  stderr: { type: "string", required: true },
1281
- error: { type: "string", required: true },
1281
+ error: { type: "string" },
1282
1282
  truncated: { type: "boolean", required: true },
1283
1283
  isTimeout: { type: "boolean", required: true }
1284
1284
  }
@@ -2194,7 +2194,7 @@ function apply(ctx, config) {
2194
2194
  ok: { type: "boolean", required: true },
2195
2195
  path: { type: "string", required: true },
2196
2196
  entries: { type: "array", required: true, items: { type: "object", additionalProperties: true } },
2197
- error: { type: "string", required: true }
2197
+ error: { type: "string" }
2198
2198
  }
2199
2199
  },
2200
2200
  render: textRender(function (a, v) {
@@ -2223,7 +2223,7 @@ function apply(ctx, config) {
2223
2223
  content: { type: "string", required: true },
2224
2224
  binary: { type: "boolean", required: true },
2225
2225
  truncated: { type: "boolean", required: true },
2226
- error: { type: "string", required: true }
2226
+ error: { type: "string" }
2227
2227
  }
2228
2228
  },
2229
2229
  render: textRender(function (a, v) { return v.error || v.content || ""; })
@@ -2262,10 +2262,16 @@ function apply(ctx, config) {
2262
2262
  const syncParams = {
2263
2263
  workspaceId: { type: "string", description: "远程工作区 id(留空则在当前远程工作区会话中自动识别)。" }
2264
2264
  };
2265
+ // sync / push 共用的输出 schema(issue #14):`error` **不能**标成必填 —— 成功路径本来就没有
2266
+ // error,标必填会让「推送成功」被判成 `returned invalid output`(模型看到失败、副作用却已落地,
2267
+ // 只能靠再跑一次 ls 才能确认)。同理成功路径会带上 remotePath / mirrorPath,必须在 schema 里声明
2268
+ // (additionalProperties: false 会拒掉未声明字段)。
2265
2269
  const syncOutput = {
2266
2270
  schema: { type: "object", additionalProperties: false, properties: {
2267
2271
  ok: { type: "boolean", required: true },
2268
- error: { type: "string", required: true }
2272
+ remotePath: { type: "string" },
2273
+ mirrorPath: { type: "string" },
2274
+ error: { type: "string" }
2269
2275
  } },
2270
2276
  render: textRender(function (a, v) { return v.ok ? "同步完成" : (v.error || "同步失败"); })
2271
2277
  };
@@ -2327,7 +2333,7 @@ function apply(ctx, config) {
2327
2333
  path: { type: "string", required: true },
2328
2334
  matches: { type: "array", required: true, items: { type: "object", additionalProperties: true } },
2329
2335
  truncated: { type: "boolean", required: true },
2330
- error: { type: "string", required: true }
2336
+ error: { type: "string" }
2331
2337
  }
2332
2338
  },
2333
2339
  render: textRender(function (a, v) {
@@ -2364,7 +2370,7 @@ function apply(ctx, config) {
2364
2370
  path: { type: "string", required: true },
2365
2371
  files: { type: "array", required: true, items: { type: "string" } },
2366
2372
  truncated: { type: "boolean", required: true },
2367
- error: { type: "string", required: true }
2373
+ error: { type: "string" }
2368
2374
  }
2369
2375
  },
2370
2376
  render: textRender(function (a, v) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhangfengshun/dsh-remote-ssh",
3
- "version": "2.4.12",
3
+ "version": "2.4.13",
4
4
  "description": "DSH web plugin: VSCode Remote-SSH-like remote development (SSH to supercomputers/servers, remote workspace, file explorer, integrated terminal), integrated with dsh-better-sidebar and DSH settings.",
5
5
  "keywords": [
6
6
  "dsh",