dsh-code-server-app 0.3.6 → 0.3.7

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.
@@ -18,6 +18,24 @@ import { loadDshExport } from './dsh-resolve.mjs';
18
18
  /** 消息来源标记:在会话日志里能一眼看出这条来自编辑器桥。 */
19
19
  export const SOURCE_PLUGIN = 'dsh-code-server-app:editor-bridge';
20
20
 
21
+ /**
22
+ * 取一个 DSH 服务。
23
+ *
24
+ * **只能走 `ctx.get()`,不能走属性访问**:`ctx.agents` 这类属性访问在服务"存在但对该 fiber
25
+ * 不可达"时会抛 `cannot get property "agents" without inject`;这条路径跑在路由回调里,
26
+ * 抛了就变成 500,连"没有可用会话"的 409 提示都给不出来。
27
+ * (0.3.6 的线上事故同源:`bridge-tools.mjs` 里 `ctx.systemPrompt` 的属性访问让整棵插件树
28
+ * 加载失败、dsh web 起不来。)
29
+ */
30
+ function getService(ctx, name) {
31
+ if (ctx === undefined || ctx === null || typeof ctx.get !== 'function') return undefined;
32
+ try {
33
+ return ctx.get(name);
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
21
39
  /** 拼进消息的选区文本上限(超长时截断并注明)。 */
22
40
  export const MAX_SELECTION_CHARS = 8000;
23
41
 
@@ -58,7 +76,7 @@ export function composeEditorPrompt(input) {
58
76
  * @param {object} ctx cordis 上下文
59
77
  */
60
78
  export function pickAgent(ctx) {
61
- const agents = ctx?.agents ?? (typeof ctx?.get === 'function' ? ctx.get('agents') : undefined);
79
+ const agents = getService(ctx, 'agents');
62
80
  if (agents === undefined || agents === null) return null;
63
81
  try {
64
82
  if (typeof agents.currentInitiator === 'function') {
@@ -69,6 +69,38 @@ async function loadDefineTool() {
69
69
  return loadDshExport('@deepseek-ai/dsh-tools', 'defineTool');
70
70
  }
71
71
 
72
+ /**
73
+ * 取一个 DSH 服务。
74
+ *
75
+ * **必须走 `ctx.get()`,不能走属性访问**(0.3.6 的真实线上事故:用 `ctx.systemPrompt`
76
+ * 让整棵插件树加载失败、dsh web 直接起不来)。
77
+ *
78
+ * 区别是确定的(cordis `src/reflect.ts`):
79
+ * - `ctx.get(name)` → `ReflectService.get()` → `_getImpl()`:服务没提供就返回 `undefined`,
80
+ * **永不抛**;
81
+ * - `ctx.tools` 这类属性访问 → 走代理的 get trap,服务"存在但对该 fiber 不可达"时
82
+ * 依次尝试 `internal/get` 瀑布 / `props[prop].get` / `reflect.get(prop,false)`,
83
+ * 任一失败都会抛 `cannot get property "x" without inject`
84
+ * —— 而那是在 `apply()` 里,loader 会因此判定 `failed to apply loader entry` 并终止整个 profile。
85
+ *
86
+ * 所以:`ctx.get()` + 判空 = 可选服务的正确姿势;属性访问只对**已声明 inject** 的服务安全。
87
+ */
88
+ function getService(ctx, name) {
89
+ if (ctx === undefined || ctx === null || typeof ctx.get !== 'function') return undefined;
90
+ try {
91
+ return ctx.get(name);
92
+ } catch {
93
+ return undefined; // 连 get 都抛(上下文形态异常)时,退化为"没有这个服务"
94
+ }
95
+ }
96
+
97
+ /** 取一个服务上的方法,绑定好 this(避免调用时丢上下文)。 */
98
+ function getServiceMethod(ctx, serviceName, methodName) {
99
+ const service = getService(ctx, serviceName);
100
+ if (service === undefined || service === null || typeof service[methodName] !== 'function') return null;
101
+ return service[methodName].bind(service);
102
+ }
103
+
72
104
  // ---------------------------------------------------------------- 工具值投影
73
105
 
74
106
  /**
@@ -201,10 +233,11 @@ function bridgeLive(deps) {
201
233
  * (见 lib/bridge.mjs 的 createContextCache —— 扩展在每次 /sync 里刷新它)。
202
234
  */
203
235
  export async function registerEditorTools(ctx, deps) {
204
- const tools = ctx?.tools ?? (typeof ctx?.get === 'function' ? ctx.get('tools') : undefined);
236
+ const tools = getService(ctx, 'tools');
205
237
  if (tools === undefined || tools === null || typeof tools.register !== 'function') return null;
206
238
  const defineTool = await loadDefineTool();
207
239
  if (defineTool === null) return null;
240
+ const register = tools.register.bind(tools);
208
241
 
209
242
  const contextTool = defineTool({
210
243
  name: EDITOR_CONTEXT_TOOL,
@@ -281,7 +314,7 @@ export async function registerEditorTools(ctx, deps) {
281
314
  }),
282
315
  });
283
316
 
284
- const disposers = [tools.register(contextTool), tools.register(diagnosticsTool)];
317
+ const disposers = [register(contextTool), register(diagnosticsTool)];
285
318
  return () => {
286
319
  for (const dispose of disposers) {
287
320
  try {
@@ -317,14 +350,25 @@ function bridgeIsLive() {
317
350
 
318
351
  /**
319
352
  * 注册系统提示词段落,返回 disposer(或 null = 该 DSH 没有 systemPrompt 服务)。
353
+ *
354
+ * **这里就是 0.3.6 线上事故的位置**:原实现写的是 `ctx?.systemPrompt ?? ctx.get(...)`,
355
+ * 而属性访问会抛(见 `getService` 的说明)—— 可选链只挡 null/undefined,挡不住抛错,
356
+ * 于是 `??` 右边的 `ctx.get()` 永远没机会执行,整个 profile 加载失败。
357
+ * 现在只走 `ctx.get()`,并且对 `section` 调用本身也加保护。
358
+ *
320
359
  * @param {object} ctx cordis 上下文
321
360
  */
322
361
  export function registerEditorPrompt(ctx) {
323
- const systemPrompt = ctx?.systemPrompt ?? (typeof ctx?.get === 'function' ? ctx.get('systemPrompt') : undefined);
324
- if (systemPrompt === undefined || systemPrompt === null || typeof systemPrompt.section !== 'function') return null;
325
- return systemPrompt.section({
326
- name: PROMPT_SECTION,
327
- order: PROMPT_ORDER,
328
- text: () => (bridgeIsLive() ? PROMPT_TEXT : ''),
329
- });
362
+ const section = getServiceMethod(ctx, 'systemPrompt', 'section');
363
+ if (section === null) return null;
364
+ try {
365
+ return section({
366
+ name: PROMPT_SECTION,
367
+ order: PROMPT_ORDER,
368
+ text: () => (bridgeIsLive() ? PROMPT_TEXT : ''),
369
+ });
370
+ } catch (error) {
371
+ console.warn(`[code-server] 编辑器桥:提示词段落注册失败(不影响其余能力):${error && error.message ? error.message : error}`);
372
+ return null;
373
+ }
330
374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Since 0.3.0 the bundle also ships an editor bridge (assets/extensions/dshcs-editor-bridge): a read-only channel between the in-tree VS Code extension host and DSH, giving the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and letting editor gestures drive the session. The tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so the product's own produced-file chips, delivered-file previews and inline prose mentions open in the workbench. The IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs no longer reloads it. Opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen). Following a workspace switch is lightweight: the workbench re-navigates with the new ?folder= and the IDE process is not restarted (since 0.2.12). Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions get a single upgrade notice on the settings page and no other UI. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). No code-server Node layer, no argon2, no C++ toolchain.",
5
5
  "homepage": "https://github.com/jinsiyu/dsh-code-server-app",
6
6
  "repository": {
@@ -3,7 +3,7 @@
3
3
  "vscodeVersion": "1.137.0",
4
4
  "productPath": "stable-b11dabdaca0d3369986975be285db92c8795cea5",
5
5
  "layout": "vscode-only",
6
- "preparedAt": "2026-09-11T15:40:32.321Z",
6
+ "preparedAt": "2026-09-12T02:24:49.146Z",
7
7
  "source": "registry",
8
8
  "node": "v24.13.1",
9
9
  "platform": "win32",