dsh-vscode-mode 0.23.4 → 0.23.5

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
@@ -533,13 +533,13 @@ profile 请替换为实际目标,不要把示例目录当成当前运行 profi
533
533
 
534
534
  ```bash
535
535
  # ① Git 安装(clone + prepare 构建;推荐打固定 tag)
536
- dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.23.4
536
+ dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.23.5
537
537
 
538
538
  # ② npm 注册表(发布到 npm 后)
539
- dsh plugin --profile web add dsh-vscode-mode@0.23.4
539
+ dsh plugin --profile web add dsh-vscode-mode@0.23.5
540
540
 
541
541
  # ③ GitHub Release tgz 直装(Release asset 可用后)
542
- dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.23.4/dsh-vscode-mode-0.23.4.tgz
542
+ dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.23.5/dsh-vscode-mode-0.23.5.tgz
543
543
  ```
544
544
 
545
545
  > **DSH 版本政策(0.17.0 起)**:六项 DSH peer 仍统一声明为 `*`,另以
@@ -880,7 +880,7 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
880
880
  完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
881
881
  近期关键版本:
882
882
 
883
- - **v0.23.4**:修复桌面端点计划卡「查看全文」会先闪开一页官方计划预览、随即自动关闭。根因:官方卡片走 `ctx.sidebarRight.openResource(planAddress(callId))`,而本插件以 extension 优先级认领了 `dsh-resource://plan/**` 与 `dsh-resource://plan-review/**`,于是侧栏先按本插件类型建出一个页签(正文「正在打开计划预览…」),挂载后才把计划转进「文件编辑」、再把这个页签关掉——用户看到的就是这次建而即关。现由新增 `src/client/planDirect.ts` 在装配期包装 `sidebarRight.openResource`:只接管「2 段计划地址 + 会话为在屏会话」,正文依次取回顾卡片的 `params.planReview`、插件侧地址→正文缓存、可选 `resources.source()` 资源流(上限 1.5s),拿到正文后经 `requestOpen(..., { preview: true })` 直通本插件编辑器的只读计划页签,不再创建侧栏页签;非计划地址、跨会话与 subagent 5 段地址、正文不可得、编辑器 5s 未回执、以及服务对象只读导致包装赋值失败,一律回落原生 `openResource`,原「认领 + 挂载后转移」链路保留为后备。`openReceipt` 新增可选 `timeoutMs`,`planState` 新增有界(64 条)地址→正文缓存并由 live `exit_plan_mode` 写入。验证:typecheck 0 error;`tests/planDirect.test.ts`(新增 9 例)、`tests/planBridge.test.ts`(13)、`tests/planState.test.ts`(10)、`tests/openReceipt.test.ts`(9)四套 41/41;`npm run build` 双 bundle 通过(含客户端产物完整性校验);浏览器确认新 bundle 已加载且 Console 无「计划直通装配失败」;GUI 点按由用户在本机 Web 端手动复验后发布。见 [发布说明](docs/release-0.23.4.md)。
883
+ - **v0.23.5**:修复差异栏把同一文件算成两条(差异文件数偏大)。待审条目存的是写入时的相对路径,官方导航用绝对身份,而 `mergedPage()` 与 `diffSummary` 的去重都只按 path 建覆盖集,相对路径的待审行压不掉绝对身份的官方行,于是同一文件同时留下待审行与官方汇总行;现两侧覆盖集都补入条目身份(与官方 identity 同源)。另修复本会话新建的文件被渲染成一堆修改块:累计基线保留「最早内容」,新建文件首次采集时磁盘已有会话自己的中间产物,基线因此非空,「新增 516 行」被切成若干 `-`/`+` 混合块,与官方整文件新增(`before=false`、`oldLines=0`)口径相反;现按每个文件在该会话内最早的官方记录判定整文件新增,命中时待审侧基线置空,列表与详情只出一块全加差异、增删计数等于整文件行数,`addedFiles()` 查询失败则回退旧口径。验证:typecheck 0 error;全量单测通过;`npm run build` 通过;本版未做 GUI 点按验收(缺口保留)。见 [0.23.5 发布说明](docs/release-0.23.5.md)。- **v0.23.4**:修复桌面端点计划卡「查看全文」会先闪开一页官方计划预览、随即自动关闭。根因:官方卡片走 `ctx.sidebarRight.openResource(planAddress(callId))`,而本插件以 extension 优先级认领了 `dsh-resource://plan/**` 与 `dsh-resource://plan-review/**`,于是侧栏先按本插件类型建出一个页签(正文「正在打开计划预览…」),挂载后才把计划转进「文件编辑」、再把这个页签关掉——用户看到的就是这次建而即关。现由新增 `src/client/planDirect.ts` 在装配期包装 `sidebarRight.openResource`:只接管「2 段计划地址 + 会话为在屏会话」,正文依次取回顾卡片的 `params.planReview`、插件侧地址→正文缓存、可选 `resources.source()` 资源流(上限 1.5s),拿到正文后经 `requestOpen(..., { preview: true })` 直通本插件编辑器的只读计划页签,不再创建侧栏页签;非计划地址、跨会话与 subagent 5 段地址、正文不可得、编辑器 5s 未回执、以及服务对象只读导致包装赋值失败,一律回落原生 `openResource`,原「认领 + 挂载后转移」链路保留为后备。`openReceipt` 新增可选 `timeoutMs`,`planState` 新增有界(64 条)地址→正文缓存并由 live `exit_plan_mode` 写入。验证:typecheck 0 error;`tests/planDirect.test.ts`(新增 9 例)、`tests/planBridge.test.ts`(13)、`tests/planState.test.ts`(10)、`tests/openReceipt.test.ts`(9)四套 41/41;`npm run build` 双 bundle 通过(含客户端产物完整性校验);浏览器确认新 bundle 已加载且 Console 无「计划直通装配失败」;GUI 点按由用户在本机 Web 端手动复验后发布。见 [发布说明](docs/release-0.23.4.md)。
884
884
  - **v0.23.3**:修复 issue #13(已删除文件的差异条目把人卡死)与 #12(重启后成串「采集不完整」)。①文件在磁盘上被删掉时,客户端申请整文件删除预览被「已有官方审查记录」拒绝,而拒绝只是一条普通 `Error`,于是落到通用加载失败分支——「文件加载失败:…」+ 进度钉在 0%、重试同错、标签页关不掉;`edrv.review.detail` 还把 Undo 不可用解释成「缺少可验证的原始字节」,而真实原因是文件已不在。现缺失文件预览的可预期拒绝带稳定码(`official-covered` / `official-decided` / `no-records` / `no-evidence` / `too-large`),客户端据此显示「文件已在磁盘删除(· 有官方待审改动)」终态:标题 + 原因 + 重试 / 关闭标签页,不再画 0% 进度条;官方待审记录在磁盘已删除时给出「Keep 按删除结案、Undo 需要读写该文件」的准确原因(Keep 语义不变,仍只写共享审查库),记录都已结案时提示可直接关闭;`!review` 的磁盘状态抖动改为最多重读 2 次后给终态,消除缺失↔重现的无限递归。v0.22.0 起 `hasOfficial()` 已用 `typeof path !== 'string'` 守卫堵掉把不透明 FsTarget 绑进 SQL 导致的 `Unknown named parameter 'targetKey'`,本版不再复现。②官方 `workspace/changes` 的 summary 只存在宿主内存里,插件启动补采原先无条件把历史事件全部入队,重启后每一轮都必然抛「summary 已失效或尚不可用」并写成不可修复的 `gap`(面板成串「轮次 N 采集不完整」);现补采先 `summary()` 探测再入队,失效事件只记一条汇总日志。验证:typecheck 与基线同为 37 个既有错误(`fflate/browser` 依赖缺失)、零新增;定向单测 111/111(含新增 `tests/deletedFileReview.test.ts` 5 例与 `tests/officialBackfill.test.ts` 2 例)、EditorView 相关 82/82;`npm run build` 四个 bundle 通过。见 [发布说明](docs/release-0.23.3.md)。
