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.
Files changed (163) hide show
  1. package/README.md +95 -0
  2. package/assets/compare_idle.png +0 -0
  3. package/assets/compare_select.png +0 -0
  4. package/assets/vendor/monaco/vs/abap-D-t0cyap.js +1 -0
  5. package/assets/vendor/monaco/vs/apex-CcIm7xu6.js +1 -0
  6. package/assets/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +97 -0
  7. package/assets/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +30 -0
  8. package/assets/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +30 -0
  9. package/assets/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +474 -0
  10. package/assets/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +62 -0
  11. package/assets/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +67722 -0
  12. package/assets/vendor/monaco/vs/azcli-BA0tQDCg.js +1 -0
  13. package/assets/vendor/monaco/vs/basic-languages/monaco.contribution.js +1 -0
  14. package/assets/vendor/monaco/vs/bat-C397hTD6.js +1 -0
  15. package/assets/vendor/monaco/vs/bicep-DF5aW17k.js +2 -0
  16. package/assets/vendor/monaco/vs/cameligo-plsz8qhj.js +1 -0
  17. package/assets/vendor/monaco/vs/clojure-Y2auQMzK.js +1 -0
  18. package/assets/vendor/monaco/vs/coffee-Bu45yuWE.js +1 -0
  19. package/assets/vendor/monaco/vs/cpp-CkKPQIni.js +1 -0
  20. package/assets/vendor/monaco/vs/csharp-CX28MZyh.js +1 -0
  21. package/assets/vendor/monaco/vs/csp-D8uWnyxW.js +1 -0
  22. package/assets/vendor/monaco/vs/css-CaeNmE3S.js +3 -0
  23. package/assets/vendor/monaco/vs/css.worker-CyhWkhHo.js +1 -0
  24. package/assets/vendor/monaco/vs/cssMode-CV6Ay48H.js +1 -0
  25. package/assets/vendor/monaco/vs/cypher-DVThT8BS.js +1 -0
  26. package/assets/vendor/monaco/vs/dart-CmGfCvrO.js +1 -0
  27. package/assets/vendor/monaco/vs/dockerfile-CZqqYdch.js +1 -0
  28. package/assets/vendor/monaco/vs/ecl-30fUercY.js +1 -0
  29. package/assets/vendor/monaco/vs/editor/editor.main.css +1 -0
  30. package/assets/vendor/monaco/vs/editor/editor.main.js +1 -0
  31. package/assets/vendor/monaco/vs/editor/editor.worker.js +1 -0
  32. package/assets/vendor/monaco/vs/editor-KLE6jdfb.js +831 -0
  33. package/assets/vendor/monaco/vs/editor.js +1 -0
  34. package/assets/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +35 -0
  35. package/assets/vendor/monaco/vs/elixir-xjPaIfzF.js +1 -0
  36. package/assets/vendor/monaco/vs/flow9-DqtmStfK.js +1 -0
  37. package/assets/vendor/monaco/vs/freemarker2-FWreY7v7.js +3 -0
  38. package/assets/vendor/monaco/vs/fsharp-BOMdg4U1.js +1 -0
  39. package/assets/vendor/monaco/vs/go-D_hbi-Jt.js +1 -0
  40. package/assets/vendor/monaco/vs/graphql-CKUU4kLG.js +1 -0
  41. package/assets/vendor/monaco/vs/handlebars-CZi4pHCH.js +1 -0
  42. package/assets/vendor/monaco/vs/hcl-DTaboeZW.js +1 -0
  43. package/assets/vendor/monaco/vs/html-j_7ZTNRU.js +1 -0
  44. package/assets/vendor/monaco/vs/html.worker-CA3iAimZ.js +1 -0
  45. package/assets/vendor/monaco/vs/htmlMode-27by_KM4.js +1 -0
  46. package/assets/vendor/monaco/vs/index-049xrXrp.js +1 -0
  47. package/assets/vendor/monaco/vs/index-CBVt3dzv.js +5 -0
  48. package/assets/vendor/monaco/vs/index.js +1 -0
  49. package/assets/vendor/monaco/vs/ini-CsNwO04R.js +1 -0
  50. package/assets/vendor/monaco/vs/initialize-DL0l1TGY.js +1 -0
  51. package/assets/vendor/monaco/vs/java-CI4ZMsH9.js +1 -0
  52. package/assets/vendor/monaco/vs/javascript-DTJl4Jn1.js +1 -0
  53. package/assets/vendor/monaco/vs/json.worker-BizpAl9O.js +1 -0
  54. package/assets/vendor/monaco/vs/jsonMode-DLUiMUrI.js +1 -0
  55. package/assets/vendor/monaco/vs/julia-BwzEvaQw.js +1 -0
  56. package/assets/vendor/monaco/vs/kotlin-IUYPiTV8.js +1 -0
  57. package/assets/vendor/monaco/vs/language/css/css.worker.js +66 -0
  58. package/assets/vendor/monaco/vs/language/css/monaco.contribution.js +1 -0
  59. package/assets/vendor/monaco/vs/language/html/html.worker.js +443 -0
  60. package/assets/vendor/monaco/vs/language/html/monaco.contribution.js +1 -0
  61. package/assets/vendor/monaco/vs/language/json/json.worker.js +17 -0
  62. package/assets/vendor/monaco/vs/language/json/monaco.contribution.js +1 -0
  63. package/assets/vendor/monaco/vs/language/typescript/monaco.contribution.js +1 -0
  64. package/assets/vendor/monaco/vs/language/typescript/ts.worker.js +67693 -0
  65. package/assets/vendor/monaco/vs/less-C0eDYdqa.js +2 -0
  66. package/assets/vendor/monaco/vs/lexon-iON-Kj97.js +1 -0
  67. package/assets/vendor/monaco/vs/liquid-CLJYelW6.js +1 -0
  68. package/assets/vendor/monaco/vs/loader.js +1368 -0
  69. package/assets/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +2 -0
  70. package/assets/vendor/monaco/vs/lua-DtygF91M.js +1 -0
  71. package/assets/vendor/monaco/vs/m3-CsR4AuFi.js +1 -0
  72. package/assets/vendor/monaco/vs/main-BEx-Fmlo.js +15 -0
  73. package/assets/vendor/monaco/vs/main-DsK8pnKg.js +3 -0
  74. package/assets/vendor/monaco/vs/markdown-C_rD0bIw.js +1 -0
  75. package/assets/vendor/monaco/vs/mdx-CpzZIPHF.js +1 -0
  76. package/assets/vendor/monaco/vs/mips-CiYP61RB.js +1 -0
  77. package/assets/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +1 -0
  78. package/assets/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +1 -0
  79. package/assets/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +1 -0
  80. package/assets/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +1 -0
  81. package/assets/vendor/monaco/vs/msdax-C38-sJlp.js +1 -0
  82. package/assets/vendor/monaco/vs/mysql-CdtbpvbG.js +1 -0
  83. package/assets/vendor/monaco/vs/nls/lang/zh-cn.d.ts +1 -0
  84. package/assets/vendor/monaco/vs/nls/lang/zh-cn.js +9 -0
  85. package/assets/vendor/monaco/vs/nls.messages-loader.js +1 -0
  86. package/assets/vendor/monaco/vs/objective-c-CntZFaHX.js +1 -0
  87. package/assets/vendor/monaco/vs/pascal-r6kuqfl_.js +1 -0
  88. package/assets/vendor/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
  89. package/assets/vendor/monaco/vs/perl-DABw_TcH.js +1 -0
  90. package/assets/vendor/monaco/vs/pgsql-me_jFXeX.js +1 -0
  91. package/assets/vendor/monaco/vs/php-D_kh-9LK.js +1 -0
  92. package/assets/vendor/monaco/vs/pla-VfZjczW0.js +1 -0
  93. package/assets/vendor/monaco/vs/postiats-BBSzz8Pk.js +1 -0
  94. package/assets/vendor/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
  95. package/assets/vendor/monaco/vs/powershell-B-7ap1zc.js +1 -0
  96. package/assets/vendor/monaco/vs/protobuf-BmtuEB1A.js +2 -0
  97. package/assets/vendor/monaco/vs/pug-BRpRNeEb.js +1 -0
  98. package/assets/vendor/monaco/vs/python-CqWUUgfu.js +1 -0
  99. package/assets/vendor/monaco/vs/qsharp-BzsFaUU9.js +1 -0
  100. package/assets/vendor/monaco/vs/r-f8dDdrp4.js +1 -0
  101. package/assets/vendor/monaco/vs/razor-hok5y9Km.js +1 -0
  102. package/assets/vendor/monaco/vs/redis-fvZQY4PI.js +1 -0
  103. package/assets/vendor/monaco/vs/redshift-45Et0LQi.js +1 -0
  104. package/assets/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
  105. package/assets/vendor/monaco/vs/ruby-CZO8zYTz.js +1 -0
  106. package/assets/vendor/monaco/vs/rust-Bfetafyc.js +1 -0
  107. package/assets/vendor/monaco/vs/sb-3GYllVck.js +1 -0
  108. package/assets/vendor/monaco/vs/scala-foMgrKo1.js +1 -0
  109. package/assets/vendor/monaco/vs/scheme-CHdMtr7p.js +1 -0
  110. package/assets/vendor/monaco/vs/scss-C1cmLt9V.js +3 -0
  111. package/assets/vendor/monaco/vs/shell-ClXCKCEW.js +1 -0
  112. package/assets/vendor/monaco/vs/solidity-MZ6ExpPy.js +1 -0
  113. package/assets/vendor/monaco/vs/sophia-DWkuSsPQ.js +1 -0
  114. package/assets/vendor/monaco/vs/sparql-AUGFYSyk.js +1 -0
  115. package/assets/vendor/monaco/vs/sql-32GpJSV2.js +1 -0
  116. package/assets/vendor/monaco/vs/st-CuDFIVZ_.js +1 -0
  117. package/assets/vendor/monaco/vs/swift-t-PfMj7W.js +1 -0
  118. package/assets/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
  119. package/assets/vendor/monaco/vs/tcl-D74tq1nH.js +1 -0
  120. package/assets/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +61 -0
  121. package/assets/vendor/monaco/vs/ts.worker-2QLmBukE.js +1 -0
  122. package/assets/vendor/monaco/vs/tsMode-B0S_kp2q.js +11 -0
  123. package/assets/vendor/monaco/vs/twig-C6taOxMV.js +1 -0
  124. package/assets/vendor/monaco/vs/typescript-_2t_511t.js +1 -0
  125. package/assets/vendor/monaco/vs/typespec-8OfoLt6R.js +1 -0
  126. package/assets/vendor/monaco/vs/vb-Dyb2648j.js +1 -0
  127. package/assets/vendor/monaco/vs/wgsl-BhLXMOR0.js +298 -0
  128. package/assets/vendor/monaco/vs/workers-BBttULjf.js +1 -0
  129. package/assets/vendor/monaco/vs/xml-cZjNDqDT.js +1 -0
  130. package/assets/vendor/monaco/vs/yaml-A1fOIdH6.js +1 -0
  131. package/cordis.patch.yml +5 -0
  132. package/lib/client.js +1914 -0
  133. package/lib/client.js.map +1 -0
  134. package/lib/index.js +1298 -0
  135. package/lib/index.js.map +1 -0
  136. package/package.json +60 -0
  137. package/src/capture.ts +73 -0
  138. package/src/client/events.ts +36 -0
  139. package/src/client/index.ts +37 -0
  140. package/src/client/monaco/diffRender.ts +162 -0
  141. package/src/client/monaco/loader.ts +71 -0
  142. package/src/client/rpc.ts +28 -0
  143. package/src/client/state/records.ts +90 -0
  144. package/src/client/state/regions.ts +81 -0
  145. package/src/client/styles/css.d.ts +2 -0
  146. package/src/client/styles/editor.css +87 -0
  147. package/src/client/ui/DiffBadge.ts +51 -0
  148. package/src/client/ui/DiffBarEmpty.ts +37 -0
  149. package/src/client/ui/DiffBox.ts +79 -0
  150. package/src/client/ui/DiffLauncher.ts +124 -0
  151. package/src/client/ui/EditorView.ts +577 -0
  152. package/src/client/ui/QuickOpen.ts +91 -0
  153. package/src/client/ui/shared.ts +28 -0
  154. package/src/index.ts +43 -0
  155. package/src/model.ts +197 -0
  156. package/src/registry.ts +35 -0
  157. package/src/revert.ts +107 -0
  158. package/src/routes.ts +138 -0
  159. package/src/rpc.ts +295 -0
  160. package/src/shared/rpc.ts +67 -0
  161. package/src/shared/types.ts +121 -0
  162. package/src/store.ts +244 -0
  163. 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
+ }
@@ -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
+ }