dsh-vscode-mode 0.1.1
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 +95 -0
- package/assets/compare_idle.png +0 -0
- package/assets/compare_select.png +0 -0
- package/assets/vendor/monaco/vs/abap-D-t0cyap.js +1 -0
- package/assets/vendor/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/assets/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +97 -0
- package/assets/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +30 -0
- package/assets/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +30 -0
- package/assets/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +474 -0
- package/assets/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +62 -0
- package/assets/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +67722 -0
- package/assets/vendor/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/assets/vendor/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/bat-C397hTD6.js +1 -0
- package/assets/vendor/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/assets/vendor/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/assets/vendor/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/assets/vendor/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/assets/vendor/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/assets/vendor/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/assets/vendor/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/assets/vendor/monaco/vs/css-CaeNmE3S.js +3 -0
- package/assets/vendor/monaco/vs/css.worker-CyhWkhHo.js +1 -0
- package/assets/vendor/monaco/vs/cssMode-CV6Ay48H.js +1 -0
- package/assets/vendor/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/assets/vendor/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/assets/vendor/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/assets/vendor/monaco/vs/ecl-30fUercY.js +1 -0
- package/assets/vendor/monaco/vs/editor/editor.main.css +1 -0
- package/assets/vendor/monaco/vs/editor/editor.main.js +1 -0
- package/assets/vendor/monaco/vs/editor/editor.worker.js +1 -0
- package/assets/vendor/monaco/vs/editor-KLE6jdfb.js +831 -0
- package/assets/vendor/monaco/vs/editor.js +1 -0
- package/assets/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +35 -0
- package/assets/vendor/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/assets/vendor/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/assets/vendor/monaco/vs/freemarker2-FWreY7v7.js +3 -0
- package/assets/vendor/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/assets/vendor/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/assets/vendor/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/assets/vendor/monaco/vs/handlebars-CZi4pHCH.js +1 -0
- package/assets/vendor/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/assets/vendor/monaco/vs/html-j_7ZTNRU.js +1 -0
- package/assets/vendor/monaco/vs/html.worker-CA3iAimZ.js +1 -0
- package/assets/vendor/monaco/vs/htmlMode-27by_KM4.js +1 -0
- package/assets/vendor/monaco/vs/index-049xrXrp.js +1 -0
- package/assets/vendor/monaco/vs/index-CBVt3dzv.js +5 -0
- package/assets/vendor/monaco/vs/index.js +1 -0
- package/assets/vendor/monaco/vs/ini-CsNwO04R.js +1 -0
- package/assets/vendor/monaco/vs/initialize-DL0l1TGY.js +1 -0
- package/assets/vendor/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/assets/vendor/monaco/vs/javascript-DTJl4Jn1.js +1 -0
- package/assets/vendor/monaco/vs/json.worker-BizpAl9O.js +1 -0
- package/assets/vendor/monaco/vs/jsonMode-DLUiMUrI.js +1 -0
- package/assets/vendor/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/assets/vendor/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/assets/vendor/monaco/vs/language/css/css.worker.js +66 -0
- package/assets/vendor/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/html/html.worker.js +443 -0
- package/assets/vendor/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/json/json.worker.js +17 -0
- package/assets/vendor/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/typescript/ts.worker.js +67693 -0
- package/assets/vendor/monaco/vs/less-C0eDYdqa.js +2 -0
- package/assets/vendor/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/assets/vendor/monaco/vs/liquid-CLJYelW6.js +1 -0
- package/assets/vendor/monaco/vs/loader.js +1368 -0
- package/assets/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +2 -0
- package/assets/vendor/monaco/vs/lua-DtygF91M.js +1 -0
- package/assets/vendor/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/assets/vendor/monaco/vs/main-BEx-Fmlo.js +15 -0
- package/assets/vendor/monaco/vs/main-DsK8pnKg.js +3 -0
- package/assets/vendor/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/assets/vendor/monaco/vs/mdx-CpzZIPHF.js +1 -0
- package/assets/vendor/monaco/vs/mips-CiYP61RB.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +1 -0
- package/assets/vendor/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/assets/vendor/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/assets/vendor/monaco/vs/nls/lang/zh-cn.d.ts +1 -0
- package/assets/vendor/monaco/vs/nls/lang/zh-cn.js +9 -0
- package/assets/vendor/monaco/vs/nls.messages-loader.js +1 -0
- package/assets/vendor/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/assets/vendor/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/assets/vendor/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/assets/vendor/monaco/vs/perl-DABw_TcH.js +1 -0
- package/assets/vendor/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/assets/vendor/monaco/vs/php-D_kh-9LK.js +1 -0
- package/assets/vendor/monaco/vs/pla-VfZjczW0.js +1 -0
- package/assets/vendor/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/assets/vendor/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/assets/vendor/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/assets/vendor/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/assets/vendor/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/assets/vendor/monaco/vs/python-CqWUUgfu.js +1 -0
- package/assets/vendor/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/assets/vendor/monaco/vs/r-f8dDdrp4.js +1 -0
- package/assets/vendor/monaco/vs/razor-hok5y9Km.js +1 -0
- package/assets/vendor/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/assets/vendor/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/assets/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/assets/vendor/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/assets/vendor/monaco/vs/rust-Bfetafyc.js +1 -0
- package/assets/vendor/monaco/vs/sb-3GYllVck.js +1 -0
- package/assets/vendor/monaco/vs/scala-foMgrKo1.js +1 -0
- package/assets/vendor/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/assets/vendor/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/assets/vendor/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/assets/vendor/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/assets/vendor/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/assets/vendor/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/assets/vendor/monaco/vs/sql-32GpJSV2.js +1 -0
- package/assets/vendor/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/assets/vendor/monaco/vs/swift-t-PfMj7W.js +1 -0
- package/assets/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/assets/vendor/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/assets/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +61 -0
- package/assets/vendor/monaco/vs/ts.worker-2QLmBukE.js +1 -0
- package/assets/vendor/monaco/vs/tsMode-B0S_kp2q.js +11 -0
- package/assets/vendor/monaco/vs/twig-C6taOxMV.js +1 -0
- package/assets/vendor/monaco/vs/typescript-_2t_511t.js +1 -0
- package/assets/vendor/monaco/vs/typespec-8OfoLt6R.js +1 -0
- package/assets/vendor/monaco/vs/vb-Dyb2648j.js +1 -0
- package/assets/vendor/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/assets/vendor/monaco/vs/workers-BBttULjf.js +1 -0
- package/assets/vendor/monaco/vs/xml-cZjNDqDT.js +1 -0
- package/assets/vendor/monaco/vs/yaml-A1fOIdH6.js +1 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +1914 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1298 -0
- package/lib/index.js.map +1 -0
- package/package.json +60 -0
- package/src/capture.ts +73 -0
- package/src/client/events.ts +36 -0
- package/src/client/index.ts +37 -0
- package/src/client/monaco/diffRender.ts +162 -0
- package/src/client/monaco/loader.ts +71 -0
- package/src/client/rpc.ts +28 -0
- package/src/client/state/records.ts +90 -0
- package/src/client/state/regions.ts +81 -0
- package/src/client/styles/css.d.ts +2 -0
- package/src/client/styles/editor.css +87 -0
- package/src/client/ui/DiffBadge.ts +51 -0
- package/src/client/ui/DiffBarEmpty.ts +37 -0
- package/src/client/ui/DiffBox.ts +79 -0
- package/src/client/ui/DiffLauncher.ts +124 -0
- package/src/client/ui/EditorView.ts +577 -0
- package/src/client/ui/QuickOpen.ts +91 -0
- package/src/client/ui/shared.ts +28 -0
- package/src/index.ts +43 -0
- package/src/model.ts +197 -0
- package/src/registry.ts +35 -0
- package/src/revert.ts +107 -0
- package/src/routes.ts +138 -0
- package/src/rpc.ts +295 -0
- package/src/shared/rpc.ts +67 -0
- package/src/shared/types.ts +121 -0
- package/src/store.ts +244 -0
- package/src/workspace.ts +74 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — 存储层(sidecar 读写合并 + 归档持久化 + stale 检测)。
|
|
3
|
+
* 迁移自原 src/index.ts 的 fs/ctx IO 部分,语义一字不改。
|
|
4
|
+
* 作者 ddj 2026-08-20
|
|
5
|
+
*/
|
|
6
|
+
import type { ArchiveBatch, ArchiveData, DiffRecord, SidecarData } from './shared/types.js'
|
|
7
|
+
import { archiveEntryFor, groupByBatch, normalizeRecord } from './model.js'
|
|
8
|
+
|
|
9
|
+
export const SIDECAR = '.dsh-edit-review.json'
|
|
10
|
+
export const SIDECAR_ARCHIVE = '.dsh-edit-review-archive.json'
|
|
11
|
+
export const READ_CAP = 8 * 1024 * 1024
|
|
12
|
+
|
|
13
|
+
/** DSH 上下文与会话类型较宽松:本地无 dsh 类型声明,显式 any + 文档约束。 */
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
export type Ctx = any
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
export type Session = any
|
|
18
|
+
|
|
19
|
+
/** 会话沙箱策略(policyOf):可能不存在,调用方需容忍 undefined。 */
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
export function policyOf(ctx: Ctx, session: Session | undefined): any {
|
|
22
|
+
const svc = ctx.get('sandboxPolicy')
|
|
23
|
+
if (!svc) return undefined
|
|
24
|
+
return session ? svc.resolve({ session }) : svc.resolve()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 按会话 cwd 解析目标路径。 */
|
|
28
|
+
export async function resolveTarget(ctx: Ctx, session: Session | undefined, path: string): Promise<string> {
|
|
29
|
+
const fs = ctx.get('fs')
|
|
30
|
+
const cwd = session?.header?.cwd
|
|
31
|
+
return fs.resolve(path, cwd ? { cwd } : {})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 读取工作区主 sidecar 文本(缺失/失败 → null)。 */
|
|
35
|
+
async function readSidecarText(ctx: Ctx, cwd: string | null): Promise<string | null> {
|
|
36
|
+
const fs = ctx.get('fs')
|
|
37
|
+
if (!fs || !cwd) return null
|
|
38
|
+
try {
|
|
39
|
+
return await fs.readText(await fs.resolve(SIDECAR, { cwd }))
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 解析主 sidecar:v2 直接返回;v1 迁移为 v2 结构(按会话 cwd 分桶)。
|
|
47
|
+
* @author ddj 2026年08月20号
|
|
48
|
+
* @param text sidecar 文本
|
|
49
|
+
* @returns v2 数据或 null
|
|
50
|
+
*/
|
|
51
|
+
export function parseSidecar(text: string | null): SidecarData | null {
|
|
52
|
+
if (!text) return null
|
|
53
|
+
try {
|
|
54
|
+
const data = JSON.parse(text)
|
|
55
|
+
if (data && typeof data === 'object' && data.version === 2 && data.workspaces && typeof data.workspaces === 'object') {
|
|
56
|
+
return data as SidecarData
|
|
57
|
+
}
|
|
58
|
+
if (data && data.version === 1 && data.sessions && typeof data.sessions === 'object') {
|
|
59
|
+
const workspaces: SidecarData['workspaces'] = {}
|
|
60
|
+
for (const bucket of Object.values(data.sessions)) {
|
|
61
|
+
const b = bucket as { cwd?: unknown; records?: unknown; at?: unknown }
|
|
62
|
+
if (typeof b.cwd === 'string' && b.records) {
|
|
63
|
+
workspaces[b.cwd] = { at: typeof b.at === 'number' ? b.at : Date.now(), records: b.records as Record<string, DiffRecord> }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { version: 2, updatedAt: typeof data.updatedAt === 'string' ? data.updatedAt : new Date().toISOString(), workspaces }
|
|
67
|
+
}
|
|
68
|
+
return null
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 加载某工作区的记录桶(内存 Map;缺失 → 空)。 */
|
|
75
|
+
export async function loadBucket(ctx: Ctx, cwd: string): Promise<Map<string, DiffRecord>> {
|
|
76
|
+
const text = await readSidecarText(ctx, cwd)
|
|
77
|
+
const data = parseSidecar(text)
|
|
78
|
+
const map = new Map<string, DiffRecord>()
|
|
79
|
+
if (data && data.workspaces[cwd] && typeof data.workspaces[cwd].records === 'object') {
|
|
80
|
+
for (const rec of Object.values(data.workspaces[cwd].records)) {
|
|
81
|
+
const n = normalizeRecord(rec)
|
|
82
|
+
if (n) map.set(n.callId, n)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return map
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 保存某工作区的记录桶(写前合并,不覆盖其他工作区)。 */
|
|
89
|
+
export async function saveBucket(ctx: Ctx, cwd: string, recsMap: Map<string, DiffRecord>, session?: Session): Promise<void> {
|
|
90
|
+
const fs = ctx.get('fs')
|
|
91
|
+
if (!fs || !cwd) return
|
|
92
|
+
try {
|
|
93
|
+
const target = await fs.resolve(SIDECAR, { cwd })
|
|
94
|
+
const existing = parseSidecar(await readSidecarText(ctx, cwd)) ?? { version: 2, updatedAt: '', workspaces: {} } as SidecarData
|
|
95
|
+
existing.workspaces[cwd] = { at: Date.now(), records: Object.fromEntries(recsMap) }
|
|
96
|
+
existing.updatedAt = new Date().toISOString()
|
|
97
|
+
await fs.writeText(target, JSON.stringify(existing), void 0, void 0, policyOf(ctx, session))
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('edrv saveBucket failed', error)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 读取记录目标文件内容(按 path 缓存一次;缺失/过大/失败 → null/'' 占位)。
|
|
105
|
+
* @author ddj 2026年08月20号
|
|
106
|
+
* @param cache path → 内容(null=缺失/读取失败,''=过大跳过)
|
|
107
|
+
*/
|
|
108
|
+
export async function readForCache(ctx: Ctx, session: Session, cache: Map<string, string | null>, path: string): Promise<string | null> {
|
|
109
|
+
if (cache.has(path)) return cache.get(path) ?? null
|
|
110
|
+
const fs = ctx.get('fs')
|
|
111
|
+
if (!fs) { cache.set(path, null); return null }
|
|
112
|
+
try {
|
|
113
|
+
const target = await resolveTarget(ctx, session, path)
|
|
114
|
+
const info = await fs.stat(target)
|
|
115
|
+
if (!info || info.type !== 'file') { cache.set(path, null); return null }
|
|
116
|
+
if ((info.size ?? 0) > READ_CAP) { cache.set(path, ''); return '' }
|
|
117
|
+
const content = await fs.readText(target)
|
|
118
|
+
cache.set(path, content)
|
|
119
|
+
return content
|
|
120
|
+
} catch (error) {
|
|
121
|
+
cache.set(path, null)
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 判断记录是否已无任何可操作差异(stale):待决策 hunk 的新文本在磁盘均找不到,
|
|
128
|
+
* 或新建文件已不存在,或全为空差异。满足则应由 host 自动归档。
|
|
129
|
+
* @author ddj 2026年08月20号
|
|
130
|
+
*/
|
|
131
|
+
export async function recordIsStale(ctx: Ctx, session: Session, cache: Map<string, string | null>, rec: DiffRecord): Promise<boolean> {
|
|
132
|
+
if (rec.superseded === true) return true
|
|
133
|
+
if (!Array.isArray(rec.hunks) || !rec.hunks.length) return true
|
|
134
|
+
const perHunk = Array.isArray(rec.decisions.perHunk) ? rec.decisions.perHunk : []
|
|
135
|
+
let hasLocatablePending = false
|
|
136
|
+
for (let i = 0; i < rec.hunks.length; i++) {
|
|
137
|
+
const st = perHunk.length ? perHunk[i] : rec.decisions.call
|
|
138
|
+
if (st === 'accepted' || st === 'rejected') continue
|
|
139
|
+
if (rec.create === true) {
|
|
140
|
+
const content = await readForCache(ctx, session, cache, rec.path)
|
|
141
|
+
if (content !== null) { hasLocatablePending = true; break }
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
const precise = rec.toolName === 'edit' && rec.callHunk ? rec.callHunk : rec.hunks[i]
|
|
145
|
+
const oldText = precise?.oldText ?? null
|
|
146
|
+
const newText = precise?.newText ?? null
|
|
147
|
+
if (oldText !== null && oldText === newText) continue
|
|
148
|
+
if (newText === null || newText === undefined) continue
|
|
149
|
+
const content = await readForCache(ctx, session, cache, rec.path)
|
|
150
|
+
if (content !== null && content.indexOf(newText) >= 0) { hasLocatablePending = true; break }
|
|
151
|
+
}
|
|
152
|
+
return !hasLocatablePending
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* edrv.list 全量轮询时自动清理 stale 记录:标记 superseded 并归档,
|
|
157
|
+
* 防止"幽灵差异"长期留在审查列表且无法操作。
|
|
158
|
+
* @author ddj 2026年08月20号
|
|
159
|
+
* @returns 自动归档条数
|
|
160
|
+
*/
|
|
161
|
+
export async function autoArchiveStale(ctx: Ctx, session: Session, cwd: string, bucket: Map<string, DiffRecord>): Promise<number> {
|
|
162
|
+
const fs = ctx.get('fs')
|
|
163
|
+
if (!fs) return 0
|
|
164
|
+
const cache = new Map<string, string | null>()
|
|
165
|
+
const stale: DiffRecord[] = []
|
|
166
|
+
for (const rec of bucket.values()) {
|
|
167
|
+
if (rec.archived || rec.superseded === true) continue
|
|
168
|
+
if (await recordIsStale(ctx, session, cache, rec)) stale.push(rec)
|
|
169
|
+
}
|
|
170
|
+
if (!stale.length) return 0
|
|
171
|
+
for (const r of stale) { r.superseded = true; r.at = new Date().toISOString() }
|
|
172
|
+
await archiveRecords(ctx, session, cwd, bucket, stale, '差异无法定位(已被后续修改覆盖),自动归档')
|
|
173
|
+
return stale.length
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** 读取归档 sidecar 文本(缺失/失败 → null)。 */
|
|
177
|
+
export async function readArchiveText(ctx: Ctx, cwd: string | null): Promise<string | null> {
|
|
178
|
+
const fs = ctx.get('fs')
|
|
179
|
+
if (!fs || !cwd) return null
|
|
180
|
+
try {
|
|
181
|
+
return await fs.readText(await fs.resolve(SIDECAR_ARCHIVE, { cwd }))
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** 解析归档 sidecar(损坏 → 空数组)。 */
|
|
188
|
+
export function parseArchive(text: string | null): ArchiveBatch[] {
|
|
189
|
+
if (!text) return []
|
|
190
|
+
try {
|
|
191
|
+
const data = JSON.parse(text)
|
|
192
|
+
if (data && typeof data === 'object' && Array.isArray(data.batches)) return data.batches as ArchiveBatch[]
|
|
193
|
+
} catch (error) { /* 忽略损坏 */ }
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** 追加归档条目(按 cwd+path+batch 合并,同 callId 去重)。 */
|
|
198
|
+
export async function appendArchiveEntries(ctx: Ctx, cwd: string, entries: Array<ReturnType<typeof archiveEntryFor>>, session?: Session): Promise<void> {
|
|
199
|
+
const fs = ctx.get('fs')
|
|
200
|
+
if (!fs || !cwd) return
|
|
201
|
+
try {
|
|
202
|
+
const target = await fs.resolve(SIDECAR_ARCHIVE, { cwd })
|
|
203
|
+
const existing = parseArchive(await readArchiveText(ctx, cwd))
|
|
204
|
+
for (const e of entries) {
|
|
205
|
+
const idx = existing.findIndex((x) => x.cwd === e.cwd && x.path === e.path && x.batch === e.batch)
|
|
206
|
+
if (idx >= 0) {
|
|
207
|
+
const exist = existing[idx]
|
|
208
|
+
const seen = new Set((exist.records || []).map((r) => r.callId))
|
|
209
|
+
for (const rec of e.records) {
|
|
210
|
+
if (!seen.has(String(rec.callId))) {
|
|
211
|
+
exist.records.push(rec as unknown as ArchiveBatch['records'][number])
|
|
212
|
+
seen.add(String(rec.callId))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exist.lastAt = e.at
|
|
216
|
+
exist.reason = e.reason
|
|
217
|
+
} else {
|
|
218
|
+
existing.push(e as unknown as ArchiveBatch)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const data: ArchiveData = { version: 1, updatedAt: new Date().toISOString(), batches: existing }
|
|
222
|
+
await fs.writeText(target, JSON.stringify(data), void 0, void 0, policyOf(ctx, session))
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('edrv appendArchiveEntries failed', error)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 归档记录:标记 archived、按批次写入归档、落盘工作区桶。 */
|
|
229
|
+
export async function archiveRecords(
|
|
230
|
+
ctx: Ctx,
|
|
231
|
+
session: Session,
|
|
232
|
+
cwd: string,
|
|
233
|
+
bucket: Map<string, DiffRecord>,
|
|
234
|
+
recs: DiffRecord[],
|
|
235
|
+
reason: string,
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
if (!recs.length) return
|
|
238
|
+
const fresh = recs.filter((r) => !r.archived)
|
|
239
|
+
for (const r of recs) r.archived = true
|
|
240
|
+
const entries: Array<ReturnType<typeof archiveEntryFor>> = []
|
|
241
|
+
for (const list of groupByBatch(recs).values()) entries.push(archiveEntryFor(list, cwd, reason))
|
|
242
|
+
await appendArchiveEntries(ctx, cwd, entries, session)
|
|
243
|
+
if (fresh.length) await saveBucket(ctx, cwd, bucket, session)
|
|
244
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — 工作区文件清单扫描与缓存(快速打开/搜索用)。
|
|
3
|
+
* 迁移自原 src/index.ts 的 listWorkspaceFiles,语义不改。
|
|
4
|
+
* 作者 ddj 2026-08-20
|
|
5
|
+
*/
|
|
6
|
+
import type { Ctx, Session } from './store.js'
|
|
7
|
+
import { policyOf } from './store.js'
|
|
8
|
+
|
|
9
|
+
/** 文件清单缓存 TTL 与扫描上限(避免大工作区反复全量扫描)。 */
|
|
10
|
+
const FILE_INDEX_TTL = 60_000
|
|
11
|
+
const SCAN_CAP = 6000
|
|
12
|
+
/** cwd → { at, files }(随会话销毁清理)。 */
|
|
13
|
+
const fileIndex = new Map<string, { at: number; files: string[] }>()
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 扫描工作区文件清单(快速打开/搜索用):subprocess(Windows powershell / POSIX find),
|
|
17
|
+
* 排除常见重目录,容量上限 SCAN_CAP;结果缓存(TTL FILE_INDEX_TTL)。失败返回 null。
|
|
18
|
+
* @author ddj 2026年08月20号
|
|
19
|
+
* @param ctx DSH 上下文
|
|
20
|
+
* @param session 会话(取工作区根)
|
|
21
|
+
* @param cwd 工作区目录
|
|
22
|
+
* @returns 绝对路径清单;不可用(无 subprocess/fs)或扫描失败返回 null
|
|
23
|
+
*/
|
|
24
|
+
export async function listWorkspaceFiles(ctx: Ctx, session: Session, cwd: string): Promise<string[] | null> {
|
|
25
|
+
const cached = fileIndex.get(cwd)
|
|
26
|
+
if (cached && Date.now() - cached.at < FILE_INDEX_TTL) return cached.files
|
|
27
|
+
const sub = ctx.get('subprocess')
|
|
28
|
+
const fs = ctx.get('fs')
|
|
29
|
+
if (!sub || !fs) return null
|
|
30
|
+
const policy = policyOf(ctx, session)
|
|
31
|
+
let root: string
|
|
32
|
+
try {
|
|
33
|
+
const rootTarget = await fs.resolve(policy?.workspaceRoot ?? '.', {})
|
|
34
|
+
root = fs.processPath(rootTarget)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
root = cwd
|
|
37
|
+
}
|
|
38
|
+
const lines: string[] = []
|
|
39
|
+
const run = async (argv: string[]): Promise<void> => {
|
|
40
|
+
const handle = sub.spawn({
|
|
41
|
+
argv,
|
|
42
|
+
stdio: { stdout: { maxBytes: 8 << 20 }, stderr: { maxBytes: 1 << 16 }, stdin: 'ignore' },
|
|
43
|
+
graceMs: 20000,
|
|
44
|
+
})
|
|
45
|
+
const outcome = await handle.done
|
|
46
|
+
const code = outcome?.exitCode ?? outcome?.code
|
|
47
|
+
if (code !== 0) throw new Error('scan exit ' + code)
|
|
48
|
+
const text = handle.collected?.stdout?.readFrom(0).text ?? ''
|
|
49
|
+
for (const ln of text.split(/\r?\n/)) {
|
|
50
|
+
const s = ln.trim()
|
|
51
|
+
if (s) lines.push(s)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
if (process.platform === 'win32') {
|
|
56
|
+
const cmd = 'Get-ChildItem -LiteralPath "' + String(root).replace(/"/g, '""') + '" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch \'(node_modules|\\.git|\\.tmp|\\.cache|dist|build|vendor|coverage|__pycache__)\' } | Select-Object -First ' + SCAN_CAP + ' | ForEach-Object { $_.FullName }'
|
|
57
|
+
await run(['powershell', '-NoProfile', '-NonInteractive', '-Command', cmd])
|
|
58
|
+
} else {
|
|
59
|
+
const cmd = 'find ' + JSON.stringify(root) + ' -type f -not -path \'*/node_modules/*\' -not -path \'*/.git/*\' -not -path \'*/.tmp/*\' -not -path \'*/.cache/*\' -not -path \'*/dist/*\' -not -path \'*/build/*\' -not -path \'*/vendor/*\' -not -path \'*/coverage/*\' 2>/dev/null | head -n ' + SCAN_CAP
|
|
60
|
+
await run(['/bin/sh', '-c', cmd])
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('edrv scan failed', error)
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
const files = lines.slice(0, SCAN_CAP)
|
|
67
|
+
if (files.length) fileIndex.set(cwd, { at: Date.now(), files })
|
|
68
|
+
return files
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 会话销毁时清理文件索引缓存。 */
|
|
72
|
+
export function dropFileIndex(cwd: string): void {
|
|
73
|
+
fileIndex.delete(cwd)
|
|
74
|
+
}
|