dsh-vscode-mode 0.1.16 → 0.1.19
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 +17 -0
- package/lib/client.js +390 -21
- package/lib/client.js.map +1 -1
- package/lib/index.js +250 -26
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/addToConversation.ts +211 -0
- package/src/client/index.ts +16 -2
- package/src/client/styles/editor.css +6 -0
- package/src/client/styles/mcp.css +9 -0
- package/src/client/ui/EditorView.ts +168 -16
- package/src/client/ui/McpSettings.ts +32 -1
- package/src/compat.ts +3 -2
- package/src/devForm.ts +133 -0
- package/src/rpc.ts +74 -18
- package/src/shared/compat.ts +7 -0
- package/src/shared/rpc.ts +24 -2
- package/src/store.ts +6 -2
package/src/compat.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { dirname, join } from 'node:path'
|
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
import { entriesOf } from './mcp.js'
|
|
13
13
|
import { loadSettingsDeps } from './fileOpenSettings.js'
|
|
14
|
+
import { readDevForm } from './devForm.js'
|
|
14
15
|
import type { CompatAdapter, CompatReport } from './shared/compat.js'
|
|
15
16
|
import type { Ctx } from './store.js'
|
|
16
17
|
|
|
@@ -36,7 +37,7 @@ export function entryHash(id: string): string | undefined {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/** 读取随包 package.json 版本号(缺失/解析失败降级为空串)。 */
|
|
39
|
-
function pluginVersionOf(): string {
|
|
40
|
+
export function pluginVersionOf(): string {
|
|
40
41
|
try {
|
|
41
42
|
const file = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json')
|
|
42
43
|
const pkg = JSON.parse(readFileSync(file, 'utf8')) as { version?: unknown }
|
|
@@ -142,5 +143,5 @@ export async function buildReport(
|
|
|
142
143
|
for (const guard of guards) {
|
|
143
144
|
if (!guard.active && guard.note) warnings.push(guard.note)
|
|
144
145
|
}
|
|
145
|
-
return { pluginVersion: options?.version ?? pluginVersionOf(), external, guards, warnings }
|
|
146
|
+
return { pluginVersion: options?.version ?? pluginVersionOf(), external, guards, warnings, devForm: readDevForm() }
|
|
146
147
|
}
|
package/src/devForm.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — 开发形态管理(安装形态切换)。
|
|
3
|
+
* 开发形态 = profile 中以 link: 依赖 + junction 指向工作区的安装(用于插件开发);
|
|
4
|
+
* 关闭 = 改回版本依赖 + 删 junction + pnpm install 装配正式版。
|
|
5
|
+
* 切换后需重启 DSH 生效(运行中的代码仍是旧形态)。
|
|
6
|
+
* 与 compat.ts 存在模块环(devForm 用 pluginVersionOf、compat 用 readDevForm),
|
|
7
|
+
* 双方仅在函数体内交叉使用,模块求值期无依赖,ESM 安全。
|
|
8
|
+
* 作者 ddj 2026-08-24
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { dirname, join } from 'node:path'
|
|
12
|
+
import { homedir } from 'node:os'
|
|
13
|
+
import { PLUGIN_NAME, pluginVersionOf } from './compat.js'
|
|
14
|
+
import type { DevFormInfo } from './shared/compat.js'
|
|
15
|
+
import type { Ctx } from './store.js'
|
|
16
|
+
|
|
17
|
+
/** 扫描 $DSH_HOME/profiles 找到依赖本插件的 profile 目录(无 env 依赖,多 profile 安全)。 */
|
|
18
|
+
export function findProfileDir(): string | undefined {
|
|
19
|
+
const home = process.env.DSH_HOME ?? join(homedir(), '.dsh')
|
|
20
|
+
try {
|
|
21
|
+
for (const name of readdirSync(join(home, 'profiles'))) {
|
|
22
|
+
const pkgFile = join(home, 'profiles', name, 'package.json')
|
|
23
|
+
if (!existsSync(pkgFile)) continue
|
|
24
|
+
try {
|
|
25
|
+
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8')) as { dependencies?: Record<string, unknown> } | undefined
|
|
26
|
+
if (pkg?.dependencies?.[PLUGIN_NAME]) return dirname(pkgFile)
|
|
27
|
+
} catch {
|
|
28
|
+
/* 坏 manifest 跳过该 profile */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
/* profiles 目录不存在时放弃 */
|
|
33
|
+
}
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 读取当前开发形态(manifest link: 判定)。
|
|
39
|
+
* @author ddj 2026年08月24号
|
|
40
|
+
* @returns 开发形态状态
|
|
41
|
+
*/
|
|
42
|
+
export function readDevForm(): DevFormInfo {
|
|
43
|
+
const profileDir = findProfileDir()
|
|
44
|
+
if (!profileDir) return { enabled: false }
|
|
45
|
+
try {
|
|
46
|
+
const text = readFileSync(join(profileDir, 'package.json'), 'utf8').replace(/^\uFEFF/, '')
|
|
47
|
+
const pkg = JSON.parse(text) as { dependencies?: Record<string, unknown> } | undefined
|
|
48
|
+
const spec = pkg?.dependencies?.[PLUGIN_NAME]
|
|
49
|
+
if (typeof spec === 'string' && spec.startsWith('link:')) {
|
|
50
|
+
return { enabled: true, path: spec.slice('link:'.length) }
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
/* manifest 不可读视为非开发形态 */
|
|
54
|
+
}
|
|
55
|
+
return { enabled: false }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 规划切换后的 manifest 文本(纯函数,可单测;非法 JSON 抛错)。
|
|
60
|
+
* @author ddj 2026年08月24号
|
|
61
|
+
* @param text 原 manifest 文本
|
|
62
|
+
* @param enabled 目标形态(true=link:,false=版本)
|
|
63
|
+
* @param path 工作区路径(开启时必填)
|
|
64
|
+
* @param version 版本号(关闭时使用,写入 ^<version>)
|
|
65
|
+
* @returns 新 manifest 文本(无 BOM,2 空格缩进)
|
|
66
|
+
*/
|
|
67
|
+
export function planManifest(text: string, enabled: boolean, path: string, version: string): string {
|
|
68
|
+
const data = JSON.parse(text.replace(/^\uFEFF/, '')) as { dependencies?: Record<string, unknown> }
|
|
69
|
+
if (!data.dependencies || typeof data.dependencies !== 'object') {
|
|
70
|
+
throw new Error('profile package.json 缺少 dependencies 字段')
|
|
71
|
+
}
|
|
72
|
+
data.dependencies[PLUGIN_NAME] = enabled ? 'link:' + path.replace(/\\/g, '/') : '^' + version
|
|
73
|
+
return JSON.stringify(data, null, 2) + '\n'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 建立或移除 node_modules 下的开发 junction(目标不存在时开启失败)。 */
|
|
77
|
+
function applyJunction(profileDir: string, enabled: boolean, target: string): void {
|
|
78
|
+
const linkDir = join(profileDir, 'node_modules', PLUGIN_NAME)
|
|
79
|
+
if (existsSync(linkDir)) rmSync(linkDir, { recursive: true, force: true })
|
|
80
|
+
if (!enabled) return
|
|
81
|
+
if (!existsSync(target)) throw new Error('工作区路径不存在:' + target)
|
|
82
|
+
symlinkSync(target, linkDir, 'junction')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 切换开发形态:改 manifest → 建/删 junction → pnpm install(subprocess)。
|
|
87
|
+
* @author ddj 2026年08月24号
|
|
88
|
+
* @param ctx DSH host 上下文(subprocess 服务)
|
|
89
|
+
* @param enabled 目标形态
|
|
90
|
+
* @param path 工作区绝对路径(开启时必填)
|
|
91
|
+
* @returns 切换结果(restart=true 表示需重启 DSH 生效)
|
|
92
|
+
*/
|
|
93
|
+
export async function setDevForm(ctx: Ctx, enabled: boolean, path?: string): Promise<{ ok: boolean; error?: string; restart: boolean }> {
|
|
94
|
+
const profileDir = findProfileDir()
|
|
95
|
+
if (!profileDir) return { ok: false, error: '未找到依赖本插件的 profile(检查 DSH_HOME/profiles)', restart: false }
|
|
96
|
+
const current = readDevForm()
|
|
97
|
+
const target = (path ?? current.path ?? '').replace(/\\/g, '/')
|
|
98
|
+
if (enabled && !target) return { ok: false, error: '开启开发形态需要提供工作区路径(path 参数)', restart: false }
|
|
99
|
+
if (enabled === current.enabled && (!enabled || target === current.path)) {
|
|
100
|
+
return { ok: true, restart: false }
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const pkgFile = join(profileDir, 'package.json')
|
|
104
|
+
const version = pluginVersionOf()
|
|
105
|
+
if (!version) return { ok: false, error: '读取插件版本失败,无法写入版本依赖', restart: false }
|
|
106
|
+
const next = planManifest(readFileSync(pkgFile, 'utf8').replace(/^\uFEFF/, ''), enabled, target, version)
|
|
107
|
+
writeFileSync(pkgFile, next, 'utf8')
|
|
108
|
+
applyJunction(profileDir, enabled, target)
|
|
109
|
+
const installError = await runPnpmInstall(ctx, profileDir)
|
|
110
|
+
return { ok: !installError, error: installError, restart: true }
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return { ok: false, error: String(error), restart: false }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 在 profile 目录执行 pnpm install(subprocess 契约:spawn({argv,cwd,stdio,graceMs}))。 */
|
|
117
|
+
async function runPnpmInstall(ctx: Ctx, profileDir: string): Promise<string | undefined> {
|
|
118
|
+
const sub = ctx.get('subprocess')
|
|
119
|
+
if (!sub || typeof sub.spawn !== 'function') return 'manifest/junction 已更新,但缺少 subprocess:请手动在 profile 目录运行 pnpm install'
|
|
120
|
+
const handle = sub.spawn({
|
|
121
|
+
argv: ['pnpm', 'install'],
|
|
122
|
+
cwd: profileDir,
|
|
123
|
+
stdio: { stdout: { maxBytes: 1 << 16 }, stderr: { maxBytes: 1 << 16 }, stdin: 'ignore' },
|
|
124
|
+
graceMs: 120000,
|
|
125
|
+
})
|
|
126
|
+
const outcome = await handle.done
|
|
127
|
+
const code = outcome?.exitCode ?? outcome?.code
|
|
128
|
+
if (code !== 0) {
|
|
129
|
+
const stderr = handle.collected?.stderr?.readFrom(0).text ?? ''
|
|
130
|
+
return 'pnpm install 失败(exit ' + code + '):' + stderr.slice(0, 500)
|
|
131
|
+
}
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
package/src/rpc.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 新功能 = shared/rpc.ts 加方法 + 这里加一个 handler,不改其他模块。
|
|
5
5
|
* 作者 ddj 2026-08-20
|
|
6
6
|
*/
|
|
7
|
-
import type { RpcHandlerMap, RpcMethod, RpcRequestMap, RpcResult } from './shared/rpc.js'
|
|
7
|
+
import type { DecideItem, DecideResult, RpcHandlerMap, RpcMethod, RpcRequestMap, RpcResult, RpcScope } from './shared/rpc.js'
|
|
8
8
|
import type { DiffRecord, RecordView } from './shared/types.js'
|
|
9
9
|
import type { Ctx, Session } from './store.js'
|
|
10
10
|
import {
|
|
@@ -28,6 +28,7 @@ import { listMcp, refreshMcp, removeMcp, saveMcp, toggleMcp } from './mcp.js'
|
|
|
28
28
|
import { listProjects, projectRefresh, projectRemove, projectSave, projectToggle } from './mcpProject.js'
|
|
29
29
|
import { normalizeFileOpenTool, FILE_OPEN_DEFAULT, FILE_OPEN_SETTINGS_NS } from './fileOpenSettings.js'
|
|
30
30
|
import { buildReport } from './compat.js'
|
|
31
|
+
import { readDevForm, setDevForm } from './devForm.js'
|
|
31
32
|
|
|
32
33
|
/** cwd → Promise 链:串行化 debug 日志追加(fs read+write 非原子,避免并发丢行)。 */
|
|
33
34
|
const debugWriteQueues = new Map<string, Promise<void>>()
|
|
@@ -65,6 +66,52 @@ async function requireSession(ctx: Ctx, sessionId: string | undefined): Promise<
|
|
|
65
66
|
return { session, cwd }
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* 批量决策核心:一次会话/桶解析 + 逐项处理 + 统一落盘(accept/reject 单条与 decideBatch 共用)。
|
|
71
|
+
* rejected 项先做回滚,失败记入该项 error 且不改决策;本批次新增"已解决"记录一次性归档,
|
|
72
|
+
* 有任何成功项则整桶只写盘一次,避免逐条读写整个 sidecar。
|
|
73
|
+
* @author ddj 2026年08月25号
|
|
74
|
+
* @param items 决策项(按数组顺序处理,rejected 的先后即回滚顺序)
|
|
75
|
+
* @returns 逐项结果(ok 项含更新后的记录视图)
|
|
76
|
+
*/
|
|
77
|
+
async function applyDecisions(
|
|
78
|
+
ctx: Ctx,
|
|
79
|
+
session: Session,
|
|
80
|
+
cwd: string,
|
|
81
|
+
bucket: Map<string, DiffRecord>,
|
|
82
|
+
items: DecideItem[],
|
|
83
|
+
): Promise<DecideResult[]> {
|
|
84
|
+
const results: DecideResult[] = []
|
|
85
|
+
const resolved: DiffRecord[] = []
|
|
86
|
+
let changed = false
|
|
87
|
+
for (const item of items) {
|
|
88
|
+
const record = bucket.get(item.callId)
|
|
89
|
+
if (!record) {
|
|
90
|
+
results.push({ callId: item.callId, ok: false, error: '记录不存在' })
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
if (item.decision === 'rejected') {
|
|
94
|
+
const scope: RpcScope = item.scope ?? 'call'
|
|
95
|
+
const outcome = scope === 'hunk' ? await revertHunk(ctx, session, record, item.hunkIndex ?? -1) : await revertCall(ctx, session, record)
|
|
96
|
+
if (!outcome.ok) {
|
|
97
|
+
results.push({ callId: item.callId, ok: false, error: outcome.error })
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const wasResolved = recordResolved(record)
|
|
102
|
+
markDecision(record, item.scope ?? 'call', item.hunkIndex, item.decision)
|
|
103
|
+
changed = true
|
|
104
|
+
if (!wasResolved && recordResolved(record)) resolved.push(record)
|
|
105
|
+
results.push({ callId: item.callId, ok: true, record: recView(record) })
|
|
106
|
+
}
|
|
107
|
+
if (resolved.length) {
|
|
108
|
+
const reason = items.some((item) => item.decision === 'rejected') ? '已处理(回滚)' : '已处理'
|
|
109
|
+
await archiveRecords(ctx, session, cwd, bucket, resolved, reason, { deferSave: true })
|
|
110
|
+
}
|
|
111
|
+
if (changed) await saveBucket(ctx, cwd, bucket, session)
|
|
112
|
+
return results
|
|
113
|
+
}
|
|
114
|
+
|
|
68
115
|
/** 各方法 handler 表(类型由 shared/rpc 的 RpcHandlerMap 约束)。 */
|
|
69
116
|
export function buildHandlers(ctx: Ctx, registry: Registry, searcher = newSearcher(ctx)): RpcHandlerMap {
|
|
70
117
|
return {
|
|
@@ -73,7 +120,7 @@ export function buildHandlers(ctx: Ctx, registry: Registry, searcher = newSearch
|
|
|
73
120
|
if ('err' in sc) return { ok: false, error: sc.err }
|
|
74
121
|
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
75
122
|
const want = Array.isArray(args.callIds) ? new Set(args.callIds) : null
|
|
76
|
-
if (!want) await autoArchiveStale(ctx, sc.session, sc.cwd, bucket) // 全量轮询时自动清理 stale
|
|
123
|
+
if (!want && args.skipStale !== true) await autoArchiveStale(ctx, sc.session, sc.cwd, bucket) // 全量轮询时自动清理 stale 幽灵差异(批量决策后的即时刷新可 skipStale 跳过)
|
|
77
124
|
const out: RecordView[] = []
|
|
78
125
|
for (const rec of bucket.values()) {
|
|
79
126
|
// 面板全量查询过滤已归档;聊天条按 callId 查询保留(状态徽章仍需正确显示)
|
|
@@ -88,27 +135,26 @@ export function buildHandlers(ctx: Ctx, registry: Registry, searcher = newSearch
|
|
|
88
135
|
const sc = await requireSession(ctx, args.sessionId)
|
|
89
136
|
if ('err' in sc) return { ok: false, error: sc.err }
|
|
90
137
|
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
else await saveBucket(ctx, sc.cwd, bucket, sc.session)
|
|
96
|
-
return { ok: true, record: recView(record) }
|
|
138
|
+
const results = await applyDecisions(ctx, sc.session, sc.cwd, bucket, [{ callId: args.callId, scope: args.scope, hunkIndex: args.hunkIndex, decision: 'accepted' }])
|
|
139
|
+
const item = results[0]
|
|
140
|
+
if (!item?.ok) return { ok: false, error: item?.error ?? '操作失败' }
|
|
141
|
+
return { ok: true, record: item.record! }
|
|
97
142
|
},
|
|
98
143
|
'edrv.reject': async (args) => {
|
|
99
144
|
const sc = await requireSession(ctx, args.sessionId)
|
|
100
145
|
if ('err' in sc) return { ok: false, error: sc.err }
|
|
101
146
|
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
147
|
+
const results = await applyDecisions(ctx, sc.session, sc.cwd, bucket, [{ callId: args.callId, scope: args.scope, hunkIndex: args.hunkIndex, decision: 'rejected' }])
|
|
148
|
+
const item = results[0]
|
|
149
|
+
if (!item?.ok) return { ok: false, error: item?.error ?? '操作失败' }
|
|
150
|
+
return { ok: true, record: item.record! }
|
|
151
|
+
},
|
|
152
|
+
'edrv.decideBatch': async (args) => {
|
|
153
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
154
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
155
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
156
|
+
const results = await applyDecisions(ctx, sc.session, sc.cwd, bucket, Array.isArray(args.items) ? args.items : [])
|
|
157
|
+
return { ok: true, results }
|
|
112
158
|
},
|
|
113
159
|
'edrv.read': async (args) => {
|
|
114
160
|
const sc = await requireSession(ctx, args.sessionId)
|
|
@@ -324,6 +370,16 @@ export function buildHandlers(ctx: Ctx, registry: Registry, searcher = newSearch
|
|
|
324
370
|
}
|
|
325
371
|
},
|
|
326
372
|
'compat': async () => ({ ok: true, report: await buildReport(ctx) }),
|
|
373
|
+
'vscode.devFormGet': async () => ({ ok: true, devForm: readDevForm() }),
|
|
374
|
+
'vscode.devFormSet': async (args) => {
|
|
375
|
+
try {
|
|
376
|
+
const result = await setDevForm(ctx, args.enabled === true, args.path)
|
|
377
|
+
if (!result.ok) return { ok: false, error: result.error ?? '切换开发形态失败' }
|
|
378
|
+
return { ok: true, devForm: readDevForm(), restart: result.restart }
|
|
379
|
+
} catch (error) {
|
|
380
|
+
return { ok: false, error: String(error) }
|
|
381
|
+
}
|
|
382
|
+
},
|
|
327
383
|
}
|
|
328
384
|
}
|
|
329
385
|
|
package/src/shared/compat.ts
CHANGED
|
@@ -11,10 +11,17 @@ export interface CompatAdapter {
|
|
|
11
11
|
note?: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** 开发形态信息:profile 中以 link: 依赖 + junction 指向工作区的安装形态。 */
|
|
15
|
+
export interface DevFormInfo {
|
|
16
|
+
enabled: boolean
|
|
17
|
+
path?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/** 兼容性报告:host 侧构建(RPC edrv.compat 返回),client 侧补充本地适配项后展示。 */
|
|
15
21
|
export interface CompatReport {
|
|
16
22
|
pluginVersion: string
|
|
17
23
|
external: CompatAdapter[]
|
|
18
24
|
guards: CompatAdapter[]
|
|
19
25
|
warnings: string[]
|
|
26
|
+
devForm?: DevFormInfo
|
|
20
27
|
}
|
package/src/shared/rpc.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
StaleHunk,
|
|
14
14
|
} from './types.js'
|
|
15
15
|
import type { MpcConfig, MpcProject, MpcProjectSaveInput, MpcServer } from './mcp.js'
|
|
16
|
-
import type { CompatReport } from './compat.js'
|
|
16
|
+
import type { CompatReport, DevFormInfo } from './compat.js'
|
|
17
17
|
|
|
18
18
|
/** webServer 精确路由。 */
|
|
19
19
|
export const RPC_PATH = '/edrv/rpc'
|
|
@@ -21,11 +21,28 @@ export const RPC_PATH = '/edrv/rpc'
|
|
|
21
21
|
/** 决策作用域:call=整条记录,hunk=单个差异块。 */
|
|
22
22
|
export type RpcScope = 'call' | 'hunk'
|
|
23
23
|
|
|
24
|
+
/** 一条批量决策项(decideBatch 的 items 元素,与 accept/reject 单条参数同构)。 */
|
|
25
|
+
export interface DecideItem {
|
|
26
|
+
callId: string
|
|
27
|
+
scope?: RpcScope
|
|
28
|
+
hunkIndex?: number
|
|
29
|
+
decision: 'accepted' | 'rejected'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 批量决策逐项结果。 */
|
|
33
|
+
export interface DecideResult {
|
|
34
|
+
callId: string
|
|
35
|
+
ok: boolean
|
|
36
|
+
error?: string
|
|
37
|
+
record?: RecordView
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
/** 每个方法的请求参数(sessionId 为公共可选字段)。 */
|
|
25
41
|
export interface RpcRequestMap {
|
|
26
|
-
'edrv.list': { sessionId?: string; callIds?: string[] }
|
|
42
|
+
'edrv.list': { sessionId?: string; callIds?: string[]; skipStale?: boolean }
|
|
27
43
|
'edrv.accept': { sessionId?: string; callId: string; scope?: RpcScope; hunkIndex?: number }
|
|
28
44
|
'edrv.reject': { sessionId?: string; callId: string; scope?: RpcScope; hunkIndex?: number }
|
|
45
|
+
'edrv.decideBatch': { sessionId?: string; items: DecideItem[] }
|
|
29
46
|
'edrv.read': { sessionId?: string; path: string }
|
|
30
47
|
'edrv.original': { sessionId?: string; path: string }
|
|
31
48
|
'edrv.save': { sessionId?: string; path: string; content: string }
|
|
@@ -46,6 +63,8 @@ export interface RpcRequestMap {
|
|
|
46
63
|
'mcp.projectRefresh': { workspacePath: string; serverName: string }
|
|
47
64
|
'vscode.fileOpenSettingsGet': {}
|
|
48
65
|
'vscode.fileOpenSettingsUpdate': { fileOpenTool: string; expectedRevision?: number }
|
|
66
|
+
'vscode.devFormGet': {}
|
|
67
|
+
'vscode.devFormSet': { enabled: boolean; path?: string }
|
|
49
68
|
'compat': {}
|
|
50
69
|
}
|
|
51
70
|
|
|
@@ -56,6 +75,7 @@ export interface RpcOkMap {
|
|
|
56
75
|
'edrv.list': { records: RecordView[] }
|
|
57
76
|
'edrv.accept': { record: RecordView }
|
|
58
77
|
'edrv.reject': { record: RecordView }
|
|
78
|
+
'edrv.decideBatch': { results: DecideResult[] }
|
|
59
79
|
'edrv.read': { content: string; size: number }
|
|
60
80
|
'edrv.original': { content: string; size: number; stale: StaleHunk[]; fallback: boolean }
|
|
61
81
|
'edrv.save': object
|
|
@@ -76,6 +96,8 @@ export interface RpcOkMap {
|
|
|
76
96
|
'mcp.projectRefresh': { project: MpcProject }
|
|
77
97
|
'vscode.fileOpenSettingsGet': { fileOpenTool: string; revision?: number }
|
|
78
98
|
'vscode.fileOpenSettingsUpdate': { fileOpenTool: string; revision?: number }
|
|
99
|
+
'vscode.devFormGet': { devForm: DevFormInfo }
|
|
100
|
+
'vscode.devFormSet': { devForm: DevFormInfo; restart: boolean }
|
|
79
101
|
'compat': { report: CompatReport }
|
|
80
102
|
}
|
|
81
103
|
|
package/src/store.ts
CHANGED
|
@@ -223,7 +223,10 @@ export async function appendArchiveEntries(ctx: Ctx, cwd: string, entries: Array
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* 归档记录:标记 archived、按批次写入归档、落盘工作区桶。
|
|
228
|
+
* @param opts.deferSave 为 true 时只写归档、不落盘桶(由批量调用方统一 saveBucket 一次)
|
|
229
|
+
*/
|
|
227
230
|
export async function archiveRecords(
|
|
228
231
|
ctx: Ctx,
|
|
229
232
|
session: Session,
|
|
@@ -231,6 +234,7 @@ export async function archiveRecords(
|
|
|
231
234
|
bucket: Map<string, DiffRecord>,
|
|
232
235
|
recs: DiffRecord[],
|
|
233
236
|
reason: string,
|
|
237
|
+
opts?: { deferSave?: boolean },
|
|
234
238
|
): Promise<void> {
|
|
235
239
|
if (!recs.length) return
|
|
236
240
|
const fresh = recs.filter((r) => !r.archived)
|
|
@@ -238,5 +242,5 @@ export async function archiveRecords(
|
|
|
238
242
|
const entries: Array<ReturnType<typeof archiveEntryFor>> = []
|
|
239
243
|
for (const list of groupByBatch(recs).values()) entries.push(archiveEntryFor(list, cwd, reason))
|
|
240
244
|
await appendArchiveEntries(ctx, cwd, entries, session)
|
|
241
|
-
if (fresh.length) await saveBucket(ctx, cwd, bucket, session)
|
|
245
|
+
if (fresh.length && !opts?.deferSave) await saveBucket(ctx, cwd, bucket, session)
|
|
242
246
|
}
|