885
885
  - **v0.23.2**:修复 Keep 在非活跃端(桌面端)点了没反应,两处成因。①`edrv.review.decide` 原先一律要求本端活跃会话,而能力位只按审查库推导,会话在另一实例时按钮亮着、点击后抛错被兜底成 HTTP 500。现按写入副作用区分:官方记录 Keep 只写共享审查库(`BEGIN IMMEDIATE` + 幂等键 + 版本 CAS),冷端放行;官方 Undo(校验证据并写回源文件)与待审投影条目(本机 JSON 无跨进程锁)仍限活跃端,返回可读跨端原因而非 500;详情新增 `capability.live`,冷端禁用 Undo 并说明、Keep 提示补「写入共享审查库,仍可执行」。②已结算待审投影的 `capability.keep` 恒为假,却因排在 `items[0]`、同路径官方待审行被历史过滤,把官方记录挤出差异栏(表现为「待审 0」+Keep 禁用+点击无反应);现 `pendingPageOf()` 剔除已结算投影,官方记录重新成为审查目标。另详情未加载时点 Keep 不再静默失败;打开文件时若某条官方记录的改动已全部不在当前文件中(无可定位差异且缓冲区干净),自动按 Keep 结案(只标记不写盘),差异栏不再显示「本记录 N 处改动均已不在当前文件中」;官方元数据首次载入期间差异栏显示 `—` 而非按历史记录报数,重启瞬间不再闪出几十条假待审。验证:typecheck 0 error、目标单测 57/57、build 通过、真实审查库探针确认首条回到官方记录且 `capability={keep:true,undo:true,live:true}`;桌面端 GUI 点按验收因重启后恢复的是另一个会话未完成,缺口保留。发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.2.md)。
