dsh-vscode-mode 0.20.4 → 0.20.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/lib/index.js CHANGED
@@ -2409,6 +2409,17 @@ async function resolveTarget(ctx, session, path) {
2409
2409
  return fs.resolve(path, cwd ? { cwd } : {});
2410
2410
  }
2411
2411
  /**
2412
+ * 按已知工作区目录解析目标路径:跨实例只读路径在无 live 会话时使用。
2413
+ * @author ddj 2026年10月28号
2414
+ * @param ctx DSH 上下文
2415
+ * @param cwd 工作区绝对路径(live 投影或持久化 header)
2416
+ * @param path 客户端请求路径
2417
+ * @returns 解析后的目标
2418
+ */
2419
+ async function resolveTargetIn(ctx, cwd, path) {
2420
+ return ctx.get("fs").resolve(path, cwd ? { cwd } : {});
2421
+ }
2422
+ /**
2412
2423
  * 严格读取旧 sidecar:仅确认确实不存在时才允许从空记录迁移。
2413
2424
  * @author ddj 2026年09月30号
2414
2425
  * @param ctx DSH 上下文
@@ -6263,6 +6274,10 @@ function registerRoutes(ctx, config, handleRpc, onWarning) {
6263
6274
  }
6264
6275
  //#endregion
6265
6276
  //#region src/registry.ts
6277
+ /** sessionId → cwd 冷读缓存:header.cwd 建会话后不可变,故只增不改。 */
6278
+ const coldCwdCache = /* @__PURE__ */ new Map();
6279
+ /** 冷读缓存上限:超出后整表清空,避免长跑实例无限累积。 */
6280
+ const COLD_CWD_CACHE_MAX = 2048;
6266
6281
  /** 按 sessionId 取会话;缺省时仅当唯一活跃会话才返回。 */
6267
6282
  function sessionOf(ctx, sessionId) {
6268
6283
  const sessions = ctx.get("sessions");
@@ -6290,6 +6305,78 @@ function bucketOf(registry, ctx, cwd) {
6290
6305
  return map;
6291
6306
  });
6292
6307
  }
