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/README.md +5 -4
- package/lib/client.js +6 -3
- package/lib/client.js.map +1 -1
- package/lib/index.js +124 -23
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sessionError.ts +6 -3
- package/src/registry.ts +98 -0
- package/src/reviewRpc.ts +25 -9
- package/src/rpc.ts +20 -18
- package/src/store.ts +13 -0
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
|
-
/**
|
|
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月
|
|
13063
|
+
/** @private @author ddj 2026年10月28号 @param sessionId 请求作用域 @returns 已迁移工作区(允许跨实例冷读) */
|
|
12969
13064
|
async function workspace(sessionId) {
|
|
12970
|
-
const
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
14361
|
+
const sc = await resolveScope(ctx, args.sessionId);
|
|
14261
14362
|
if ("err" in sc) return {
|
|
14262
14363
|
ok: false,
|
|
14263
14364
|
error: sc.err
|