886
886
  - **v0.23.1**:修复 Keep All / Undo All 在待审超过一页时必定中止——批量按钮原先复用分页接口并把每页的展示用计数当全量总数校验,官方待审 150 条时先报「待审记录在分页期间变化」或最终报「待审记录数量不完整」,实际 0 项被决策。现改为一次性完整快照(`edrv.review.list` 新增 `all: true`,服务端循环取全并与历史改动投影去重,`nextCursor: null`、`pending` 为真实总数),客户端单次取快照、校验唯一且仍待审后逐项决策;超过 2000 条明确拒绝并提示分批,途中变化仍整批中止不部分写入。差异栏浏览仍走原分页接口。本版未在重启后的 GUI 点按验证,缺口保留;发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.1.md)。
package/lib/client.js CHANGED
@@ -4117,6 +4117,7 @@ window.__ModuleLoader__.load({
4117
4117
  if (!item.id.startsWith("pending:")) continue;
4118
4118
  const path = item.currentPath || item.path;
4119
4119
  covered.add(diffPathKey(path));
4120
+ covered.add(diffPathKey(item.id.slice(8)));
4120
4121
  rows.push({
4121
4122
  path,
4122
4123
  label: item.display || path,
package/lib/client.js.map CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/lib/client.js.map
3
- size: 35694036 bytes
4
- sha256: 5a0053a6aa5733ebef51b077a624f0fc5bcf1a9c555232c357762a7eb8bdf2ef
3
+ size: 35694514 bytes
4
+ sha256: 7fa559b003ee01e2e53a8698796d4a4e567f8e63d0b6d00548272a0057026c6d
package/lib/index.js CHANGED
@@ -7652,6 +7652,27 @@ function reviewPage(cwd, cursor = 0, history = false, path) {
7652
7652
  /**
7653
7653
  * @public
7654
7654
  * @author ddj 2026年10月31号
7655
+ * @description: 该会话里由官方记录判为「整文件新增」的文件身份集合。待审条目命中时基线要置空:
7656
+ * 文件是本会话新建的,累计基线只是会话自己的中间产物,拿它做旧侧会把新增文件渲染成一堆修改块。
7657
+ * @param cwd 迁移后的工作区
7658
+ * @param sessionId 会话
7659
+ * @returns 整文件新增的文件身份集合
7660
+ */
7661
+ function addedFiles(cwd, sessionId) {
7662
+ try {
7663
+ const db = dbFor(cwd);
7664
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE name='review_files' AND type='table'").get()) return /* @__PURE__ */ new Set();
7665
+ const rows = db.prepare(`SELECT identity FROM (SELECT identity,
7666
+ json_extract(diff,'$.before') AS before, ROW_NUMBER() OVER (PARTITION BY identity ORDER BY turn, seq) AS rank
7667
+ FROM review_files WHERE session_id=?) WHERE rank=1 AND before=0`).all(sessionId);
7668
+ return new Set(rows.map((row) => String(row.identity)));
7669
+ } catch {
7670
+ return /* @__PURE__ */ new Set();
7671
+ }
7672
+ }
7673
+ /**
7674
+ * @public
7675
+ * @author ddj 2026年10月31号
7655
7676
  * @description: 该文件是否还有未处理的官方待审改动。缺失文件预览据此区分「需要去差异栏 Keep 结案」
7656
7677
  * 与「记录都已处理、可直接关闭标签页」,避免已结案的删除文件继续提示待审。
7657
7678
  * @param cwd Migrated workspace
@@ -9297,10 +9318,22 @@ function capabilityOf(entry, flags) {
9297
9318
  * @author ddj 2026年10月29号
9298
9319
  * @description: 供详情接口复用的差异构造。
9299
9320
  * @param entry 待审条目
9321
+ * @param baselineEmpty 基线置空:文件由本次会话新建,旧基线只是会话自己的中间产物
9300
9322
  * @returns 官方差异
9301
9323
  */
9302
- function diffOf(entry) {
9303
- return textDiff(entry.path, entry.path, entry.oldText, entry.newText);
9324
+ function diffOf(entry, baselineEmpty = false) {
9325
+ if (!baselineEmpty) return textDiff(entry.path, entry.path, entry.oldText, entry.newText);
9326
+ if (entry.newText === "") return textDiff(entry.path, entry.path, "", "");
9327
+ const diff = textDiff(entry.path, entry.path, "", entry.newText);
9328
+ if (diff.kind !== "text") return diff;
9329
+ return {
9330
+ ...diff,
9331
+ hunks: diff.hunks.map((hunk) => ({
9332
+ ...hunk,
9333
+ oldLines: 0,
9334
+ lines: hunk.lines.filter((line) => line !== "-")
9335
+ }))
9336
+ };
9304
9337
  }
9305
9338
  //#endregion
9306
9339
  //#region src/pending/persist.ts
@@ -9560,10 +9593,11 @@ function flagsOfRow(row) {
9560
9593
  * @author ddj 2026年10月29号
9561
9594
  * @description: 统计一次差异的增删行数;非文本差异统一记 0/0。
9562
9595
  * @param entry 待审条目
9596
+ * @param baselineEmpty 基线置空(新建文件按全量新增计)
9563
9597
  * @returns 增删行数
9564
9598
  */
9565
- function countsOf(entry) {
9566
- const diff = diffOf(entry);
9599
+ function countsOf(entry, baselineEmpty = false) {
9600
+ const diff = diffOf(entry, baselineEmpty);
9567
9601
  if (diff.kind !== "text") return {
9568
9602
  added: 0,
9569
9603
  deleted: 0
@@ -9633,11 +9667,12 @@ function foldWrite(stored, write) {
9633
9667
  * @author ddj 2026年10月29号
9634
9668
  * @description: 条目转列表投影,已解决条目仍保留但不可再决定。
9635
9669
  * @param stored 条目与标记
9670
+ * @param baselineEmpty 基线置空(新建文件按全量新增计)
9636
9671
  * @returns 列表项
9637
9672
  */
9638
- function itemOf(stored) {
9673
+ function itemOf(stored, baselineEmpty = false) {
9639
9674
  const { entry, flags } = stored;
9640
- const counts = countsOf(entry);
9675
+ const counts = countsOf(entry, baselineEmpty);
9641
9676
  const item = {
9642
9677
  id: entry.id,
9643
9678
  path: entry.path,
@@ -9662,12 +9697,13 @@ function itemOf(stored) {
9662
9697
  * @description: 读一页待审条目,按更新时间升序;会话过滤走血缘集合。
9663
9698
  * @param cwd 工作区
9664
9699
  * @param sessionId 当前会话
9700
+ * @param wholeAdd 本次会话新建的文件身份集合,命中时基线置空
9665
9701
  * @returns 列表
9666
9702
  */
9667
- function loadPage(cwd, sessionId) {
9703
+ function loadPage(cwd, sessionId, wholeAdd) {
9668
9704
  const all = loadEntries(cwd);
9669
9705
  const visible = sessionId ? all.filter((stored) => stored.entry.sessionIds.includes(sessionId)) : all;
9670
- const items = visible.map(itemOf);
9706
+ const items = visible.map((stored) => itemOf(stored, wholeAdd?.has(stored.entry.id) === true));
9671
9707
  const gaps = [];
9672
9708
  for (const stored of visible) if (stored.flags.oversized) gaps.push(`${stored.entry.path}: 文本超出上限,仅保留标记`);
9673
9709
  return {
@@ -9682,14 +9718,15 @@ function loadPage(cwd, sessionId) {
9682
9718
  * @description: 读单条详情,包含可交给现有面板渲染的差异与块决定。
9683
9719
  * @param cwd 工作区
9684
9720
  * @param id 条目身份
9721
+ * @param baselineEmpty 基线置空(新建文件按全量新增渲染)
9685
9722
  * @returns 详情,条目不存在时 null
9686
9723
  */
9687
- function loadDetail(cwd, id) {
9724
+ function loadDetail(cwd, id, baselineEmpty = false) {
9688
9725
  const stored = loadEntries(cwd).find((item) => item.entry.id === id);
9689
9726
  if (!stored) return null;
9690
- const diff = diffOf(stored.entry);
9727
+ const diff = diffOf(stored.entry, baselineEmpty);
9691
9728
  return {
9692
- item: itemOf(stored),
9729
+ item: itemOf(stored, baselineEmpty),
9693
9730
  diff,
9694
9731
  blocks: blockStatesOf(stored.entry),
9695
9732
  capability: capabilityOf(stored.entry, stored.flags)
@@ -10249,7 +10286,11 @@ function pendingCovers(cwd, path) {
10249
10286
  * @returns 合并后的页
10250
10287
  */
10251
10288
  function mergedPage(official, pending, cwd) {
10252
- const covered = new Set(pending.items.map((item) => pathKey$1(item.path)));
10289
+ const covered = /* @__PURE__ */ new Set();
10290
+ for (const item of pending.items) {
10291
+ covered.add(pathKey$1(item.path));
10292
+ covered.add(pathKey$1(item.id));
10293
+ }
10253
10294
  const kept = official.items.filter((item) => !covered.has(pathKey$1(item.currentPath)) && !covered.has(pathKey$1(item.path)));
10254
10295
  return {
10255
10296
  items: [...pending.items.map((item) => pendingItemOf(item, cwd)), ...kept],
@@ -16154,7 +16195,7 @@ function reviewHandlers(ctx, registry) {
16154
16195
  */
16155
16196
  function pendingPageOf(cwd, sessionId) {
16156
16197
  try {
16157
- const page = loadPage(cwd, sessionId);
16198
+ const page = loadPage(cwd, sessionId, addedFiles(cwd, sessionId));
16158
16199
  return page.items.some((item) => item.settled === true) ? {
16159
16200
  ...page,
16160
16201
  items: page.items.filter((item) => item.settled !== true)
@@ -16201,7 +16242,8 @@ function reviewHandlers(ctx, registry) {
16201
16242
  const scope = await scopeOf(args.sessionId);
16202
16243
  const cwd = scope.cwd;
16203
16244
  if (args.id.startsWith("pending:")) {
16204
- const detail = loadDetail(cwd, args.id.slice(8));
16245
+ const entryId = args.id.slice(8);
16246
+ const detail = loadDetail(cwd, entryId, addedFiles(cwd, args.sessionId).has(entryId));
16205
16247
  if (!detail) return {
16206
16248
  ok: false,
16207
16249
  error: "待审条目不存在"
@@ -16350,7 +16392,7 @@ function readHandlers(kit) {
16350
16392
  return {
16351
16393
  "edrv.pending.list": async (args) => {
16352
16394
  const cwd = await kit.read(args.sessionId);
16353
- const page = loadPage(cwd, args.sessionId);
16395
+ const page = loadPage(cwd, args.sessionId, addedFiles(cwd, args.sessionId));
16354
16396
  for (const item of page.items) trackPendingCover(cwd, item.path, true);
16355
16397
  return {
16356
16398
  ok: true,
@@ -16358,7 +16400,9 @@ function readHandlers(kit) {
16358
16400
  };
16359
16401
  },
16360
16402
  "edrv.pending.detail": async (args) => {
16361
- const detail = loadDetail(await kit.read(args.sessionId), pendingRawId(args.id));
16403
+ const cwd = await kit.read(args.sessionId);
16404
+ const rawId = pendingRawId(args.id);
16405
+ const detail = loadDetail(cwd, rawId, addedFiles(cwd, args.sessionId).has(rawId));
16362
16406
  return detail ? {
16363
16407
  ok: true,
16364
16408
  detail