dsh-vscode-mode 0.16.0 → 0.17.0
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 +13 -9
- package/lib/client.js +376 -84
- package/lib/client.js.map +1 -1
- package/lib/index.js +4391 -4350
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -2
- package/locale/zh.json +4 -2
- package/package.json +4 -2
- package/src/capture.ts +2 -2
- package/src/client/compat.ts +2 -1
- package/src/client/index.ts +22 -7
- package/src/client/localeClient.ts +97 -0
- package/src/client/officialSidebar.ts +4 -1
- package/src/client/state/recordsFeed.ts +241 -0
- package/src/client/ui/ConversationDiffDock.ts +10 -19
- package/src/client/ui/DiffBadge.ts +12 -19
- package/src/client/ui/EditorView.ts +38 -33
- package/src/client/ui/PerfSizeBadge.ts +7 -4
- package/src/client/ui/PluginDetailSection.ts +82 -0
- package/src/dshVersion.ts +4 -2
- package/src/fileVersions.ts +2 -2
- package/src/lsp/rpc.ts +2 -2
- package/src/mcpRuntime.ts +2 -1
- package/src/registry.ts +15 -2
- package/src/rpc.ts +2 -2
- package/src/rules.ts +9 -4
- package/src/search/fallback.ts +2 -2
- package/src/search/ripgrep.ts +2 -2
- package/src/store.ts +26 -1
- package/src/svn.ts +2 -2
|
@@ -19,6 +19,7 @@ import { useOpenReceipt } from '../useOpenReceipt.js'
|
|
|
19
19
|
import { acceptsLoad, receiptErrorFor, receiptPath, receiptReadyFor, receiptTabPath } from '../receiptState.js'
|
|
20
20
|
import { bindPanelCommand } from '../panelCommands.js'
|
|
21
21
|
import { dbg, isDebug, rpc } from '../rpc.js'
|
|
22
|
+
import { refreshFeed, subscribeErrors, subscribeRecords } from '../state/recordsFeed.js'
|
|
22
23
|
import { emitFileChanged, emitRefresh } from '../events.js'
|
|
23
24
|
import { langOf, loadMonaco, snippetLanguageOf } from '../monaco/loader.js'
|
|
24
25
|
import { bindEditActions, runEditAction } from '../monaco/editActions.js'
|
|
@@ -870,38 +871,43 @@ export function EditorView(props) {
|
|
|
870
871
|
return true
|
|
871
872
|
}
|
|
872
873
|
|
|
873
|
-
|
|
874
|
-
|
|
874
|
+
/**
|
|
875
|
+
* 把共享订阅拿到的记录落进组件状态(内容一致时保持引用不变,避免整树重渲染)。
|
|
876
|
+
* @private @author ddj 2026年10月19号
|
|
877
|
+
* @param list 差异记录数组
|
|
878
|
+
*/
|
|
879
|
+
const applyRecords = (list) => {
|
|
875
880
|
// #region debug log
|
|
876
881
|
const trace = isDebug()
|
|
877
|
-
const
|
|
882
|
+
const compareStart = trace ? performance.now() : 0
|
|
878
883
|
// #endregion
|
|
879
|
-
|
|
884
|
+
const map = {}
|
|
885
|
+
for (const r of list) {
|
|
886
|
+
if (r.archived === true) continue
|
|
887
|
+
map[r.callId] = r
|
|
888
|
+
}
|
|
889
|
+
setRecords((prev) => {
|
|
890
|
+
const equal = sameRecords(prev, map)
|
|
880
891
|
// #region debug log
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (r.archived === true) continue
|
|
887
|
-
map[r.callId] = r
|
|
892
|
+
if (trace) {
|
|
893
|
+
try {
|
|
894
|
+
const compareMs = performance.now() - compareStart
|
|
895
|
+
if (compareMs > 20) dbg(sessionId, `[DEBUG diffList] count=${list.length} compareMs=${compareMs.toFixed(1)} changed=${!equal}`)
|
|
896
|
+
} catch (e) { /* 诊断不可影响轮询 */ }
|
|
888
897
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return equal ? prev : map
|
|
903
|
-
})
|
|
904
|
-
}).catch((e) => setError('list异常:' + String(e)))
|
|
898
|
+
// #endregion
|
|
899
|
+
return equal ? prev : map
|
|
900
|
+
})
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* 主动刷新一次差异记录(走会话级共享通道,同会话多消费方共享一次 RPC)。
|
|
905
|
+
* @private @author ddj 2026年10月19号
|
|
906
|
+
* @param skipStale 是否跳过过期判定
|
|
907
|
+
*/
|
|
908
|
+
const refreshRecords = (skipStale) => {
|
|
909
|
+
if (!sessionId) return
|
|
910
|
+
void refreshFeed(sessionId, skipStale === true)
|
|
905
911
|
}
|
|
906
912
|
|
|
907
913
|
/**
|
|
@@ -1185,14 +1191,13 @@ export function EditorView(props) {
|
|
|
1185
1191
|
})
|
|
1186
1192
|
}
|
|
1187
1193
|
|
|
1188
|
-
//
|
|
1194
|
+
// 挂载:接入会话级共享订阅(单定时器由 recordsFeed 持有,本组件不再自建 interval)
|
|
1189
1195
|
React.useEffect(() => {
|
|
1190
|
-
if (!sessionId) return
|
|
1196
|
+
if (!sessionId) return undefined
|
|
1197
|
+
const offRecords = subscribeRecords(sessionId, applyRecords)
|
|
1198
|
+
const offErrors = subscribeErrors(sessionId, (message) => setError('list异常:' + message))
|
|
1191
1199
|
refreshRecords()
|
|
1192
|
-
|
|
1193
|
-
const onRefresh = () => refreshRecords()
|
|
1194
|
-
window.addEventListener('edrv:refresh', onRefresh)
|
|
1195
|
-
return () => { clearInterval(t); window.removeEventListener('edrv:refresh', onRefresh) }
|
|
1200
|
+
return () => { offRecords(); offErrors() }
|
|
1196
1201
|
}, [sessionId])
|
|
1197
1202
|
|
|
1198
1203
|
/**
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
3
|
* dsh-vscode-mode client — PerfSizeBadge:对话 header 会话体积指示器。
|
|
4
|
-
*
|
|
4
|
+
* 定时器复用 state/recordsFeed.ts 的会话级心跳(单会话只保留 1 个定时器),
|
|
5
|
+
* 轮询当前会话持久化体积(host 仅 stat,近零成本),≥1MB 显示、超阈值变色,
|
|
5
6
|
* 引导"一次任务一个短会话":会话过大时建议 /compact 或新开会话。
|
|
6
7
|
* 作者 ddj 2026-09-02
|
|
7
8
|
*/
|
|
8
9
|
import React from 'react'
|
|
9
10
|
import { rpc } from '../rpc.js'
|
|
11
|
+
import { subscribeTick } from '../state/recordsFeed.js'
|
|
10
12
|
|
|
11
13
|
/** 软阈值:≥ 此值琥珀色(默认 2MB)。 */
|
|
12
14
|
const SOFT = 2 * 1024 * 1024
|
|
@@ -38,10 +40,11 @@ export function PerfSizeBadge(props) {
|
|
|
38
40
|
}, [sessionId, sessions])
|
|
39
41
|
|
|
40
42
|
React.useEffect(() => {
|
|
43
|
+
if (!sessionId) return undefined
|
|
41
44
|
load()
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
}, [load])
|
|
45
|
+
// 共享心跳:recordsFeed 每周期回调一次,避免本组件再自建 setInterval。
|
|
46
|
+
return subscribeTick(sessionId, load)
|
|
47
|
+
}, [load, sessionId])
|
|
45
48
|
|
|
46
49
|
if (!size || size < SHOW_MIN) return null
|
|
47
50
|
const label = size >= 1024 * 1024 ? (size / (1024 * 1024)).toFixed(1) + 'MB' : Math.round(size / 1024) + 'KB'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 官方插件管理器详情页的只读状态段。
|
|
4
|
+
* 挂在 DSH 0.2.1 新增的 `plugins.detail.section`(list / root)扩展位上,
|
|
5
|
+
* 只在「本插件自己」的详情页渲染,其余插件页返回 null。
|
|
6
|
+
* 作者 ddj 2026年10月19号
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import { rpc } from '../rpc.js'
|
|
10
|
+
|
|
11
|
+
/** 本插件标识(与 package.json name / bundle 行 id 一致)。 */
|
|
12
|
+
export const SELF_ID = 'dsh-vscode-mode'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 判断详情页 subject 是否指向本插件。
|
|
16
|
+
* 官方有两种形状:官方内置项 `{kind:'item', id}`、安装包 `{kind:'bundle', pkg:{name}}`。
|
|
17
|
+
* @author ddj 2026年10月19号
|
|
18
|
+
* @param subject 详情页主题
|
|
19
|
+
* @returns 是否为本插件
|
|
20
|
+
*/
|
|
21
|
+
export function isSelfSubject(subject) {
|
|
22
|
+
if (!subject || typeof subject !== 'object') return false
|
|
23
|
+
if (subject.kind === 'item') return subject.id === SELF_ID
|
|
24
|
+
if (subject.kind === 'bundle') return subject.pkg?.name === SELF_ID
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 把适配项列表压成「正常 / 未生效」计数文案。
|
|
30
|
+
* @author ddj 2026年10月19号
|
|
31
|
+
* @param list 适配项数组
|
|
32
|
+
* @returns 计数文案
|
|
33
|
+
*/
|
|
34
|
+
function countText(list) {
|
|
35
|
+
const items = Array.isArray(list) ? list : []
|
|
36
|
+
const ok = items.filter((item) => item?.active === true).length
|
|
37
|
+
return ok + ' / ' + items.length + ' 项生效'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 插件详情页的只读状态段。
|
|
42
|
+
* @author ddj 2026年10月19号
|
|
43
|
+
* @param props DSH 注入的 { subject }
|
|
44
|
+
* @returns 状态段元素或 null(非本插件页)
|
|
45
|
+
*/
|
|
46
|
+
export function PluginDetailSection(props) {
|
|
47
|
+
const subject = props?.subject
|
|
48
|
+
const isSelf = isSelfSubject(subject)
|
|
49
|
+
const [report, setReport] = React.useState(null)
|
|
50
|
+
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
if (!isSelf) return undefined
|
|
53
|
+
let alive = true
|
|
54
|
+
rpc('compat', {}).then((result) => {
|
|
55
|
+
if (!alive || !result || !result.ok) return
|
|
56
|
+
setReport(result.report ?? null)
|
|
57
|
+
}).catch(() => { /* 状态段属锦上添花,取不到就不显示 */ })
|
|
58
|
+
return () => { alive = false }
|
|
59
|
+
}, [isSelf])
|
|
60
|
+
|
|
61
|
+
if (!isSelf) return null
|
|
62
|
+
|
|
63
|
+
const row = (name, note) => React.createElement('div', { key: name, className: 'vsm-compat-item ok' },
|
|
64
|
+
React.createElement('span', { className: 'vsm-compat-name' }, name),
|
|
65
|
+
React.createElement('span', { className: 'vsm-compat-note' }, note),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const versionText = report?.dshVersion ? 'DSH ' + report.dshVersion : '未探测到 DSH 版本'
|
|
69
|
+
const pluginText = report?.pluginVersion ? '插件 ' + report.pluginVersion : '插件版本未上报'
|
|
70
|
+
const warnCount = Array.isArray(report?.warnings) ? report.warnings.length : 0
|
|
71
|
+
|
|
72
|
+
return React.createElement('section', { className: 'vsm-panel', 'data-edrv-plugin-status': '1' },
|
|
73
|
+
React.createElement('h3', { className: 'vsm-panel-title' }, 'VSCodeMode 运行状态'),
|
|
74
|
+
React.createElement('div', { className: 'vsm-panel-body' },
|
|
75
|
+
row('版本', versionText + ' · ' + pluginText),
|
|
76
|
+
row('版本适配', countText(report?.adapters)),
|
|
77
|
+
row('外部插件与服务', countText(report?.external)),
|
|
78
|
+
row('护栏', countText(report?.guards)),
|
|
79
|
+
row('兼容性警告', warnCount ? warnCount + ' 条(设置页「兼容性」可查看详情)' : '未检测到'),
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
}
|
package/src/dshVersion.ts
CHANGED
|
@@ -84,14 +84,16 @@ export function inDshRange(version: DshVersion | null | undefined, range: DshRan
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* 版本线标签:报告与文档展示版本归属;不可解析返回 '未知'。
|
|
87
|
-
* 逐线自新到旧匹配,先命中先返回;仅已静态审计的 0.2.0-rc.1/rc.2 精确标记。
|
|
88
|
-
* @author ddj 2026年09月02号 / 2026年09月18号 / 2026年09月22号 / 2026年09月29号 / 2026年10月08号
|
|
87
|
+
* 逐线自新到旧匹配,先命中先返回;仅已静态审计的 0.2.0-rc.1/rc.2、0.2.1-rc.2 精确标记。
|
|
88
|
+
* @author ddj 2026年09月02号 / 2026年09月18号 / 2026年09月22号 / 2026年09月29号 / 2026年10月08号 / 2026年10月19号
|
|
89
89
|
* @param input 版本串(如 '0.1.6-alpha.2')
|
|
90
90
|
* @returns 版本线标签
|
|
91
91
|
*/
|
|
92
92
|
export function familyLabel(input: string): string {
|
|
93
93
|
const version = parseDshVersion(input)
|
|
94
94
|
if (!version) return '未知'
|
|
95
|
+
if (inDshRange(version, { from: '0.2.1-rc.2', to: '0.2.1-rc.2' })) return '0.2.1-rc.2(设置=profile Config;workingDirectory 投影跟随 cd;plugins.detail 扩展位;按能力探测)'
|
|
96
|
+
if (inDshRange(version, { from: '0.2.1-0', to: '0.2.1' })) return '0.2.1 线(rc.2 已逐版适配,其余按能力探测)'
|
|
95
97
|
if (inDshRange(version, { from: '0.2.0-rc.2', to: '0.2.0-rc.2' })) return '0.2.0-rc.2(设置=profile Config;按能力探测)'
|
|
96
98
|
if (inDshRange(version, { from: '0.2.0-rc.1', to: '0.2.0-rc.1' })) return '0.2.0-rc.1(设置=profile Config;按能力探测)'
|
|
97
99
|
if (inDshRange(version, { from: '0.2.0-0' })) return '0.2.0 及更新(除 rc.1/rc.2 外未经逐版适配;按能力探测)'
|
package/src/fileVersions.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { FileVersionItem } from './shared/rpc.js'
|
|
22
22
|
import type { Ctx } from './store.js'
|
|
23
|
-
import {
|
|
23
|
+
import { cwdOfSession, sessionOf } from './registry.js'
|
|
24
24
|
import { invalidateIndex } from './treeIndex.js'
|
|
25
25
|
import { log } from './log.js'
|
|
26
26
|
|
|
@@ -150,7 +150,7 @@ export function createFileVersions(ctx: Ctx): FileVersions {
|
|
|
150
150
|
.slice(0, MAX_PATHS)
|
|
151
151
|
if (!fs || !paths.length) return { ok: true, items: [] }
|
|
152
152
|
const session = sessionOf(ctx, args.sessionId)
|
|
153
|
-
const cwd =
|
|
153
|
+
const cwd = cwdOfSession(ctx, session)
|
|
154
154
|
const items: FileVersionItem[] = []
|
|
155
155
|
for (const path of paths) items.push(await observe(fs, cwd, path))
|
|
156
156
|
return { ok: true, items }
|
package/src/lsp/rpc.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Ctx } from '../store.js'
|
|
9
9
|
import { searchRoot } from '../search/ripgrep.js'
|
|
10
|
-
import {
|
|
10
|
+
import { cwdOfSession, sessionOf } from '../registry.js'
|
|
11
11
|
import { resolveProviderSpec, langOfPath, configFromPlugin, mergeConfig, sanitizeLang, LSP_LANGUAGES, type LspConfig, type LspLangConfig } from './config.js'
|
|
12
12
|
import { clearProviderCache, candidatesFor } from './providers.js'
|
|
13
13
|
import { capCompletions } from './completionCap.js'
|
|
@@ -154,7 +154,7 @@ export function createLspRpc(deps: LspRpcDeps): { handlers: Partial<RpcHandlerMa
|
|
|
154
154
|
const rootOf = async (sessionId: string | undefined): Promise<{ root: string; sessionId: string } | { err: string }> => {
|
|
155
155
|
const session = sessionOf(ctx, sessionId)
|
|
156
156
|
if (!session) return { err: SESSION_MISSING_ERROR }
|
|
157
|
-
const cwd =
|
|
157
|
+
const cwd = cwdOfSession(ctx, session)
|
|
158
158
|
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
|
|
159
159
|
try {
|
|
160
160
|
const root = await searchRoot(ctx, session)
|
package/src/mcpRuntime.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { matchWorkspace } from './mcpIsolation.js'
|
|
|
6
6
|
import { listProjects, projectEntryId, reconcileProject } from './mcpProject.js'
|
|
7
7
|
import type { MpcConfig, MpcServer } from './shared/mcp.js'
|
|
8
8
|
import type { Ctx } from './store.js'
|
|
9
|
+
import { cwdOfSession } from './store.js'
|
|
9
10
|
|
|
10
11
|
export type ProjectDef = { config: MpcConfig; enabled: boolean }
|
|
11
12
|
type Instance = { fiber?: any; key: string; path: string; error?: string; stopping?: Promise<void> }
|
|
@@ -76,7 +77,7 @@ export class McpRuntime {
|
|
|
76
77
|
*/
|
|
77
78
|
workspace(agent: any): string | undefined {
|
|
78
79
|
const paths = (this.ctx.get('workspaceRegistry')?.list?.() ?? []).map((ws: { path: string }) => ws.path)
|
|
79
|
-
return matchWorkspace(agent?.session
|
|
80
|
+
return matchWorkspace(cwdOfSession(this.ctx, agent?.session) ?? undefined, paths)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/**
|
package/src/registry.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { DiffRecord } from './shared/types.js'
|
|
7
7
|
import type { Ctx, Session } from './store.js'
|
|
8
|
-
import { loadBucket } from './store.js'
|
|
8
|
+
import { cwdOfSession, loadBucket } from './store.js'
|
|
9
9
|
|
|
10
10
|
/** cwd → 记录桶(内存缓存;随会话销毁清理)。 */
|
|
11
11
|
export type Registry = Map<string, Map<string, DiffRecord>>
|
|
@@ -19,7 +19,20 @@ export function sessionOf(ctx: Ctx, sessionId?: string): Session | undefined {
|
|
|
19
19
|
return live.length === 1 ? live[0] : undefined
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* 会话工作区 cwd(跟随会话内 cd;服务缺失时回落 header.cwd)。
|
|
24
|
+
* 定义在 store.ts 以避免 registry ↔ store 循环依赖,此处仅转出为会话助手入口。
|
|
25
|
+
* @author ddj 2026年10月19号
|
|
26
|
+
*/
|
|
27
|
+
export { cwdOfSession }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 会话头部的原始 cwd(不跟随 cd)。仅用于会话销毁等无上下文场景:
|
|
31
|
+
* 此时投影可能已随会话卸载,读 header 才能保证拿到建会话时的目录。
|
|
32
|
+
* @author ddj 2026年10月19号
|
|
33
|
+
* @param session 目标会话
|
|
34
|
+
* @returns 绝对路径或 null
|
|
35
|
+
*/
|
|
23
36
|
export function cwdOf(session: Session | undefined): string | null {
|
|
24
37
|
return session?.header?.cwd ?? null
|
|
25
38
|
}
|
package/src/rpc.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { archiveEntryFor, markDecision, recordResolved, reconstructOriginal } fr
|
|
|
25
25
|
import { clearRevertState, listArchives, pageArchives, readArchives, revertState, setRevertState, startReverts } from './archiveDb.js'
|
|
26
26
|
import { log } from './log.js'
|
|
27
27
|
import type { Registry } from './registry.js'
|
|
28
|
-
import { bucketOf,
|
|
28
|
+
import { bucketOf, cwdOfSession, sessionOf } from './registry.js'
|
|
29
29
|
import type { SearchOrchestrator } from './search/orchestrator.js'
|
|
30
30
|
import { newSearcher } from './search/orchestrator.js'
|
|
31
31
|
import type { ContentSearcher } from './search/content.js'
|
|
@@ -100,7 +100,7 @@ async function requireSession(ctx: Ctx, sessionId: string | undefined): Promise<
|
|
|
100
100
|
> {
|
|
101
101
|
const session = sessionOf(ctx, sessionId)
|
|
102
102
|
if (!session) return { err: SESSION_MISSING_ERROR }
|
|
103
|
-
const cwd =
|
|
103
|
+
const cwd = cwdOfSession(ctx, session)
|
|
104
104
|
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
|
|
105
105
|
return { session, cwd }
|
|
106
106
|
}
|
package/src/rules.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { join, resolve } from 'node:path'
|
|
|
16
16
|
import { dshHome } from './paths.js'
|
|
17
17
|
import type { RuleInfo, RuleProject, RuleRefInput, RuleScope, RuleSaveInput, UserRuleOrigin } from './shared/rules.js'
|
|
18
18
|
import type { Ctx } from './store.js'
|
|
19
|
+
import { cwdOfSession } from './store.js'
|
|
19
20
|
|
|
20
21
|
// --region 常量与目录定位
|
|
21
22
|
/** 规则文件名白名单:字母数字开头,仅字母数字点横下划线,.mdc 后缀(天然拒绝路径分隔符)。 */
|
|
@@ -550,13 +551,17 @@ export function readUserRulesSync(dirs = userRuleDirs()): LoadedRule[] {
|
|
|
550
551
|
return mergeUserRules(current, legacy, (rule) => rule.info.file)
|
|
551
552
|
}
|
|
552
553
|
|
|
553
|
-
/**
|
|
554
|
+
/**
|
|
555
|
+
* 从装配上下文解析会话工作区 cwd(agent.id → sessions → 当前目录;缺链返回 null)。
|
|
556
|
+
* @author ddj 2026年10月19号
|
|
557
|
+
* @param ctx DSH 上下文
|
|
558
|
+
* @param asm 装配上下文(含 agent.id)
|
|
559
|
+
* @returns 项目规则根的绝对路径或 null
|
|
560
|
+
*/
|
|
554
561
|
function cwdFromAssemble(ctx: Ctx, asm: { agent?: { id?: unknown } } | undefined): string | null {
|
|
555
562
|
const id = asm?.agent?.id
|
|
556
563
|
if (typeof id !== 'string' || !id) return null
|
|
557
|
-
|
|
558
|
-
const cwd = session?.header?.cwd
|
|
559
|
-
return typeof cwd === 'string' && cwd ? cwd : null
|
|
564
|
+
return cwdOfSession(ctx, ctx.get('sessions')?.get?.(id))
|
|
560
565
|
}
|
|
561
566
|
|
|
562
567
|
/**
|
package/src/search/fallback.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 作者 ddj 2026年08月24号
|
|
5
5
|
*/
|
|
6
6
|
import type { Ctx, Session } from '../store.js'
|
|
7
|
-
import { policyOf } from '../store.js'
|
|
7
|
+
import { cwdOfSession, policyOf } from '../store.js'
|
|
8
8
|
import { pathText, pathMatch, prepareQuery } from './query.js'
|
|
9
9
|
import type { PreparedQuery, WorkspaceSearchInput, WorkspaceSearchProvider, WorkspaceSearchResult } from './types.js'
|
|
10
10
|
|
|
@@ -61,7 +61,7 @@ export async function searchFallback(input: WorkspaceSearchInput): Promise<Works
|
|
|
61
61
|
const fs = input.ctx.get('fs') as SearchFs | undefined
|
|
62
62
|
if (!fs) throw new Error('缺少 fs')
|
|
63
63
|
const policy = policyOf(input.ctx, input.session)
|
|
64
|
-
const cwd = input.session
|
|
64
|
+
const cwd = cwdOfSession(input.ctx, input.session)
|
|
65
65
|
const rootTarget = await fs.resolve(policy?.workspaceRoot ?? '.', cwd ? { cwd, signal: input.signal } : { signal: input.signal })
|
|
66
66
|
const signal = input.signal ?? new AbortController().signal
|
|
67
67
|
const files: string[] = []
|
package/src/search/ripgrep.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { existsSync } from 'node:fs'
|
|
|
7
7
|
import { join } from 'node:path'
|
|
8
8
|
import { createRequire } from 'node:module'
|
|
9
9
|
import type { Ctx, Session } from '../store.js'
|
|
10
|
-
import { policyOf } from '../store.js'
|
|
10
|
+
import { cwdOfSession, policyOf } from '../store.js'
|
|
11
11
|
import { prepareQuery, pathText } from './query.js'
|
|
12
12
|
import type { WorkspaceSearchInput, WorkspaceSearchProvider, WorkspaceSearchResult } from './types.js'
|
|
13
13
|
|
|
@@ -127,7 +127,7 @@ export async function searchRoot(ctx: Ctx, session: Session): Promise<string> {
|
|
|
127
127
|
const fs = ctx.get('fs')
|
|
128
128
|
if (!fs) throw new Error('缺少 fs')
|
|
129
129
|
const policy = policyOf(ctx, session)
|
|
130
|
-
const cwd = session
|
|
130
|
+
const cwd = cwdOfSession(ctx, session)
|
|
131
131
|
const rootTarget = await fs.resolve(policy?.workspaceRoot ?? '.', cwd ? { cwd } : {})
|
|
132
132
|
return fs.processPath(rootTarget)
|
|
133
133
|
}
|
package/src/store.ts
CHANGED
|
@@ -33,10 +33,35 @@ export function policyOf(ctx: Ctx, session: Session | undefined): any {
|
|
|
33
33
|
return session ? svc.resolve({ session }) : svc.resolve()
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 读取会话当前工作目录:优先 workingDirectory 投影(DSH 0.2.1 起支持会话内 cd),
|
|
38
|
+
* 服务缺失或投影未就绪时回落到 header.cwd(会话创建时的原始目录)。
|
|
39
|
+
* 服务一律可选探测:不得写入模块 inject,否则旧版 DSH 会停等一个不存在的服务。
|
|
40
|
+
* @author ddj 2026年10月19号
|
|
41
|
+
* @param ctx DSH 上下文(用于可选探测 workingDirectory 服务)
|
|
42
|
+
* @param session 目标会话
|
|
43
|
+
* @returns 绝对路径;无法确定时 null
|
|
44
|
+
*/
|
|
45
|
+
export function cwdOfSession(ctx: Ctx, session: Session | undefined): string | null {
|
|
46
|
+
const headerCwd = session?.header?.cwd
|
|
47
|
+
const fallback = typeof headerCwd === 'string' && headerCwd ? headerCwd : null
|
|
48
|
+
if (!session) return null
|
|
49
|
+
|
|
50
|
+
const service = ctx?.get?.('workingDirectory')
|
|
51
|
+
if (!service || typeof service.get !== 'function') return fallback
|
|
52
|
+
try {
|
|
53
|
+
const current = service.get(session)
|
|
54
|
+
return typeof current === 'string' && current ? current : fallback
|
|
55
|
+
} catch {
|
|
56
|
+
// 会话已销毁或投影缺失:回落原始目录,不让新能力变成新的故障点
|
|
57
|
+
return fallback
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
36
61
|
/** 按会话 cwd 解析目标路径。 */
|
|
37
62
|
export async function resolveTarget(ctx: Ctx, session: Session | undefined, path: string): Promise<string> {
|
|
38
63
|
const fs = ctx.get('fs')
|
|
39
|
-
const cwd = session
|
|
64
|
+
const cwd = cwdOfSession(ctx, session)
|
|
40
65
|
return fs.resolve(path, cwd ? { cwd } : {})
|
|
41
66
|
}
|
|
42
67
|
|
package/src/svn.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { readdir, stat } from 'node:fs/promises'
|
|
|
10
10
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
11
11
|
import { tmpdir } from 'node:os'
|
|
12
12
|
import type { Ctx, Session } from './store.js'
|
|
13
|
-
import {
|
|
13
|
+
import { cwdOfSession, sessionOf } from './registry.js'
|
|
14
14
|
import type { SvnAction, SvnAiPlan, SvnChangeEntry, SvnConflictArtifact, SvnFeature, SvnIgnoreItem, SvnItemStatus, SvnLogEntry, SvnRemoteOutdatedEntry, SvnStatusPayload, SvnSumEntry } from './shared/svn.js'
|
|
15
15
|
import { SESSION_MISSING_ERROR, SESSION_NO_WORKSPACE_ERROR } from './shared/rpc.js'
|
|
16
16
|
import { AI_PLAN_DIFF_CAP, AI_PLAN_PATHS_CAP, BATCH_PATHS_CAP, FILE_SIZES_CAP, SVN_CLI_DEFAULT, TORTOISE_EXE, contentGuardOf, normalizeAiPlan, svnChangelistNameErrorOf, tortoiseLaunchArgv } from './shared/svn.js'
|
|
@@ -463,7 +463,7 @@ async function requireSvnSession(
|
|
|
463
463
|
): Promise<{ cwd: string } | { err: string }> {
|
|
464
464
|
const session: Session | undefined = sessionOf(ctx, sessionId)
|
|
465
465
|
if (!session) return { err: SESSION_MISSING_ERROR }
|
|
466
|
-
const cwd =
|
|
466
|
+
const cwd = cwdOfSession(ctx, session)
|
|
467
467
|
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
|
|
468
468
|
return { cwd }
|
|
469
469
|
}
|