6308
+ /**
6309
+ * 解析会话作用域:优先本进程 live 会话;未命中时冷读持久化会话头取 cwd。
6310
+ * 仅供只读 RPC 使用——桌面端与 Web 端各有独立宿主,会话只在创建端 live,
6311
+ * 但审查数据按 cwd 落盘、跨实例可读;写类 RPC 必须另行要求 live 会话。
6312
+ * @author ddj 2026年10月28号
6313
+ * @param ctx DSH 上下文(可选探测 sessions 与 sessionQuery)
6314
+ * @param sessionId 目标会话 id(冷读必需;缺省时只认唯一活跃会话)
6315
+ * @returns 会话作用域,或与 requireSession 同源的错误文案
6316
+ */
6317
+ async function resolveScope(ctx, sessionId) {
6318
+ const session = sessionOf(ctx, sessionId);
6319
+ if (session) {
6320
+ const cwd = cwdOfSession(ctx, session);
6321
+ return cwd ? {
6322
+ session,
6323
+ cwd
6324
+ } : { err: SESSION_NO_WORKSPACE_ERROR };
6325
+ }
6326
+ const cwd = await coldCwdOf(ctx, sessionId);
6327
+ return cwd ? { cwd } : { err: SESSION_MISSING_ERROR };
6328
+ }
6329
+ /**
6330
+ * 冷读持久化会话头的 cwd:走 DSH sessionQuery 的持久化视图,不读事件日志、不碰写句柄。
6331
+ * @author ddj 2026年10月28号
6332
+ * @param ctx DSH 上下文
6333
+ * @param sessionId 目标会话 id
6334
+ * @returns 绝对路径;服务缺失、查无此会话或读取失败时 null(失败语义等同 live 未命中)
6335
+ */
6336
+ async function coldCwdOf(ctx, sessionId) {
6337
+ if (!sessionId) return null;
6338
+ const cached = coldCwdCache.get(sessionId);
6339
+ if (cached) return cached;
6340
+ const query = ctx?.get?.("sessionQuery");
6341
+ if (!query) return null;
6342
+ try {
6343
+ const cwd = (await findColdRecord(query, sessionId))?.header?.cwd;
6344
+ if (typeof cwd !== "string" || !cwd) return null;
6345
+ rememberColdCwd(sessionId, cwd);
6346
+ return cwd;
6347
+ } catch {
6348
+ return null;
6349
+ }
6350
+ }
6351
+ /**
6352
+ * 在 sessionQuery 的持久化会话视图里定位一条记录(优先精确过滤,其次整表列举)。
6353
+ * 两种能力都缺失时返回 null,调用方回落原有「会话不存在」。
6354
+ * @author ddj 2026年10月28号
6355
+ * @param query ctx.sessionQuery 服务(不同 DSH 版本能力不同)
6356
+ * @param sessionId 目标会话 id
6357
+ * @returns 会话记录;无此会话或能力缺失时 null
6358
+ */
6359
+ async function findColdRecord(query, sessionId) {
6360
+ const filterable = typeof query.filterSessions === "function";
6361
+ const listable = typeof query.listSessions === "function";
6362
+ if (!filterable && !listable) return null;
6363
+ const records = filterable ? await query.filterSessions([{
6364
+ kind: "id",
6365
+ values: [sessionId]
6366
+ }]) : await query.listSessions();
6367
+ if (!Array.isArray(records)) return null;
6368
+ return records.find((item) => item?.header?.id === sessionId) ?? null;
6369
+ }
6370
+ /**
6371
+ * 记录冷读结果(header.cwd 不可变,可长期复用)。
6372
+ * @author ddj 2026年10月28号
6373
+ * @param sessionId 目标会话 id
6374
+ * @param cwd 已确认的绝对工作区路径
6375
+ */
6376
+ function rememberColdCwd(sessionId, cwd) {
6377
+ if (coldCwdCache.size >= COLD_CWD_CACHE_MAX) coldCwdCache.clear();
6378
+ coldCwdCache.set(sessionId, cwd);
6379
+ }
6293
6380
  //#endregion
6294
6381
  //#region src/remoteWorkspace.ts
