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.
@@ -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
- const refreshRecords = (skipStale) => {
874
- if (!sessionId) return
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 start = trace ? performance.now() : 0
882
+ const compareStart = trace ? performance.now() : 0
878
883
  // #endregion
879
- rpc('edrv.list', { sessionId, ...(skipStale ? { skipStale: true } : {}) }).then((res) => {
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
- const received = trace ? performance.now() : 0
882
- // #endregion
883
- if (!res || !res.ok || !Array.isArray(res.records)) return
884
- const map = {}
885
- for (const r of res.records) {
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
- setRecords((prev) => {
890
- // #region debug log
891
- const compareStart = trace ? performance.now() : 0
892
- // #endregion
893
- const equal = sameRecords(prev, map)
894
- // #region debug log
895
- if (trace) {
896
- try {
897
- const compareMs = performance.now() - compareStart
898
- if (compareMs > 20 || received - start > 100) dbg(sessionId, `[DEBUG diffList] count=${res.records.length} skipStale=${skipStale === true} rpcMs=${(received - start).toFixed(1)} compareMs=${compareMs.toFixed(1)} changed=${!equal}`)
899
- } catch (e) { /* 诊断不可影响轮询 */ }
900
- }
901
- // #endregion
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
- const t = setInterval(refreshRecords, 5000)
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
- * 5s 轮询当前会话持久化体积(host 仅 stat,近零成本),≥1MB 显示、超阈值变色,
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
- const t = setInterval(load, 5000)
43
- return () => { clearInterval(t) }
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 外未经逐版适配;按能力探测)'
@@ -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 { cwdOf, sessionOf } from './registry.js'
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 = cwdOf(session)
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 { sessionOf, cwdOf } from '../registry.js'
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 = cwdOf(session)
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?.header?.cwd, paths)
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
- /** 会话工作区 cwd。 */
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, cwdOf, sessionOf } from './registry.js'
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 = cwdOf(session)
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
- /** 从装配上下文解析会话工作区 cwd(agent.id → sessions → header.cwd;缺链返回 null)。 */
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
- const session = ctx.get('sessions')?.get?.(id)
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
  /**
@@ -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?.header?.cwd
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[] = []
@@ -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?.header?.cwd
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?.header?.cwd
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 { cwdOf, sessionOf } from './registry.js'
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 = cwdOf(session)
466
+ const cwd = cwdOfSession(ctx, session)
467
467
  if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
468
468
  return { cwd }
469
469
  }