dsh-vscode-mode 0.23.2 → 0.23.4

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/lib/index.js CHANGED
@@ -7649,6 +7649,25 @@ function reviewPage(cwd, cursor = 0, history = false, path) {
7649
7649
  }))]
7650
7650
  };
7651
7651
  }
7652
+ /**
7653
+ * @public
7654
+ * @author ddj 2026年10月31号
7655
+ * @description: 该文件是否还有未处理的官方待审改动。缺失文件预览据此区分「需要去差异栏 Keep 结案」
7656
+ * 与「记录都已处理、可直接关闭标签页」,避免已结案的删除文件继续提示待审。
7657
+ * @param cwd Migrated workspace
7658
+ * @param path Host path of the file
7659
+ * @returns 存在未决官方记录时为 true
7660
+ */
7661
+ function officialPending(cwd, path) {
7662
+ if (typeof path !== "string") return false;
7663
+ const db = dbFor(cwd);
7664
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE name='review_files' AND type='table'").get()) return false;
7665
+ if (path.length > 1 && path[1] === ":") {
7666
+ const folded = path.split(String.fromCharCode(92)).join("/").toLowerCase();
7667
+ return !!db.prepare("SELECT 1 FROM review_files WHERE current=1 AND decision='pending' AND lower(replace(identity,char(92),'/'))=? LIMIT 1").get(folded);
7668
+ }
7669
+ return !!db.prepare("SELECT 1 FROM review_files WHERE current=1 AND decision='pending' AND identity=? LIMIT 1").get(path);
7670
+ }
7652
7671
  /** @public @author ddj 2026年10月26号 @param cwd Migrated workspace. @param path Legacy write target. @returns Whether any official version owns the file, including decided history. */
7653
7672
  function hasOfficial(cwd, path) {
7654
7673
  if (typeof path !== "string") return false;
@@ -7926,20 +7945,31 @@ function installCapture(ctx, onSnapshot) {
7926
7945
  log.warn("官方历史补采不可用:缺少 sessionQuery;后续实时事件仍会采集");
7927
7946
  return;
7928
7947
  }
7948
+ const source = ctx.get("workspaceChanges");
7949
+ if (!source?.summary) {
7950
+ log.warn("官方历史补采不可用:缺少 workspaceChanges;后续实时事件仍会采集");
7951
+ return;
7952
+ }
7929
7953
  try {
7930
7954
  if (session.header?.cwd) {
7931
7955
  await loadBucket(ctx, session.header.cwd);
7932
7956
  await repairPaths(session.header.cwd, resolvePath);
7933
7957
  }
7934
7958
  const events = await query.listEvents(session.id);
7959
+ let expired = 0;
7935
7960
  for (const event of events) {
7936
7961
  if (event.type !== "workspace/changes" || event.seq < (session.inheritedEventCount ?? 0)) continue;
7962
+ if (!source.summary(session.id, event.seq)) {
7963
+ expired += 1;
7964
+ continue;
7965
+ }
7937
7966
  const window = await query.readEvent({
7938
7967
  sessionId: session.id,
7939
7968
  seq: event.seq
7940
7969
  });
7941
7970
  collector.enqueue(session, window.target);
7942
7971
  }
7972
+ if (expired) log.info(`官方历史补采跳过 ${expired} 轮已失效事件:官方 summary 不跨宿主重启保留,实时事件照常采集`);
7943
7973
  } catch (error) {
7944
7974
  log.warn("官方历史补采失败:" + String(error));
7945
7975
  }
@@ -16015,6 +16045,39 @@ function batchSnapshot(cwd, pending) {
16015
16045
  };
16016
16046
  }
16017
16047
  /**
16048
+ * @private
16049
+ * @author ddj 2026年10月31号
16050
+ * @description: 文件已在磁盘删除时修正官方记录的能力说明。Keep 只写审查库、仍可结案,
16051
+ * Undo 需要读写源文件、必然不可用:把「缺少原始字节」这种误导性原因换成可执行说明。
16052
+ * 只改文案与 undo 位,不参与判定写入。
16053
+ * @param ctx Host 能力
16054
+ * @param cwd 工作区
16055
+ * @param detail 官方记录详情
16056
+ * @param capability 已按冷/热端调整过的能力
16057
+ * @returns 修正后的能力;无法确认删除时原样返回
16058
+ */
16059
+ async function absenceCapability(ctx, cwd, detail, capability) {
16060
+ if (!capability.keep) return capability;
16061
+ if (detail.diff.kind === "text" && !detail.diff.after) return {
16062
+ ...capability,
16063
+ undo: false,
16064
+ reason: "该记录删除了这个文件:Keep 按删除结案;Undo 需要完整文件内容,官方差异只有片段,不可用。"
16065
+ };
16066
+ const fs = ctx.get("fs");
16067
+ if (!fs) return capability;
16068
+ try {
16069
+ const target = await resolveTargetIn(ctx, cwd, detail.item.currentPath);
16070
+ if (await fs.stat(target)) return capability;
16071
+ } catch {
16072
+ return capability;
16073
+ }
16074
+ return {
16075
+ ...capability,
16076
+ undo: false,
16077
+ reason: "文件已在磁盘删除:Keep 按删除结案;Undo 需要读写该文件,当前不可用。"
16078
+ };
16079
+ }
16080
+ /**
16018
16081
  * @public
16019
16082
  * @author ddj 2026年10月26号 / 2026年10月28号
16020
16083
  * @description: 审查 RPC 处理器。工作区一律由 host 解析,绝不采信调用方 cwd:
@@ -16160,7 +16223,7 @@ function reviewHandlers(ctx, registry) {
16160
16223
  error: "官方审查记录不存在"
16161
16224
  };
16162
16225
  }
16163
- const capability = liveCapabilityOf(detail.capability, scope.live, false);
16226
+ const capability = await absenceCapability(ctx, cwd, detail, liveCapabilityOf(detail.capability, scope.live, false));
16164
16227
  log.debug(`[review] detail live=${scope.live ? 1 : 0} id=${args.id} found=1 keep=${capability.keep ? 1 : 0} undo=${capability.undo ? 1 : 0}`);
16165
16228
  return {
16166
16229
  ok: true,
@@ -16470,6 +16533,29 @@ function pendingHandlers(ctx, registry) {
16470
16533
  /** Missing historical files: frozen, whole-file deletion reviews. */