6295
6382
  /**
@@ -12963,15 +13050,29 @@ function finishUndo(cwd, request) {
12963
13050
  }
12964
13051
  //#endregion
12965
13052
  //#region src/reviewRpc.ts
12966
- /** @public @author ddj 2026年10月26号 @param ctx Host. @param registry Workspace cache. @returns Handlers resolving workspace from the live authenticated session, never caller cwd. */
13053
+ /**
13054
+ * @public
13055
+ * @author ddj 2026年10月26号 / 2026年10月28号
13056
+ * @description: 审查 RPC 处理器。工作区一律由 host 解析,绝不采信调用方 cwd:
13057
+ * 读路径优先 live 会话、其次冷读持久化会话头(跨实例只读);写路径仍须 live 会话。
13058
+ * @param ctx Host 上下文
13059
+ * @param registry 工作区记录桶缓存
13060
+ * @returns 审查相关处理器表
13061
+ */
12967
13062
  function reviewHandlers(ctx, registry) {
12968
- /** @private @author ddj 2026年10月26号 @param sessionId Request scope. @returns Migrated workspace, or explicit failure without fallback. */
13063
+ /** @private @author ddj 2026年10月28号 @param sessionId 请求作用域 @returns 已迁移工作区(允许跨实例冷读) */
12969
13064
  async function workspace(sessionId) {
12970
- const session = sessionOf(ctx, sessionId);
12971
- const cwd = cwdOfSession(ctx, session);
12972
- if (!session || !cwd) throw new Error("会话不存在或没有工作区");
12973
- await bucketOf(registry, ctx, cwd);
12974
- return cwd;
13065
+ const scope = await resolveScope(ctx, sessionId);
13066
+ if ("err" in scope) throw new Error("会话不存在或没有工作区");
13067
+ await bucketOf(registry, ctx, scope.cwd);
13068
+ return scope.cwd;
13069
+ }
13070
+ /** @private @author ddj 2026年10月28号 @param sessionId 请求作用域 @returns 本端 live 会话的工作区;冷读作用域一律拒绝写入 */
13071
+ async function liveWorkspace(sessionId) {
13072
+ const scope = await resolveScope(ctx, sessionId);
13073
+ if ("err" in scope || !scope.session) throw new Error("会话不存在或没有工作区");
13074
+ await bucketOf(registry, ctx, scope.cwd);
13075
+ return scope.cwd;
12975
13076
  }
12976
13077
  return {
12977
13078
  "edrv.review.list": async (args) => {
@@ -12991,7 +13092,7 @@ function reviewHandlers(ctx, registry) {
12991
13092
  };
12992
13093
  },
12993
13094
  "edrv.review.decide": async (args) => {
12994
- const cwd = await workspace(args.sessionId);
13095
+ const cwd = await liveWorkspace(args.sessionId);
12995
13096
  try {
12996
13097
  return {
12997
13098
  ok: true,
@@ -13092,7 +13193,7 @@ async function readTargetOf(ctx, sc, path) {
13092
13193
  const fs = ctx.get("fs");
13093
13194
  if (!fs) return { err: "缺少 fs" };
13094
13195
  try {
13095
- const target = await resolveTarget(ctx, sc.session, path);
13196
+ const target = await resolveTargetIn(ctx, sc.cwd, path);
13096
13197
  debugRecord(ctx, sc.cwd, "[DEBUG path.resolve] input=" + String(path ?? "") + " resolved=" + fs.processPath(target), "debug");
13097
13198
  const info = await fs.stat(target);
13098
13199
  if (!info || info.type !== "file") return {
@@ -13504,7 +13605,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13504
13605
  const start = performance.now();
13505
13606
  let staleMs = 0;
13506
13607
  let staleChecked = false;
13507
- const sc = await requireSession(ctx, args.sessionId);
13608
+ const sc = await resolveScope(ctx, args.sessionId);
13508
13609
  if ("err" in sc) return {
13509
13610
  ok: false,
13510
13611
  error: sc.err
@@ -13512,7 +13613,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13512
13613
  const bucket = await bucketOf(registry, ctx, sc.cwd);
13513
13614
  const loadMs = performance.now() - start;
13514
13615
  const want = Array.isArray(args.callIds) ? new Set(args.callIds) : null;
13515
- if (!want && args.skipStale !== true) {
13616
+ if (sc.session && !want && args.skipStale !== true) {
13516
13617
  const now = Date.now();
13517
13618
  if (now - (staleCheckedAt.get(sc.cwd) ?? 0) > STALE_CHECK_MIN_MS) {
13518
13619
  staleCheckedAt.set(sc.cwd, now);
@@ -13547,7 +13648,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13547
13648
  * @author ddj 2026年09月28号
13548
13649
  */
13549
13650
  "edrv.uiState.get": async (args) => {
13550
- const sc = await requireSession(ctx, args.sessionId);
13651
+ const sc = await resolveScope(ctx, args.sessionId);
13551
13652
  if ("err" in sc) return {
13552
13653
  ok: false,
13553
13654
  error: sc.err
@@ -13564,7 +13665,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13564
13665
  * @author ddj 2026年09月28号 / 2026年09月29号
13565
13666
  */
13566
13667
  "edrv.uiState.set": async (args) => {
13567
- const sc = await requireSession(ctx, args.sessionId);
13668
+ const sc = await resolveScope(ctx, args.sessionId);
13568
13669
  if ("err" in sc) return {
13569
13670
  ok: false,
13570
13671
  error: sc.err
@@ -13642,14 +13743,14 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13642
13743
  },
13643
13744
  /** @author ddj 2026年10月22号 @param args Read-only request. @returns Bounded official range read, or unsupported on older providers; never bypasses Host IO. */
13644
13745
  "edrv.textPage": async (args) => {
13645
- const sc = await requireSession(ctx, args.sessionId);
13746
+ const sc = await resolveScope(ctx, args.sessionId);
13646
13747
  if ("err" in sc) return {
13647
13748
  ok: false,
13648
13749
  error: sc.err,
13649
13750
  reason: "read-failed"
13650
13751
  };
13651
13752
  try {
13652
- const target = await resolveTarget(ctx, sc.session, args.path);
13753
+ const target = await resolveTargetIn(ctx, sc.cwd, args.path);
13653
13754
  const fs = ctx.get("fs");
13654
13755
  if (typeof fs?.readByteRange !== "function" || typeof fs.stat !== "function") return readTextPage(void 0, args);
13655
13756
  const targetKey = typeof target === "string" ? target : target.targetKey;
@@ -13721,7 +13822,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13721
13822
  resolvedPath: snippetTarget
13722
13823
  };
13723
13824
  }
13724
- const sc = await requireSession(ctx, args.sessionId);
13825
+ const sc = await resolveScope(ctx, args.sessionId);
13725
13826
  if ("err" in sc) return {
13726
13827
  ok: false,
13727
13828
  error: sc.err
@@ -13760,7 +13861,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13760
13861
  }
13761
13862
  },
13762
13863
  "edrv.readBinary": async (args) => {
13763
- const sc = await requireSession(ctx, args.sessionId);
13864
+ const sc = await resolveScope(ctx, args.sessionId);
13764
13865
  if ("err" in sc) return {
13765
13866
  ok: false,
13766
13867
  error: sc.err
@@ -13829,7 +13930,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13829
13930
  };
13830
13931
  },
13831
13932
  "edrv.original": async (args) => {
13832
- const sc = await requireSession(ctx, args.sessionId);
13933
+ const sc = await resolveScope(ctx, args.sessionId);
13833
13934
  if ("err" in sc) return {
13834
13935
  ok: false,
13835
13936
  error: sc.err
@@ -13840,7 +13941,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
13840
13941
  error: "缺少 fs"
13841
13942
  };
13842
13943
  try {
13843
- const target = await resolveTarget(ctx, sc.session, args.path);
13944
+ const target = await resolveTargetIn(ctx, sc.cwd, args.path);
13844
13945
  debugRecord(ctx, sc.cwd, "[DEBUG path.resolve] input=" + String(args.path ?? "") + " resolved=" + fs.processPath(target), "debug");
13845
13946
  const info = await fs.stat(target);
13846
13947
  if (!info || info.type !== "file") return {
@@ -14000,7 +14101,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
14000
14101
  }
14001
14102
  },
14002
14103
  "edrv.archiveList": async (args) => {
14003
- const sc = await requireSession(ctx, args.sessionId);
14104
+ const sc = await resolveScope(ctx, args.sessionId);
14004
14105
  if ("err" in sc) return {
14005
14106
  ok: false,
14006
14107
  error: sc.err
@@ -14022,7 +14123,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
14022
14123
  }
14023
14124
  },
14024
14125
  "edrv.archivePage": async (args) => {
14025
- const sc = await requireSession(ctx, args.sessionId);
14126
+ const sc = await resolveScope(ctx, args.sessionId);
14026
14127
  if ("err" in sc) return {
14027
14128
  ok: false,
14028
14129
  error: sc.err
@@ -14046,7 +14147,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
14046
14147
  }
14047
14148
  },
14048
14149
  "edrv.archiveRead": async (args) => {
14049
- const sc = await requireSession(ctx, args.sessionId);
14150
+ const sc = await resolveScope(ctx, args.sessionId);
14050
14151
  if ("err" in sc) return {
14051
14152
  ok: false,
14052
14153
  error: sc.err
@@ -14257,7 +14358,7 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
14257
14358
  }
14258
14359
  },
14259
14360
  "edrv.listDir": async (args) => {
14260
- const sc = await requireSession(ctx, args.sessionId);
14361
+ const sc = await resolveScope(ctx, args.sessionId);
14261
14362
  if ("err" in sc) return {
14262
14363
  ok: false,
14263
14364
  error: sc.err