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/rpc.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — RPC 分发:类型化方法表替代巨型 switch。
|
|
3
|
+
* 迁移自原 src/index.ts 的 handleRpc,方法名/载荷/错误文案一字不改。
|
|
4
|
+
* 新功能 = shared/rpc.ts 加方法 + 这里加一个 handler,不改其他模块。
|
|
5
|
+
* 作者 ddj 2026-08-20
|
|
6
|
+
*/
|
|
7
|
+
import type { RpcHandlerMap, RpcMethod, RpcRequestMap, RpcResult } from './shared/rpc.js'
|
|
8
|
+
import type { DiffRecord, RecordView } from './shared/types.js'
|
|
9
|
+
import type { Ctx, Session } from './store.js'
|
|
10
|
+
import {
|
|
11
|
+
READ_CAP,
|
|
12
|
+
appendArchiveEntries,
|
|
13
|
+
archiveRecords,
|
|
14
|
+
autoArchiveStale,
|
|
15
|
+
parseArchive,
|
|
16
|
+
policyOf,
|
|
17
|
+
readArchiveText,
|
|
18
|
+
resolveTarget,
|
|
19
|
+
saveBucket,
|
|
20
|
+
} from './store.js'
|
|
21
|
+
import { archiveEntryFor, markDecision, recordResolved, reconstructOriginal } from './model.js'
|
|
22
|
+
import type { Registry } from './registry.js'
|
|
23
|
+
import { bucketOf, cwdOf, sessionOf } from './registry.js'
|
|
24
|
+
import { listWorkspaceFiles } from './workspace.js'
|
|
25
|
+
import { restoreFile, revertCall, revertHunk } from './revert.js'
|
|
26
|
+
|
|
27
|
+
/** cwd → Promise 链:串行化 debug 日志追加(fs read+write 非原子,避免并发丢行)。 */
|
|
28
|
+
const debugWriteQueues = new Map<string, Promise<void>>()
|
|
29
|
+
|
|
30
|
+
/** 记录 → 客户端视图(不含 before 全文,仅长度)。 */
|
|
31
|
+
function recView(record: DiffRecord): RecordView {
|
|
32
|
+
return {
|
|
33
|
+
callId: record.callId,
|
|
34
|
+
toolName: record.toolName,
|
|
35
|
+
path: record.path,
|
|
36
|
+
beforeLen: typeof record.before === 'string' ? record.before.length : 0,
|
|
37
|
+
create: record.create === true,
|
|
38
|
+
callHunk: record.callHunk,
|
|
39
|
+
hunks: record.hunks,
|
|
40
|
+
decisions: record.decisions,
|
|
41
|
+
note: record.note ?? null,
|
|
42
|
+
superseded: record.superseded === true,
|
|
43
|
+
at: record.at,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 会话/工作区公共前置:返回 {session,cwd} 或错误文案。 */
|
|
48
|
+
async function requireSession(ctx: Ctx, sessionId: string | undefined): Promise<
|
|
49
|
+
{ session: Session; cwd: string } | { err: string }
|
|
50
|
+
> {
|
|
51
|
+
const session = sessionOf(ctx, sessionId)
|
|
52
|
+
if (!session) return { err: '会话不存在' }
|
|
53
|
+
const cwd = cwdOf(session)
|
|
54
|
+
if (!cwd) return { err: '会话无工作区' }
|
|
55
|
+
return { session, cwd }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 各方法 handler 表(类型由 shared/rpc 的 RpcHandlerMap 约束)。 */
|
|
59
|
+
export function buildHandlers(ctx: Ctx, registry: Registry): RpcHandlerMap {
|
|
60
|
+
return {
|
|
61
|
+
'edrv.list': async (args) => {
|
|
62
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
63
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
64
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
65
|
+
const want = Array.isArray(args.callIds) ? new Set(args.callIds) : null
|
|
66
|
+
if (!want) await autoArchiveStale(ctx, sc.session, sc.cwd, bucket) // 全量轮询时自动清理 stale 幽灵差异
|
|
67
|
+
const out: RecordView[] = []
|
|
68
|
+
for (const rec of bucket.values()) {
|
|
69
|
+
// 面板全量查询过滤已归档;聊天条按 callId 查询保留(状态徽章仍需正确显示)
|
|
70
|
+
if (!want && rec.archived) continue
|
|
71
|
+
if (want && !want.has(rec.callId)) continue
|
|
72
|
+
out.push(recView(rec))
|
|
73
|
+
}
|
|
74
|
+
out.sort((a, b) => (a.at < b.at ? -1 : 1))
|
|
75
|
+
return { ok: true, records: out }
|
|
76
|
+
},
|
|
77
|
+
'edrv.accept': async (args) => {
|
|
78
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
79
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
80
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
81
|
+
const record = bucket.get(args.callId)
|
|
82
|
+
if (!record) return { ok: false, error: '记录不存在' }
|
|
83
|
+
markDecision(record, args.scope ?? 'call', args.hunkIndex, 'accepted')
|
|
84
|
+
if (recordResolved(record)) await archiveRecords(ctx, sc.session, sc.cwd, bucket, [record], '已处理')
|
|
85
|
+
else await saveBucket(ctx, sc.cwd, bucket, sc.session)
|
|
86
|
+
return { ok: true, record: recView(record) }
|
|
87
|
+
},
|
|
88
|
+
'edrv.reject': async (args) => {
|
|
89
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
90
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
91
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
92
|
+
const record = bucket.get(args.callId)
|
|
93
|
+
if (!record) return { ok: false, error: '记录不存在' }
|
|
94
|
+
const scope = args.scope ?? 'call'
|
|
95
|
+
const outcome = scope === 'hunk' ? await revertHunk(ctx, sc.session, record, args.hunkIndex ?? -1) : await revertCall(ctx, sc.session, record)
|
|
96
|
+
if (!outcome.ok) return { ok: false, error: outcome.error }
|
|
97
|
+
markDecision(record, scope, args.hunkIndex, 'rejected')
|
|
98
|
+
record.at = new Date().toISOString()
|
|
99
|
+
if (recordResolved(record)) await archiveRecords(ctx, sc.session, sc.cwd, bucket, [record], '已处理(回滚)')
|
|
100
|
+
else await saveBucket(ctx, sc.cwd, bucket, sc.session)
|
|
101
|
+
return { ok: true, record: recView(record) }
|
|
102
|
+
},
|
|
103
|
+
'edrv.read': async (args) => {
|
|
104
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
105
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
106
|
+
const fs = ctx.get('fs')
|
|
107
|
+
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
108
|
+
try {
|
|
109
|
+
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
110
|
+
const info = await fs.stat(target)
|
|
111
|
+
if (!info || info.type !== 'file') return { ok: false, error: '文件不存在' }
|
|
112
|
+
if ((info.size ?? 0) > READ_CAP) return { ok: false, error: '文件过大(>8MB),不支持整文件预览' }
|
|
113
|
+
const content = await fs.readText(target)
|
|
114
|
+
return { ok: true, content, size: content.length }
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return { ok: false, error: '读取失败:' + String(error) }
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
'edrv.original': async (args) => {
|
|
120
|
+
// 重建"本批次修改前"内容:DiffEditor 原始侧。仅反解 pending 块;
|
|
121
|
+
// 全部反解失败时回退到最早记录的整体 before(若存在)。
|
|
122
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
123
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
124
|
+
const fs = ctx.get('fs')
|
|
125
|
+
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
126
|
+
try {
|
|
127
|
+
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
128
|
+
const info = await fs.stat(target)
|
|
129
|
+
if (!info || info.type !== 'file') return { ok: false, error: '文件不存在' }
|
|
130
|
+
if ((info.size ?? 0) > READ_CAP) return { ok: false, error: '文件过大(>8MB),不支持差异重建' }
|
|
131
|
+
const content = await fs.readText(target)
|
|
132
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
133
|
+
const records = [...bucket.values()].filter((r) => r.path === args.path && !r.archived).sort((a, b) => (a.at < b.at ? -1 : 1))
|
|
134
|
+
if (!records.length) return { ok: true, content, size: content.length, stale: [], fallback: false }
|
|
135
|
+
const rebuilt = reconstructOriginal(records, content)
|
|
136
|
+
if (rebuilt.stale.length && records[0].before !== null) {
|
|
137
|
+
return { ok: true, content: records[0].before, size: records[0].before.length, stale: rebuilt.stale, fallback: true }
|
|
138
|
+
}
|
|
139
|
+
return { ok: true, content: rebuilt.content, size: rebuilt.content.length, stale: rebuilt.stale, fallback: false }
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return { ok: false, error: '重建失败:' + String(error) }
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
'edrv.save': async (args) => {
|
|
145
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
146
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
147
|
+
const fs = ctx.get('fs')
|
|
148
|
+
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
149
|
+
try {
|
|
150
|
+
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
151
|
+
await fs.writeText(target, args.content, void 0, void 0, policyOf(ctx, sc.session))
|
|
152
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
153
|
+
let changed = false
|
|
154
|
+
for (const rec of bucket.values()) {
|
|
155
|
+
if (rec.path === args.path && rec.superseded !== true) { rec.superseded = true; rec.at = new Date().toISOString(); changed = true }
|
|
156
|
+
}
|
|
157
|
+
if (changed) {
|
|
158
|
+
const done: DiffRecord[] = []
|
|
159
|
+
for (const rec of bucket.values()) if (rec.path === args.path && rec.superseded) done.push(rec)
|
|
160
|
+
if (done.length) await archiveRecords(ctx, sc.session, sc.cwd, bucket, done, '被手动编辑覆盖')
|
|
161
|
+
else await saveBucket(ctx, sc.cwd, bucket, sc.session)
|
|
162
|
+
}
|
|
163
|
+
return { ok: true }
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return { ok: false, error: '保存失败:' + String(error) }
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
'edrv.archiveList': async (args) => {
|
|
169
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
170
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
171
|
+
const batches = parseArchive(await readArchiveText(ctx, sc.cwd)).filter((b) => b.cwd === sc.cwd)
|
|
172
|
+
const entries = batches.map((b) => {
|
|
173
|
+
const recs = Array.isArray(b.records) ? b.records : []
|
|
174
|
+
const sum = recs.reduce((s, r) => {
|
|
175
|
+
const sm = r.summary || { accepted: 0, rejected: 0, pending: 0, superseded: false }
|
|
176
|
+
s.accepted += sm.accepted || 0
|
|
177
|
+
s.rejected += sm.rejected || 0
|
|
178
|
+
s.pending += sm.pending || 0
|
|
179
|
+
if (sm.superseded) s.superseded++
|
|
180
|
+
return s
|
|
181
|
+
}, { accepted: 0, rejected: 0, pending: 0, superseded: 0 })
|
|
182
|
+
return { at: b.at, lastAt: b.lastAt || b.at, path: b.path, batch: b.batch ?? null, reason: b.reason ?? null, nRecords: recs.length, summary: sum }
|
|
183
|
+
})
|
|
184
|
+
entries.sort((a, b) => (Number(b.batch ?? -1) - Number(a.batch ?? -1)) || (a.at < b.at ? 1 : -1))
|
|
185
|
+
return { ok: true, entries }
|
|
186
|
+
},
|
|
187
|
+
'edrv.archiveRead': async (args) => {
|
|
188
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
189
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
190
|
+
const batchPath = args.path
|
|
191
|
+
const batches = parseArchive(await readArchiveText(ctx, sc.cwd)).filter((b) => b.cwd === sc.cwd && (!batchPath || b.path === batchPath))
|
|
192
|
+
return { ok: true, batches }
|
|
193
|
+
},
|
|
194
|
+
'edrv.rollback': async (args) => {
|
|
195
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
196
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
197
|
+
const fs = ctx.get('fs')
|
|
198
|
+
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
199
|
+
const batch = args.batch
|
|
200
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
201
|
+
let affected: DiffRecord[] = []
|
|
202
|
+
let beforeRec: DiffRecord | null = null
|
|
203
|
+
if (batch !== undefined) {
|
|
204
|
+
const all = parseArchive(await readArchiveText(ctx, sc.cwd)).filter((b) => b.cwd === sc.cwd && b.path === args.path)
|
|
205
|
+
const recs: DiffRecord[] = []
|
|
206
|
+
for (const b of all) if (b.batch === batch) for (const r of (b.records || [])) recs.push(r as unknown as DiffRecord)
|
|
207
|
+
if (!recs.length) return { ok: false, error: '归档中找不到该批次' }
|
|
208
|
+
recs.sort((a, b2) => (a.at < b2.at ? -1 : 1))
|
|
209
|
+
beforeRec = recs[0]
|
|
210
|
+
affected = recs
|
|
211
|
+
} else {
|
|
212
|
+
for (const r of bucket.values()) if (r.path === args.path && !r.archived) affected.push(r)
|
|
213
|
+
if (!affected.length) return { ok: false, error: '该文件没有可回滚的差异' }
|
|
214
|
+
affected.sort((a, b2) => (a.at < b2.at ? -1 : 1))
|
|
215
|
+
beforeRec = affected[0]
|
|
216
|
+
}
|
|
217
|
+
const rerr = await restoreFile(ctx, sc.session, beforeRec)
|
|
218
|
+
if (rerr) return { ok: false, error: rerr }
|
|
219
|
+
if (batch === undefined) {
|
|
220
|
+
await archiveRecords(ctx, sc.session, sc.cwd, bucket, affected, '已回滚')
|
|
221
|
+
} else {
|
|
222
|
+
const all = parseArchive(await readArchiveText(ctx, sc.cwd)).filter((b) => b.cwd === sc.cwd && b.path === args.path && b.batch === batch)
|
|
223
|
+
const sumRecs = all.reduce((s, b) => s + ((b.records || []).length), 0)
|
|
224
|
+
const logRec = Object.assign({}, affected[0], { note: (affected[0].note ? affected[0].note + ';' : '') + '回滚至本批次前(批次 ' + batch + ')' })
|
|
225
|
+
await appendArchiveEntries(ctx, sc.cwd, [archiveEntryFor([logRec], sc.cwd, '已回滚(批次 ' + batch + ',' + sumRecs + ' 条)')], sc.session)
|
|
226
|
+
// 批次回滚恢复的是旧内容,文件当前活跃差异已失效,一并归档
|
|
227
|
+
const activeRecs: DiffRecord[] = []
|
|
228
|
+
for (const r of bucket.values()) if (r.path === args.path && !r.archived) activeRecs.push(r)
|
|
229
|
+
if (activeRecs.length) await archiveRecords(ctx, sc.session, sc.cwd, bucket, activeRecs, '已回滚(批次回滚覆盖)')
|
|
230
|
+
}
|
|
231
|
+
return { ok: true, path: args.path, batch: batch ?? null }
|
|
232
|
+
},
|
|
233
|
+
'edrv.debug': async (args) => {
|
|
234
|
+
// 诊断日志:client 上报 → 写入工作区旁车 .dsh-edit-review-debug.log(console 不一定落盘,文件可靠)
|
|
235
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
236
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
237
|
+
const fs = ctx.get('fs')
|
|
238
|
+
const text = String(args.text ?? '')
|
|
239
|
+
const prev = debugWriteQueues.get(sc.cwd) || Promise.resolve()
|
|
240
|
+
const task = prev.then(async () => {
|
|
241
|
+
try {
|
|
242
|
+
const target = await fs.resolve('.dsh-edit-review-debug.log', { cwd: sc.cwd })
|
|
243
|
+
const old = await fs.readText(target).catch(() => '')
|
|
244
|
+
const line = new Date().toISOString() + ' ' + text + '\n'
|
|
245
|
+
await fs.writeText(target, (old || '') + line, void 0, void 0, policyOf(ctx, sc.session))
|
|
246
|
+
} catch (e) { /* 写日志失败忽略 */ }
|
|
247
|
+
})
|
|
248
|
+
debugWriteQueues.set(sc.cwd, task)
|
|
249
|
+
await task
|
|
250
|
+
console.error('[edrv-debug] ' + text)
|
|
251
|
+
return { ok: true }
|
|
252
|
+
},
|
|
253
|
+
'edrv.searchFiles': async (args) => {
|
|
254
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
255
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
256
|
+
const query = String(args.query ?? '').trim().toLowerCase()
|
|
257
|
+
const files: string[] = []
|
|
258
|
+
if (query.length >= 2) {
|
|
259
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
260
|
+
const pool = new Set<string>()
|
|
261
|
+
// 活跃差异路径恒并入(保证差异文件一定可搜到,即使工作区扫描不可用)
|
|
262
|
+
for (const rec of bucket.values()) if (rec.path) pool.add(rec.path)
|
|
263
|
+
const listed = await listWorkspaceFiles(ctx, sc.session, sc.cwd)
|
|
264
|
+
if (listed) for (const f of listed) pool.add(f)
|
|
265
|
+
const score = (p: string): number => {
|
|
266
|
+
const pl = p.toLowerCase()
|
|
267
|
+
const base = pl.split(/[\\/]/).pop() || ''
|
|
268
|
+
if (base.includes(query)) return 0
|
|
269
|
+
if (pl.includes(query)) return 1
|
|
270
|
+
return -1
|
|
271
|
+
}
|
|
272
|
+
const hit = [...pool].filter((p) => score(p) >= 0)
|
|
273
|
+
hit.sort((a, b) => (score(a) - score(b)) || (a < b ? -1 : 1))
|
|
274
|
+
files.push(...hit.slice(0, 50))
|
|
275
|
+
}
|
|
276
|
+
return { ok: true, files, truncated: files.length >= 50 }
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 统一入口:按方法分发到 handler 表。
|
|
283
|
+
* @author ddj 2026年08月20号
|
|
284
|
+
*/
|
|
285
|
+
export async function handleRpc<M extends RpcMethod>(
|
|
286
|
+
ctx: Ctx,
|
|
287
|
+
registry: Registry,
|
|
288
|
+
method: M,
|
|
289
|
+
args: RpcRequestMap[M],
|
|
290
|
+
): Promise<RpcResult<M>> {
|
|
291
|
+
const handlers = buildHandlers(ctx, registry)
|
|
292
|
+
const handler = handlers[method]
|
|
293
|
+
if (!handler) return { ok: false, error: '未知方法: ' + String(method) } as RpcResult<M>
|
|
294
|
+
return handler(args)
|
|
295
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode RPC contract — method names, per-method request/ok payloads,
|
|
3
|
+
* and the typed handler map shared by the host dispatcher and the client
|
|
4
|
+
* fetch wrapper. 载荷形状与历史实现一字不差,仅把隐式约定类型化。
|
|
5
|
+
*
|
|
6
|
+
* Purity rule: no node/react imports (same as types.ts).
|
|
7
|
+
* 作者 ddj 2026-08-20
|
|
8
|
+
*/
|
|
9
|
+
import type {
|
|
10
|
+
ArchiveBatch,
|
|
11
|
+
ArchiveEntry,
|
|
12
|
+
RecordView,
|
|
13
|
+
StaleHunk,
|
|
14
|
+
} from './types.js'
|
|
15
|
+
|
|
16
|
+
/** webServer 精确路由。 */
|
|
17
|
+
export const RPC_PATH = '/edrv/rpc'
|
|
18
|
+
|
|
19
|
+
/** 决策作用域:call=整条记录,hunk=单个差异块。 */
|
|
20
|
+
export type RpcScope = 'call' | 'hunk'
|
|
21
|
+
|
|
22
|
+
/** 每个方法的请求参数(sessionId 为公共可选字段)。 */
|
|
23
|
+
export interface RpcRequestMap {
|
|
24
|
+
'edrv.list': { sessionId?: string; callIds?: string[] }
|
|
25
|
+
'edrv.accept': { sessionId?: string; callId: string; scope?: RpcScope; hunkIndex?: number }
|
|
26
|
+
'edrv.reject': { sessionId?: string; callId: string; scope?: RpcScope; hunkIndex?: number }
|
|
27
|
+
'edrv.read': { sessionId?: string; path: string }
|
|
28
|
+
'edrv.original': { sessionId?: string; path: string }
|
|
29
|
+
'edrv.save': { sessionId?: string; path: string; content: string }
|
|
30
|
+
'edrv.archiveList': { sessionId?: string }
|
|
31
|
+
'edrv.archiveRead': { sessionId?: string; path?: string }
|
|
32
|
+
'edrv.rollback': { sessionId?: string; path: string; batch?: number }
|
|
33
|
+
'edrv.debug': { sessionId?: string; text: string }
|
|
34
|
+
'edrv.searchFiles': { sessionId?: string; query: string }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RpcMethod = keyof RpcRequestMap
|
|
38
|
+
|
|
39
|
+
/** 每个方法成功(ok:true)时的附加载荷。 */
|
|
40
|
+
export interface RpcOkMap {
|
|
41
|
+
'edrv.list': { records: RecordView[] }
|
|
42
|
+
'edrv.accept': { record: RecordView }
|
|
43
|
+
'edrv.reject': { record: RecordView }
|
|
44
|
+
'edrv.read': { content: string; size: number }
|
|
45
|
+
'edrv.original': { content: string; size: number; stale: StaleHunk[]; fallback: boolean }
|
|
46
|
+
'edrv.save': object
|
|
47
|
+
'edrv.archiveList': { entries: ArchiveEntry[] }
|
|
48
|
+
'edrv.archiveRead': { batches: ArchiveBatch[] }
|
|
49
|
+
'edrv.rollback': { path: string; batch: number | null }
|
|
50
|
+
'edrv.debug': object
|
|
51
|
+
'edrv.searchFiles': { files: string[]; truncated: boolean }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 统一响应:{ok:true, ...payload} 或 {ok:false, error}。 */
|
|
55
|
+
export type RpcResult<M extends RpcMethod> =
|
|
56
|
+
| ({ ok: true } & RpcOkMap[M])
|
|
57
|
+
| { ok: false; error: string }
|
|
58
|
+
|
|
59
|
+
/** 每个方法的 handler 签名(host 分发表与 client 类型检查共用)。 */
|
|
60
|
+
export type RpcHandler<M extends RpcMethod> = (
|
|
61
|
+
args: RpcRequestMap[M],
|
|
62
|
+
) => Promise<RpcResult<M>>
|
|
63
|
+
|
|
64
|
+
/** host RPC 分发表:method → handler。 */
|
|
65
|
+
export type RpcHandlerMap = {
|
|
66
|
+
[M in RpcMethod]: RpcHandler<M>
|
|
67
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode shared contract — the single source of truth for the
|
|
3
|
+
* host↔client data shapes that cross the /edrv/rpc boundary.
|
|
4
|
+
*
|
|
5
|
+
* Purity rule: this module MUST NOT import node or react (or anything that
|
|
6
|
+
* pulls them). It is consumed by the host (tsc/tsdown node build) and bundled
|
|
7
|
+
* into the browser client (type-only imports are erased at build time).
|
|
8
|
+
* 作者 ddj 2026-08-20
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 单条差异块的决策状态。 */
|
|
12
|
+
export type Decision = 'pending' | 'accepted' | 'rejected'
|
|
13
|
+
|
|
14
|
+
/** 一条 hunk:oldText=null 表示纯新增(create/写入场景)。 */
|
|
15
|
+
export interface Hunk {
|
|
16
|
+
oldText: string | null
|
|
17
|
+
newText: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 单条 edit 调用的精确 old/new 串(edit 工具参数原样)。 */
|
|
21
|
+
export interface CallHunk {
|
|
22
|
+
oldText: string
|
|
23
|
+
newText: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 记录级 + 逐 hunk 决策。 */
|
|
27
|
+
export interface Decisions {
|
|
28
|
+
call: Decision
|
|
29
|
+
perHunk: Decision[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 审查记录(host 内部存储与传给 client 的视图共用核心字段)。 */
|
|
33
|
+
export interface RecordBase {
|
|
34
|
+
callId: string
|
|
35
|
+
toolName: 'edit' | 'write'
|
|
36
|
+
path: string
|
|
37
|
+
create: boolean
|
|
38
|
+
callHunk: CallHunk | null
|
|
39
|
+
hunks: Hunk[]
|
|
40
|
+
decisions: Decisions
|
|
41
|
+
note: string | null
|
|
42
|
+
superseded: boolean
|
|
43
|
+
at: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** host 内部完整记录(含持久化专用字段)。 */
|
|
47
|
+
export interface DiffRecord extends RecordBase {
|
|
48
|
+
before: string | null
|
|
49
|
+
archived: boolean
|
|
50
|
+
batch: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 发往 client 的记录视图(不含 before 全文,仅长度)。 */
|
|
54
|
+
export interface RecordView extends RecordBase {
|
|
55
|
+
beforeLen: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 归档条目内单条记录的存档视图。 */
|
|
59
|
+
export interface ArchiveRecord extends RecordBase {
|
|
60
|
+
before: string | null
|
|
61
|
+
batch: number | null
|
|
62
|
+
summary: RecordSummary
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 单条记录的决策摘要。 */
|
|
66
|
+
export interface RecordSummary {
|
|
67
|
+
accepted: number
|
|
68
|
+
rejected: number
|
|
69
|
+
pending: number
|
|
70
|
+
superseded: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 归档批次聚合摘要(archiveList 轻量视图用;superseded 为被覆盖条目计数)。 */
|
|
74
|
+
export interface ArchiveSummary {
|
|
75
|
+
accepted: number
|
|
76
|
+
rejected: number
|
|
77
|
+
pending: number
|
|
78
|
+
superseded: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 归档批次(sidecar .dsh-edit-review-archive.json 内一条)。 */
|
|
82
|
+
export interface ArchiveBatch {
|
|
83
|
+
at: string
|
|
84
|
+
lastAt?: string
|
|
85
|
+
cwd: string
|
|
86
|
+
path: string
|
|
87
|
+
batch: number | null
|
|
88
|
+
reason: string
|
|
89
|
+
records: ArchiveRecord[]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 归档列表条目(edrv.archiveList 返回的轻量视图)。 */
|
|
93
|
+
export interface ArchiveEntry {
|
|
94
|
+
at: string
|
|
95
|
+
lastAt: string
|
|
96
|
+
path: string
|
|
97
|
+
batch: number | null
|
|
98
|
+
reason: string | null
|
|
99
|
+
nRecords: number
|
|
100
|
+
summary: ArchiveSummary
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** edrv.original 重建结果中的 stale 差异定位。 */
|
|
104
|
+
export interface StaleHunk {
|
|
105
|
+
callId: string
|
|
106
|
+
idx: number
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** sidecar 主文件 v2 结构(.dsh-edit-review.json)。 */
|
|
110
|
+
export interface SidecarData {
|
|
111
|
+
version: 2
|
|
112
|
+
updatedAt: string
|
|
113
|
+
workspaces: Record<string, { at: number; records: Record<string, DiffRecord> }>
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 归档 sidecar v1 结构(.dsh-edit-review-archive.json)。 */
|
|
117
|
+
export interface ArchiveData {
|
|
118
|
+
version: 1
|
|
119
|
+
updatedAt: string
|
|
120
|
+
batches: ArchiveBatch[]
|
|
121
|
+
}
|