16471
16534
  const frozen = /* @__PURE__ */ new Map();
16472
16535
  const locks = /* @__PURE__ */ new Set();
16536
+ /**
16537
+ * @public
16538
+ * @author ddj 2026年10月31号
16539
+ * @description: 给缺失文件预览的可预期拒绝挂稳定错误码。客户端据此显示终态说明,
16540
+ * 而不是把内部错误原文当「加载失败」反复重试;也不让上游把 SQLite 报错原文透给编辑器。
16541
+ * @param code 稳定错误码(official-covered / no-records / no-evidence / too-large)
16542
+ * @param message 可读原因
16543
+ * @returns 带 code 的 Error
16544
+ */
16545
+ function refusal(code, message) {
16546
+ return Object.assign(new Error(message), { code });
16547
+ }
16548
+ /**
16549
+ * @public
16550
+ * @author ddj 2026年10月31号
16551
+ * @description: 从任意错误里取回缺失文件预览的稳定拒绝码;不是本模块抛出的错误一律返回 undefined。
16552
+ * @param error 捕获到的错误
16553
+ * @returns 稳定拒绝码或 undefined
16554
+ */
16555
+ function refusalCodeOf(error) {
16556
+ const code = error?.code;
16557
+ return code === "official-covered" || code === "official-decided" || code === "no-records" || code === "no-evidence" || code === "too-large" ? code : void 0;
16558
+ }
16473
16559
  /** @private @author ddj 2026年10月28号 @param target Resolved absolute path. @returns Platform file identity. */
16474
16560
  function fileKey(target) {
16475
16561
  const normalized = resolve(target).replace(/\\/g, "/");
@@ -16520,13 +16606,16 @@ async function fileAbsent(ctx, cwd, path) {
16520
16606
  async function missingReview(ctx, session, cwd, bucket, path) {
16521
16607
  const abs = targetPathOf(ctx, await resolveTargetIn(ctx, cwd, path));
16522
16608
  if (!abs) throw new Error("无法解析目标文件路径");
16523
- if (hasOfficial(cwd, path) || hasOfficial(cwd, abs)) throw new Error("该文件已有官方审查记录,请使用官方审查入口");
16609
+ if (hasOfficial(cwd, path) || hasOfficial(cwd, abs)) {
16610
+ if (!officialPending(cwd, abs) && !officialPending(cwd, path)) throw refusal("official-decided", "该文件已在磁盘删除,官方审查记录均已处理:可直接关闭该标签页");
16611
+ throw refusal("official-covered", "该文件已有官方审查记录(磁盘上已删除):请在差异栏对该待审条目执行 Keep 结案;整文件删除预览只适用于待审历史记录");
16612
+ }
16524
16613
  if (!await fileAbsent(ctx, cwd, abs)) throw new Error("文件已重新出现,请刷新");
16525
16614
  const records = await fileRecords(ctx, cwd, bucket, abs);
16526
16615
  const latest = records.at(-1);
16527
- if (!records.some((record) => !record.archived && !recordResolved(record))) throw new Error("没有待审历史记录");
16528
- if (!latest || typeof latest.after !== "string") throw new Error("缺少可靠的完整历史快照,无法展示或恢复;不会用局部差异拼接文件");
16529
- if (Buffer.byteLength(latest.after, "utf8") > 8388608) throw new Error("历史快照过大,无法整文件预览");
16616
+ if (!records.some((record) => !record.archived && !recordResolved(record))) throw refusal("no-records", "磁盘上已没有该文件,且没有待审历史记录:可直接关闭该标签页");
16617
+ if (!latest || typeof latest.after !== "string") throw refusal("no-evidence", "磁盘上已没有该文件,且缺少可靠的完整历史快照,无法展示或恢复;不会用局部差异拼接文件");
16618
+ if (Buffer.byteLength(latest.after, "utf8") > 8388608) throw refusal("too-large", "磁盘上已没有该文件,且历史快照过大,无法整文件预览");
16530
16619
  for (const [token, item] of frozen) if (item.expires < Date.now()) frozen.delete(token);
16531
16620
  if (frozen.size >= 256) throw new Error("缺失文件预览过多,请稍后重试");
16532
16621
  const view = {
@@ -17316,9 +17405,11 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
17316
17405
  review: await missingReview(ctx, sc.session, sc.cwd, bucket, args.path)
17317
17406
  };
17318
17407
  } catch (error) {
17408
+ const code = refusalCodeOf(error);
17319
17409
  return {
17320
17410
  ok: false,
17321
- error: String(error)
17411
+ error: String(error),
17412
+ ...code ? { code } : {}
17322
17413
  };
17323
17414
  }
17324
17415
  },