dsh-vscode-mode 0.14.7 → 0.14.8
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 +9 -7
- package/lib/client.js +19 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +22 -0
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/openFlow.ts +33 -3
- package/src/index.ts +9 -1
- package/src/openBridge.ts +13 -0
- package/src/rpc.ts +1 -1
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +58 -4
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs +13 -2
- package/unity/com.dsh.editor/package.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.8",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车,界面状态(页签/光标/侧栏/编辑 Tab/导航历史)镜像到 DSH home 跨重启恢复",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"keywords": [
|
package/src/client/openFlow.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { rpc } from './rpc.js'
|
|
|
10
10
|
import { chooseWorkspace } from './chooseDialog.js'
|
|
11
11
|
import { requestOpen } from './openReceipt.js'
|
|
12
12
|
import { toastDom } from './externalOpen.js'
|
|
13
|
-
import { createAddToConversation } from './addToConversation.js'
|
|
13
|
+
import { createAddToConversation, newDraftSession } from './addToConversation.js'
|
|
14
14
|
import type { AddOutcome, CtxLike } from './addToConversation.js'
|
|
15
15
|
import type { OpenParams } from '../shared/externalOpen.js'
|
|
16
16
|
|
|
@@ -307,12 +307,42 @@ function realDeps(ctx: unknown, overrides: Partial<OpenDeps> = {}): OpenDeps {
|
|
|
307
307
|
const snap = (service('sessions')?.list?.getSnapshot?.() ?? {}) as { ids?: string[]; byId?: Record<string, SessionRow>; phase?: string }
|
|
308
308
|
return { ids: snap.ids ?? [], byId: snap.byId ?? {}, ready: snap.phase !== 'pending' }
|
|
309
309
|
},
|
|
310
|
-
createSession: async (opts) =>
|
|
310
|
+
createSession: async (opts) => {
|
|
311
|
+
// 桌面端 sessions 服务面缺 create(实证缺 open;create 同代面可能缺)——
|
|
312
|
+
// 直连 create 不可用时回落 newDraftSession 的多通道建会话(宽松解析 id)。
|
|
313
|
+
const sessions = service('sessions') as { create?: (o: object) => unknown } | undefined
|
|
314
|
+
if (typeof sessions?.create === 'function') {
|
|
315
|
+
try {
|
|
316
|
+
const created = await sessions.create(opts)
|
|
317
|
+
const id = typeof created === 'string' && created ? created
|
|
318
|
+
: typeof (created as { id?: unknown } | null)?.id === 'string' ? (created as { id: string }).id
|
|
319
|
+
: typeof (created as { sessionId?: unknown } | null)?.sessionId === 'string' ? (created as { sessionId: string }).sessionId
|
|
320
|
+
: ''
|
|
321
|
+
if (id) return id
|
|
322
|
+
} catch { /* 回落下一通道 */ }
|
|
323
|
+
}
|
|
324
|
+
const draft = await newDraftSession(ctx as CtxLike, opts.cwd)
|
|
325
|
+
if (draft.ok && draft.id) return draft.id
|
|
326
|
+
throw new Error('新建会话失败')
|
|
327
|
+
},
|
|
311
328
|
createWorkspace: async (path) => {
|
|
312
329
|
const ws = await service('workspaces').create({ path })
|
|
313
330
|
return { workspaceId: ws.workspaceId, title: ws.title }
|
|
314
331
|
},
|
|
315
|
-
openSession: (id) =>
|
|
332
|
+
openSession: (id) => {
|
|
333
|
+
// 桌面端 sessions 服务面缺 .open(实证 TypeError)——按 startDraftSession 同款
|
|
334
|
+
// 多面切换:materializeScope 铸 scope → manager.select 切会话 → open 兜底,缺面静默。
|
|
335
|
+
try {
|
|
336
|
+
const sessions = service('sessions') as {
|
|
337
|
+
materializeScope?: (id: string) => unknown
|
|
338
|
+
manager?: { select?: (id: string) => void }
|
|
339
|
+
open?: (id: string) => void
|
|
340
|
+
} | undefined
|
|
341
|
+
sessions?.materializeScope?.(id)
|
|
342
|
+
void sessions?.manager?.select?.(id)
|
|
343
|
+
void sessions?.open?.(id)
|
|
344
|
+
} catch { /* 面能力缺失不致命:打开失败由 openChecked 抛错兜底 */ }
|
|
345
|
+
},
|
|
316
346
|
reference: async (sessionId, path, appearance) => {
|
|
317
347
|
const outcome: AddOutcome = await add.appendReference(sessionId, path, undefined, appearance)
|
|
318
348
|
// ok/busy(纯文本降级)都算已落点;unavailable/failed 交给 settleRef 继续重试
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { registerRoutes } from './routes.js'
|
|
3
3
|
import { captureToolResult } from './capture.js'
|
|
4
4
|
import { closeArchiveDbs } from './archiveDb.js'
|
|
5
|
-
import { handleRpc } from './rpc.js'
|
|
5
|
+
import { handleRpc, integrationBaseUrlOf } from './rpc.js'
|
|
6
6
|
import { newSearcher } from './search/orchestrator.js'
|
|
7
7
|
import { newContentSearcher } from './search/content.js'
|
|
8
8
|
import { installIsolation } from './mcpIsolation.js'
|
|
@@ -13,6 +13,7 @@ import type { SettingsDeps } from './fileOpenSettings.js'
|
|
|
13
13
|
import z from '@deepseek-ai/schemastery'
|
|
14
14
|
import { shellMenuLifecycle } from './integrate.js'
|
|
15
15
|
import { startOpenInbox } from './openInbox.js'
|
|
16
|
+
import { ensureActiveBridge } from './openBridge.js'
|
|
16
17
|
import { installPlanTool } from './svnPlanTool.js'
|
|
17
18
|
import { disposeIndex } from './treeIndex.js'
|
|
18
19
|
import { sweepTreeCache } from './paths.js'
|
|
@@ -142,6 +143,13 @@ export async function apply(ctx: Ctx, config?: unknown): Promise<void> {
|
|
|
142
143
|
await installPlanTool(ctx, submitPlan)
|
|
143
144
|
ctx.effect(mountInbox, 'vscode-mode:open-inbox')
|
|
144
145
|
ctx.effect(() => shellMenuLifecycle(ctx), 'vscode-mode:integration')
|
|
146
|
+
/** @author ddj 2026年10月12号 @returns Active-profile bridge disposer; failures never block boot. */
|
|
147
|
+
async function provisionBridge(): Promise<() => void> {
|
|
148
|
+
try { await ensureActiveBridge(ctx, await integrationBaseUrlOf(ctx)) }
|
|
149
|
+
catch (error) { noteWarning('外部打开桥接未就绪:' + String(error)) }
|
|
150
|
+
return () => {}
|
|
151
|
+
}
|
|
152
|
+
ctx.effect(provisionBridge, 'vscode-mode:bridge')
|
|
145
153
|
void sweepTreeCache()
|
|
146
154
|
await logCompatSummary(ctx, warnings)
|
|
147
155
|
log.info('编辑差异审查已装配(认证路由、项目 MCP 作用域、SVN 方案工具与外部打开通道)')
|
package/src/openBridge.ts
CHANGED
|
@@ -56,6 +56,19 @@ export async function ensureOpenBridge(ctx: Ctx, baseUrl: string): Promise<strin
|
|
|
56
56
|
return join(dir, 'dsh-open.ini')
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** @public @author ddj 2026年10月12号
|
|
60
|
+
* Provision the active profile's independent bridge at startup (desktop or web).
|
|
61
|
+
* Mirrors unityInstall's per-profile bridge without requiring a registered project,
|
|
62
|
+
* so Unity can select either target. Unknown profile returns null instead of failing.
|
|
63
|
+
* @param ctx Active host context.
|
|
64
|
+
* @param baseUrl Selected public Web URL (unused for desktop wake).
|
|
65
|
+
* @returns Absolute profile bridge INI path, or null when no owned profile is active.
|
|
66
|
+
*/
|
|
67
|
+
export async function ensureActiveBridge(ctx: Ctx, baseUrl: string): Promise<string | null> {
|
|
68
|
+
if (!findProfileDir(ctx)) return null
|
|
69
|
+
return ensureOpenBridge(ctx, baseUrl)
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
/** @private @author ddj 2026年09月28号
|
|
60
73
|
* Replace a bridge file atomically without following a preexisting file link.
|
|
61
74
|
* @param path Destination in the validated bridge directory.
|
package/src/rpc.ts
CHANGED
|
@@ -461,7 +461,7 @@ async function latestPatchBackup(patchPath: string): Promise<string | undefined>
|
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
/** 读取设置中的深链基址(缺省/非法回退默认 3080)。 */
|
|
464
|
-
async function integrationBaseUrlOf(ctx: Ctx): Promise<string> {
|
|
464
|
+
export async function integrationBaseUrlOf(ctx: Ctx): Promise<string> {
|
|
465
465
|
const settings = ctx.get('settings')
|
|
466
466
|
const value = sectionOf(settings, FILE_OPEN_SETTINGS_NS)?.value as { integrationBaseUrl?: unknown } | undefined
|
|
467
467
|
const raw = typeof value?.integrationBaseUrl === 'string' ? value.integrationBaseUrl.trim() : ''
|
|
@@ -15,8 +15,13 @@ namespace Dsh.EditorIntegration
|
|
|
15
15
|
{
|
|
16
16
|
private const string EditorName = "DSH 文件编辑";
|
|
17
17
|
private const string VirtualPath = "dsh-editor://vscode-mode";
|
|
18
|
-
private const string DshVersion = "0.3.
|
|
18
|
+
private const string DshVersion = "0.3.1";
|
|
19
19
|
internal const string VersionText = DshVersion;
|
|
20
|
+
private const string TargetAuto = "auto";
|
|
21
|
+
private const string TargetWeb = "web";
|
|
22
|
+
private const string TargetDesktop = "desktop";
|
|
23
|
+
private static readonly string[] TargetLabels = { "自动", "DSH Web", "DSH 桌面端" };
|
|
24
|
+
private static readonly string[] TargetValues = { TargetAuto, TargetWeb, TargetDesktop };
|
|
20
25
|
|
|
21
26
|
/// <summary>Register this editor without changing the user's selection. @author ddj 2026-09-28</summary>
|
|
22
27
|
static DshCodeEditor()
|
|
@@ -57,7 +62,13 @@ namespace Dsh.EditorIntegration
|
|
|
57
62
|
string selected = EditorPrefs.GetString(key, string.Empty);
|
|
58
63
|
string value = EditorGUILayout.TextField("DSH 桥接配置", selected);
|
|
59
64
|
if (value != selected) EditorPrefs.SetString(key, value.Trim());
|
|
60
|
-
|
|
65
|
+
string targetKey = TargetKey();
|
|
66
|
+
string current = EditorPrefs.GetString(targetKey, TargetAuto);
|
|
67
|
+
int picked = System.Array.IndexOf(TargetValues, current);
|
|
68
|
+
if (picked < 0) picked = 0;
|
|
69
|
+
int chosen = EditorGUILayout.Popup("打开目标", picked, TargetLabels);
|
|
70
|
+
if (TargetValues[chosen] != current) EditorPrefs.SetString(targetKey, TargetValues[chosen]);
|
|
71
|
+
EditorGUILayout.HelpBox("留空时自动读取本项目 UserSettings/dsh-editor.ini;没有安装提示时使用 DSH_HOME 中的旧 shell 配置。填写绝对路径可覆盖自动选择。打开目标选择 DSH 桌面端或 DSH Web 时优先使用对应 profile 的独立 bridge。请求须收到 ACK 才确认成功。", MessageType.Info);
|
|
61
72
|
if (GUILayout.Button("测试:在 DSH 中打开当前项目")) BeginOpen(ProjectRoot(), 0, 0);
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -134,7 +145,8 @@ namespace Dsh.EditorIntegration
|
|
|
134
145
|
private static void BeginOpen(string path, int line, int column)
|
|
135
146
|
{
|
|
136
147
|
string config = EditorPrefs.GetString(ConfigKey(), string.Empty);
|
|
137
|
-
|
|
148
|
+
string target = EditorPrefs.GetString(TargetKey(), TargetAuto);
|
|
149
|
+
var worker = new DshOpenWorker(config, path, line, column, ProjectRoot(), LegacyConfig(), target);
|
|
138
150
|
var thread = new Thread(worker.Run) { IsBackground = true };
|
|
139
151
|
thread.Start();
|
|
140
152
|
}
|
|
@@ -143,6 +155,10 @@ namespace Dsh.EditorIntegration
|
|
|
143
155
|
/// <returns>Project-local editor preference key.</returns>
|
|
144
156
|
private static string ConfigKey() { return "DshEditor.Config." + ProjectRoot(); }
|
|
145
157
|
|
|
158
|
+
/// <summary>Derive a project-specific open-target preference key. @author ddj 2026-10-12</summary>
|
|
159
|
+
/// <returns>Project-local open-target preference key.</returns>
|
|
160
|
+
private static string TargetKey() { return "DshEditor.Target." + ProjectRoot(); }
|
|
161
|
+
|
|
146
162
|
/// <summary>Read the project's installed profile hint on the worker. @author ddj 2026-09-28</summary>
|
|
147
163
|
/// <param name="root">Project root captured on the main thread.</param><param name="fallback">Legacy shell INI.</param>
|
|
148
164
|
/// <returns>The hinted absolute INI, or legacy path only when the hint is absent.</returns>
|
|
@@ -171,10 +187,48 @@ namespace Dsh.EditorIntegration
|
|
|
171
187
|
/// <summary>Honor custom DSH_HOME for legacy installations. @author ddj 2026-09-28</summary>
|
|
172
188
|
/// <returns>Legacy installed shell INI path, without filesystem IO.</returns>
|
|
173
189
|
private static string LegacyConfig()
|
|
190
|
+
{
|
|
191
|
+
return Path.Combine(Path.Combine(Path.Combine(Home(), "dsh-vscode-mode"), "shell"), "dsh-open.ini");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// <summary>Resolve the user DSH home honoring DSH_HOME. @author ddj 2026-10-12</summary>
|
|
195
|
+
/// <returns>Absolute user DSH home directory.</returns>
|
|
196
|
+
internal static string Home()
|
|
174
197
|
{
|
|
175
198
|
string home = Environment.GetEnvironmentVariable("DSH_HOME");
|
|
176
199
|
if (string.IsNullOrEmpty(home)) home = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".dsh");
|
|
177
|
-
return
|
|
200
|
+
return home;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// <summary>Find the first profile bridge whose mode matches a requested target. @author ddj 2026-10-12</summary>
|
|
204
|
+
/// <param name="mode">Target mode (web or desktop) stored in the bridge INI.</param>
|
|
205
|
+
/// <returns>Absolute matching bridge INI path, or null when no profile provides it.</returns>
|
|
206
|
+
internal static string FindBridge(string mode)
|
|
207
|
+
{
|
|
208
|
+
string profiles = Path.Combine(Home(), "profiles");
|
|
209
|
+
if (!Directory.Exists(profiles)) return null;
|
|
210
|
+
foreach (string profile in Directory.GetDirectories(profiles))
|
|
211
|
+
{
|
|
212
|
+
string ini = Path.Combine(Path.Combine(Path.Combine(profile, "dsh-vscode-mode"), "bridge"), "dsh-open.ini");
|
|
213
|
+
if (!File.Exists(ini) || ModeOf(ini) != mode) continue;
|
|
214
|
+
return Path.GetFullPath(ini);
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// <summary>Read the mode value of a bridge INI without guessing a profile. @author ddj 2026-10-12</summary>
|
|
220
|
+
/// <param name="ini">Absolute bridge INI path.</param><returns>mode value, or empty when absent.</returns>
|
|
221
|
+
private static string ModeOf(string ini)
|
|
222
|
+
{
|
|
223
|
+
foreach (string text in File.ReadAllLines(ini))
|
|
224
|
+
{
|
|
225
|
+
int equal = text.IndexOf('=');
|
|
226
|
+
if (equal < 0) continue;
|
|
227
|
+
string key = text.Substring(0, equal).Trim();
|
|
228
|
+
if (!string.Equals(key, "mode", StringComparison.OrdinalIgnoreCase)) continue;
|
|
229
|
+
return text.Substring(equal + 1).Trim();
|
|
230
|
+
}
|
|
231
|
+
return string.Empty;
|
|
178
232
|
}
|
|
179
233
|
|
|
180
234
|
/// <summary>Resolve asset paths from the Unity project root. @author ddj 2026-09-28</summary>
|
|
@@ -16,6 +16,7 @@ namespace Dsh.EditorIntegration
|
|
|
16
16
|
private readonly string targetPath;
|
|
17
17
|
private readonly string projectRoot;
|
|
18
18
|
private readonly string legacyConfig;
|
|
19
|
+
private readonly string target;
|
|
19
20
|
private readonly int line;
|
|
20
21
|
private readonly int column;
|
|
21
22
|
|
|
@@ -23,12 +24,14 @@ namespace Dsh.EditorIntegration
|
|
|
23
24
|
/// <param name="config">Selected INI path.</param><param name="path">Absolute asset or project.</param>
|
|
24
25
|
/// <param name="row">One-based line.</param><param name="col">One-based column.</param>
|
|
25
26
|
/// <param name="root">Project root.</param><param name="fallback">Legacy shell INI path.</param>
|
|
26
|
-
|
|
27
|
+
/// <param name="mode">Requested target (auto/web/desktop); empty means auto. @author ddj 2026-10-12</param>
|
|
28
|
+
internal DshOpenWorker(string config, string path, int row, int col, string root, string fallback, string mode)
|
|
27
29
|
{
|
|
28
30
|
configPath = config;
|
|
29
31
|
targetPath = path;
|
|
30
32
|
projectRoot = root;
|
|
31
33
|
legacyConfig = fallback;
|
|
34
|
+
target = string.IsNullOrEmpty(mode) ? "auto" : mode;
|
|
32
35
|
line = row;
|
|
33
36
|
column = col;
|
|
34
37
|
}
|
|
@@ -105,7 +108,15 @@ namespace Dsh.EditorIntegration
|
|
|
105
108
|
/// <returns>Selected bridge INI path; missing or malformed hints are handled by the caller.</returns>
|
|
106
109
|
internal string SelectConfig()
|
|
107
110
|
{
|
|
108
|
-
|
|
111
|
+
if (!string.IsNullOrEmpty(configPath)) return configPath;
|
|
112
|
+
if (target == "web") return DshCodeEditor.FindBridge("web") ?? DshCodeEditor.DefaultConfig(projectRoot, legacyConfig);
|
|
113
|
+
if (target == "desktop")
|
|
114
|
+
{
|
|
115
|
+
string desktop = DshCodeEditor.FindBridge("desktop");
|
|
116
|
+
if (desktop != null) return desktop;
|
|
117
|
+
throw new FileNotFoundException("No DSH desktop bridge configuration found. Open the DSH desktop app once, then retry, or set an explicit bridge configuration in External Tools.");
|
|
118
|
+
}
|
|
119
|
+
return DshCodeEditor.DefaultConfig(projectRoot, legacyConfig);
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
/// <summary>Build helper arguments without routing files through a URL. @author ddj 2026-09-28</summary>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.dsh.editor",
|
|
3
3
|
"displayName": "DSH 文件编辑",
|
|
4
|
-
"description": "DSH(dsh-vscode-mode)外部脚本编辑器:通过当前用户私有 OPEN REQUEST/ACK 文件队列打开脚本、Console 行列或项目;确认匹配 ACK 后才报告成功,支持官方桌面与所选 Web profile
|
|
5
|
-
"version": "0.3.
|
|
4
|
+
"description": "DSH(dsh-vscode-mode)外部脚本编辑器:通过当前用户私有 OPEN REQUEST/ACK 文件队列打开脚本、Console 行列或项目;确认匹配 ACK 后才报告成功,支持官方桌面与所选 Web profile,可在外部工具面板选择打开目标(自动/DSH Web/DSH 桌面端)。",
|
|
5
|
+
"version": "0.3.1",
|
|
6
6
|
"unity": "2019.4"
|
|
7
7
|
}
|