dsh-code-server-app 0.3.22 → 0.3.24

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.
Files changed (41) hide show
  1. package/README.en.md +24 -11
  2. package/README.md +16 -8
  3. package/assets/extensions/dshcs-editor-bridge/extension.js +32 -3
  4. package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +18 -4
  5. package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +13 -0
  6. package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
  7. package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +2 -2
  8. package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +36 -3
  9. package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +28 -20
  10. package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +573 -7
  11. package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +108 -11
  12. package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +89 -10
  13. package/assets/extensions/dshcs-editor-bridge/webview/thread.css +1 -1
  14. package/assets/extensions/dshcs-editor-bridge/webview/thread.js +66 -63
  15. package/lib/bridge-approval.mjs +48 -14
  16. package/lib/bridge-session.mjs +40 -0
  17. package/lib/bridge-thread.mjs +80 -24
  18. package/lib/client.js +2 -2
  19. package/lib/index.js +240 -14
  20. package/package.json +2 -2
  21. package/vendor/VENDOR.json +1 -1
  22. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
  23. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
  24. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
  25. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
  26. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
  27. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
  28. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
  29. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
  30. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
  31. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
  32. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
  33. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
  34. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
  35. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
  36. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
  37. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
  38. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
  39. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
  40. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
  41. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
package/README.en.md CHANGED
@@ -209,21 +209,32 @@ only the editor knows, and lets editor gestures drive the current session.
209
209
 
210
210
  - The tools are only registered while the bridge is live (so the model never sees an unusable tool), and the
211
211
  system-prompt section renders only then too.
212
- - **Ask panel** (extension 0.2.0; since 0.2.3 the body goes through the official renderer): the context-menu command
213
- no longer pops a one-line input box but opens a panel — that session's **new content** on top, input at the bottom.
214
- The selection is captured **at send time** (so you can change it while the panel stays open).
212
+ - **Ask panel** (extension 0.2.0; the official renderer since 0.2.3; a **floating dialog over the DSH UI since
213
+ 0.2.5**): the context-menu command no longer opens a panel inside the editor (that is always a tab or a column,
214
+ never a dialog) — the plugin's client half pops up a draggable, resizable floating chat window in the DSH page
215
+ itself (bottom-right, ✕ closes it), leaving the editor layout alone. Hosts without that capability fall back to
216
+ the in-editor webview panel. The selection can still be changed while the dialog stays open.
215
217
  The two commands **remember their intent** (0.3.21): "ask about selection" carries a **line range + selection text**
216
218
  only when something is actually selected, while "ask about file" **never carries line numbers or a selection** — the
217
219
  cursor line is irrelevant to the question and only misleads the agent. With no selection, the selection command also
218
220
  degrades to the plain file.
221
+ - **Injected context is collapsed** (0.3.24): the location line plus the selection code block the bridge adds to the
222
+ message are split out into a collapsed Context row (click it to see the code), while the bubble keeps only the user's
223
+ own words — the same treatment the DSH UI gives injected context.
219
224
  - **The panel renders exactly what DSH renders** (0.3.22): the panel bundles DSH's official Markdown renderer
220
225
  (`MarkdownText` from `@deepseek-ai/dsh-client-ui-primitives`) plus the official design tokens — the same
221
226
  micromark/mdast pipeline, the same incremental streaming parser, the same shiki highlighting (boot set:
222
227
  typescript / shellscript / json), KaTeX math and the same heading/table typography. Only **new content** is
223
228
  rendered (from the moment the panel subscribes); history is **not replayed** and there is no "load earlier".
224
- - **Approvals are handled right in the panel** (0.3.22): while the panel is open, that session's approval requests
225
- ask the panel first (8 s by default). Clicking "allow once" / "reject" settles it immediately; if nobody answers,
226
- the request is handed back **unchanged** to the official path (the DSH UI shows the same card).
229
+ - **Thinking shows up like in DSH** (0.3.23): assistant reasoning becomes a Think row — **collapsed by default**,
230
+ showing its first line (or the latest line while streaming) and expanding on a row click, built from the official
231
+ `DisclosureRow` plus the official think icon and typography language.
232
+ - **Approvals are handled right in the panel** (0.3.22; window fixed in 0.3.23): while the panel is open, that
233
+ session's approval requests ask the panel first (5 minutes by default). Clicking "allow once" / "reject" settles it
234
+ immediately; **closing the panel** or letting the window expire hands the request back **unchanged** to the official
235
+ path (the DSH UI shows the same card). 0.3.22's 8-second window was far too short for a human — the buttons went
236
+ grey before anyone could click (reported as "the approval box stopped working"); the window is now 5 minutes and
237
+ closing the panel hands off immediately instead of waiting it out.
227
238
  **Nothing is ever auto-approved** — `allowed-once` can only come from a click, and there is no "always allow".
228
239
  - The question enters the DSH session as a **plain user message** (`source: { kind: 'user' }`, host 0.3.19): earlier
229
240
  versions used `{kind:'plugin'}`, which DSH renders as a *context update* — it did not look like something the user
@@ -263,7 +274,7 @@ The three `/sync` fields the panel actually consumes (0.3.22):
263
274
  |---|---|---|
264
275
  | `thread` | **new content** entries (user / assistant / tool / approval) of the session the panel watches; bounded: ≤120 entries per session, ≤8000 chars per body, ≤4 watched sessions | assistant bodies go to the official renderer; tools and approvals become compact summary rows |
265
276
  | `approvals` | pending approval requests `[{id, toolName, reason, at}]` (≤4) | renders the card with a countdown; a click posts `/approve` |
266
- | `approvalHoldMs` / `uiVersion` | the approval window (8000 ms by default) / the DSH UI version | countdown basis; a renderer-version mismatch is surfaced in the panel |
277
+ | `approvalHoldMs` / `uiVersion` | the approval window (300000 ms = 5 minutes by default) / the DSH UI version | countdown basis; a renderer-version mismatch is surfaced in the panel |
267
278
 
268
279
  > **Why not HTTP (settled in 0.3.13, all three measured)**
269
280
  > 1. **Desktop has no HTTP surface at all**: the renderer calls `host.fetch()` through Electron IPC
@@ -304,7 +315,8 @@ user**, so:
304
315
  **no free text, paths, or command arguments**, so it can answer questions but never start an action;
305
316
  (b) `id` must belong to a request this process created and that is **still pending** (single use);
306
317
  (c) `outcome` accepts only `allowed-once` / `rejected` — there is **no "always allow"**;
307
- (d) when no panel is watching, or the window (8 s by default) expires, the request goes **back to the official
318
+ (d) when no panel is watching, the panel is closed, or the window (5 minutes by default) expires, the request goes
319
+ **back to the official
308
320
  path** — never auto-approved (DSH's `approval/request` itself fails closed; this bridge can only keep
309
321
  "nobody answered" as "nobody answered"). `pnpm test:webview` asserts these four plus the host-side whitelist.
310
322
  3. **Any request carrying `Origin` gets 403.** Browsers always send one (including a sandboxed iframe's literal
@@ -760,9 +772,10 @@ What remains on the plugin side:
760
772
  - **Panel assets are pinned to the DSH version**: the renderer is bundled against the UI version of the deployed
761
773
  DSH, so after upgrading DSH you must rebuild the panel (`pnpm run build:webview`; the build fails loudly on a
762
774
  version mismatch). The panel also shows a mismatch notice at runtime instead of silently using the wrong renderer.
763
- - **The approval window in the panel is 8 seconds**: while the panel is open, approvals ask the panel first and are
764
- handed back to the DSH UI after 8 s — once handed back, that request can **only** be answered in the DSH UI (the
765
- panel card says "handed to the DSH UI").
775
+ - **The approval window in the panel is 5 minutes**: while the panel is open, approvals ask the panel first (the card
776
+ shows a countdown); **closing the panel** or letting the 5 minutes run out hands the request back to the DSH UI —
777
+ after that, that request can **only** be answered there (the card disappears from the panel and the thread keeps an
778
+ audit row).
766
779
  - **Unsaved buffers are reported, not taken over.** The agent still edits via its own `fs` tools, i.e. against
767
780
  disk. What the bridge adds is a notice *before* writing a dirty file, a diff *after*, and a warning instead of
768
781
  an overwrite. It does not decide whether the user saves — that would mean changing the agent's read path,
package/README.md CHANGED
@@ -203,17 +203,25 @@ DSH 用**资源地址**命名文件,`openFile` 只负责把地址交给右侧栏
203
203
  | agent → 编辑器 | agent 改了哪个文件 → 开**原生 diff** 审阅;缓冲区有未保存改动时**告警而不覆盖** | host 观察 `tools/result`,扩展轮询后开 diff + 非模态告警 |
204
204
 
205
205
  - 工具只在桥就绪时注册(IDE 没起来时模型看不到"有个用不了的工具");提示词段落也只在桥存活时渲染。
206
- - **提问面板**(扩展 0.2.0 起;0.2.3 起正文走官方渲染器):右键命令不再弹一行输入框,而是一个面板 ——
207
- 上面是该会话的**新内容**、下面是输入框;发送时**现取当前选区**(可以在面板开着的同时改选区再问)。
206
+ - **提问面板**(扩展 0.2.0 起;0.2.3 起正文走官方渲染器;0.2.5 起是**浮在 DSH 界面上的对话框**):
207
+ 右键命令不再开编辑器里的面板(那是编辑器的一个 tab/一列,怎么开都不像对话框),而是让 DSH 页面里的
208
+ 插件客户端弹出一个**可拖动、可缩放的浮动对话窗**(右下角,✕ 关闭),不占编辑器版面;
209
+ 面板开着的**同时**还能改选区再问。老宿主(探测不到对话框能力)才退回编辑器里的 webview 面板。
208
210
  两个命令的**意图分开记**(0.3.21):「针对选中内容提问」只有真的选了内容才带**行区间 + 选区正文**;
209
211
  「针对当前文件提问」**永远不带行号、不带选区** —— 光标停在哪一行跟问题无关,行号只会误导 agent;
210
212
  没选区时用选中命令提问也会退化成纯文件。
213
+ - **注入的上下文是折叠的**(0.3.24):桥拼进消息的位置行 + 选区代码块会被拆出来,显示成一行默认收起的
214
+ 「上下文」(点开才看得到那段代码),气泡里只留你的原话 —— 与 DSH 界面处理注入上下文的方式一致。
211
215
  - **面板里的正文就是 DSH 的渲染结果**(0.3.22):面板打包了 DSH 官方的 markdown 渲染器
212
216
  (`@deepseek-ai/dsh-client-ui-primitives` 的 `MarkdownText`)与官方设计令牌 —— 同一套 micromark/mdast 管线、
213
217
  同一个增量流式解析器、同一个 shiki 高亮(启动集 typescript / shellscript / json)、KaTeX 公式、同样的标题与表格排版。
214
218
  只渲染**新内容**(从面板订阅那一刻起),**不重放历史**、没有"加载更早"。
215
- - **授权就在面板里处理**(0.3.22):面板打开着的时候,该会话的授权请求**先问面板**(默认 8 秒),
216
- 点「允许一次」/「拒绝」立刻生效;没人答就把请求**原样交回官方链路**(DSH 界面照旧弹卡)。
219
+ - **思考过程也照官方显示**(0.3.23):助手的 reasoning 以「思考」行出现 —— **默认收起**、收起时显示首行
220
+ (流式时显示最新一行)、点整行展开全文,用的就是官方 `DisclosureRow` + 官方的思考图标与排版语言。
221
+ - **授权就在面板里处理**(0.3.22;0.3.23 修好窗口):面板打开着的时候,该会话的授权请求**先问面板**(默认 5 分钟),
222
+ 点「允许一次」/「拒绝」立刻生效;**关掉面板**或等满窗口就把请求**原样交回官方链路**(DSH 界面照旧弹卡)。
223
+ 0.3.22 的 8 秒窗口对人来说太短 —— 卡片还没读完按钮就灰了(实测反馈「授权框失效了」),现在窗口是 5 分钟,
224
+ 而且面板一关就立刻交回、不干等。
217
225
  **永不自动放行** —— `allowed-once` 只能来自你的一次点击,面板里没有"以后都允许"这种入口。
218
226
  - 提问进 DSH 会话时是**普通用户消息**(`source: { kind: 'user' }`,host 0.3.19 修正):早期版本用
219
227
  `{kind:'plugin'}` 会被 DSH 渲染成"上下文更新",看起来不像自己说的话;来源信息靠正文首行
@@ -251,7 +259,7 @@ host → 扩展 <extensionsDir>/.dshcs-bridge/bridge.json 端点 + 令牌(
251
259
  |---|---|---|
252
260
  | `thread` | 被面板 `watch` 的会话的**新内容**条目(user / assistant / tool / approval),有界:每会话 ≤120 条、单条正文 ≤8000 字符、同时 watch ≤4 个会话 | 助手正文交给官方渲染器;工具与授权是紧凑摘要行 |
253
261
  | `approvals` | 待决授权请求 `[{id, toolName, reason, at}]`(≤4 条) | 弹卡片 + 倒计时;点按后 `POST /approve` |
254
- | `approvalHoldMs` / `uiVersion` | 授权窗口长度(默认 8000ms)/ DSH 界面版本 | 倒计时基准;渲染器版本不一致时提示 |
262
+ | `approvalHoldMs` / `uiVersion` | 授权窗口长度(默认 300000ms = 5 分钟)/ DSH 界面版本 | 倒计时基准;渲染器版本不一致时提示 |
255
263
 
256
264
  > **为什么不是 HTTP(0.3.13 定论,三条都实测过)**
257
265
  > 1. **desktop 根本没有 HTTP 面**:渲染进程经 Electron IPC 调 `host.fetch()`
@@ -286,7 +294,7 @@ host 反向请求不到它。所以编辑器状态只能在扩展主动发起的
286
294
  (a) 只能**回答**已经存在的授权请求,请求体只有 `{id, outcome}`,**不接受任何自由文本 / 路径 / 命令参数**
287
295
  —— 它只能"回答问题",不能"发起动作";(b) `id` 必须是本进程自己发起、且**仍未决**的请求(用后即废);
288
296
  (c) `outcome` 只接受 `allowed-once` / `rejected`,**没有"永久允许"**;
289
- (d) 没有面板在看 / 窗口超时(默认 8s)→ **交回官方链路**,绝不自动放行
297
+ (d) 没有面板在看 / 面板关掉 / 窗口超时(默认 5 分钟)→ **交回官方链路**,绝不自动放行
290
298
  (DSH 的 `approval/request` 本身 fail closed,这里只能把"没人答"保持成"没人答")。
291
299
  `pnpm test:webview-bundle` 里有针对这四条与宿主侧白名单的断言。
292
300
  3. **带 `Origin` 的请求一律 403。** 浏览器发起必带 Origin(含沙箱 iframe 的 `Origin: null`),
@@ -732,8 +740,8 @@ desktop profile 由 `apps/desktop-host` 把 `/api/*` 交给同一个 `createShar
732
740
  - **面板产物与 DSH 版本绑定**:渲染器按构建时 DSH 部署的界面版本打包,DSH 升级后**要重打面板**
733
741
  (`pnpm run build:webview`;构建脚本会在版本不一致时直接报错)。运行期面板顶部也会提示版本不一致,
734
742
  不会悄悄用错版本的渲染器。
735
- - **面板里的授权窗口是 8 秒**:面板打开着的时候授权先问面板,8 秒没人答就交回 DSH 界面
736
- —— 交回之后这一条**只能**在 DSH 界面里处理(面板上的卡片会显示"已交给 DSH 界面")。
743
+ - **面板里的授权窗口是 5 分钟**:面板打开着的时候授权先问面板(卡片上有倒计时);**关掉面板**或等满 5 分钟
744
+ 就交回 DSH 界面 —— 交回之后这一条**只能**在 DSH 界面里处理(卡片从面板消失,对话流里留一行授权审计)。
737
745
 
738
746
  - ~~子路径不支持~~ **已不成立(0.2.0 实测更正)**:VS Code 渲染出的 workbench HTML 里
739
747
  **资源引用全是相对路径**(实测 9 条引用中绝对路径 0 条,`serverBasePath="."`、`rootEndpoint="."`),
@@ -58,6 +58,8 @@ let pollTimer = null;
58
58
  let lastPollAt = 0;
59
59
  /** 「问 DSH」面板:null = 没开。`{panel, state, mode}`(状态模型见 lib/ask-panel.js)。 */
60
60
  let askPanel = null;
61
+ /** 宿主是否支持 DSH 页面里的悬浮对话框(0.2.5;/sync 的 askDialog 字段)。 */
62
+ let askDialogSupported = false;
61
63
  /** 扩展根目录(activate 时记下;面板要按它取 webview 产物 URI)。 */
62
64
  let extensionRoot = null;
63
65
  /** 是否已经确认过 host 端点可达(避免把"IDE 刚起、扩展先加载"误判为断线)。 */
@@ -388,6 +390,11 @@ async function pollOnce() {
388
390
  await handleEvent(event);
389
391
  }
390
392
  if ((result.events ?? []).length > 0) client.persist();
393
+ // 能力探测:宿主支持悬浮对话框(0.3.24)⇒ 右键提问不再开编辑器面板。
394
+ if (result.askDialog === true && !askDialogSupported) {
395
+ askDialogSupported = true;
396
+ log('宿主支持 DSH 页面里的对话对话框:右键提问将浮在 DSH 界面上(不再开编辑器面板)');
397
+ }
391
398
  // 对话流 + 授权待决:host 每趟把被观看会话的**新内容**与待决授权一起回来
392
399
  // (见 host 侧 lib/bridge-thread.mjs / lib/bridge-approval.mjs),面板有变化才重画。
393
400
  if (askPanel !== null && applySync(askPanel.state, result)) refreshAskPanel();
@@ -539,8 +546,10 @@ function openAskPanel(mode = 'selection') {
539
546
  }
540
547
  const panel = vscode.window.createWebviewPanel(
541
548
  'dshAsk',
542
- 'DSH 提问',
543
- { viewColumn: vscode.ViewColumn.Beside, preserveFocus: false },
549
+ 'DSH 对话',
550
+ // 对话框形状(0.2.4):开在**当前编辑器组**,占满工作台宽度 —— 不再用 `Beside` 挤成一条侧栏。
551
+ // 面板内容自己居中限宽,看起来就是编辑器上的一扇对话窗;右上角的 ✕ 关掉它。
552
+ { viewColumn: vscode.ViewColumn.Active, preserveFocus: false },
544
553
  {
545
554
  enableScripts: true,
546
555
  retainContextWhenHidden: true,
@@ -568,6 +577,11 @@ function openAskPanel(mode = 'selection') {
568
577
  }
569
578
  if (message.type === 'approve' && typeof message.id === 'string') {
570
579
  void decideApproval(message.id, message.outcome);
580
+ return;
581
+ }
582
+ if (message.type === 'close') {
583
+ // 面板上的 ✕:关掉它(= 不再看这个会话 ⇒ 待决授权立刻交回 DSH 界面,不用干等窗口)。
584
+ panel.dispose();
571
585
  }
572
586
  });
573
587
  panel.onDidDispose(() => {
@@ -576,7 +590,8 @@ function openAskPanel(mode = 'selection') {
576
590
  }
577
591
  askPanel.state.context = contextInfo;
578
592
  askPanel.mode = mode;
579
- askPanel.panel.reveal(vscode.ViewColumn.Beside, false);
593
+ // 已经开着就提到前面(对话框不重复开)。
594
+ askPanel.panel.reveal(vscode.ViewColumn.Active, false);
580
595
  refreshAskPanel();
581
596
  }
582
597
 
@@ -600,6 +615,20 @@ async function openAskPanelFor(mode) {
600
615
  vscode.window.showInformationMessage('没有活动的编辑器:请先打开一个文件。');
601
616
  return;
602
617
  }
618
+ // 0.2.5 起优先用 **DSH 页面里的悬浮对话框**(用户要的"对话框形式":浮在 DSH 界面上,不占编辑器版面)。
619
+ // 宿主能力由 /sync 的 askDialog 字段探测;老宿主(探测不到)才退回编辑器里的 webview 面板。
620
+ if (askDialogSupported) {
621
+ try {
622
+ const result = await client.askOpen(mode);
623
+ if (result !== null && result.ok === true) {
624
+ log(`已请宿主打开对话对话框(mode=${mode}${typeof result.contextText === 'string' ? `,${result.contextText}` : ''})`);
625
+ return;
626
+ }
627
+ log(`宿主没有打开对话框(${result !== null && result.error ? result.error : '未知原因'})→ 退回编辑器面板`);
628
+ } catch (error) {
629
+ log(`ask-open 失败(${error && error.message ? error.message : error}) → 退回编辑器面板`);
630
+ }
631
+ }
603
632
  openAskPanel(mode);
604
633
  }
605
634
 
@@ -74,6 +74,10 @@ function cleanEntry(raw) {
74
74
  return {
75
75
  role,
76
76
  text,
77
+ // 思考过程(0.3.23):渲染成默认收起的「思考」行。
78
+ thinking: typeof raw.thinking === 'string' ? raw.thinking.slice(0, MAX_TEXT) : '',
79
+ // 注入的上下文(0.3.24):桥自己拼的位置行 + 选区代码块,渲染成默认收起的「上下文」行。
80
+ context: typeof raw.context === 'string' ? raw.context.slice(0, MAX_TEXT) : '',
77
81
  streaming: raw.streaming === true,
78
82
  name: typeof raw.name === 'string' ? raw.name : null,
79
83
  summary: typeof raw.summary === 'string' ? raw.summary : null,
@@ -84,13 +88,21 @@ function cleanEntry(raw) {
84
88
  }
85
89
 
86
90
  /**
87
- * 条目签名:正文只会"流式变长"或"被耐久消息原地替换"(此时 streaming 翻转),
91
+ * 条目签名:正文/思考只会"流式变长"或"被耐久消息原地替换"(此时 streaming 翻转),
88
92
  * 所以 角色 + 状态 + 长度 + 是否流式 足够判变化,不必每趟比对几十 KB 文本。
89
93
  */
90
94
  function entriesSignature(entries) {
91
95
  const parts = [];
92
96
  for (const entry of entries) {
93
- parts.push(`${entry.role}:${entry.status ?? ''}:${entry.streaming ? 1 : 0}:${entry.text.length}:${entry.summary === null ? '' : entry.summary.length}`);
97
+ parts.push([
98
+ entry.role,
99
+ entry.status ?? '',
100
+ entry.streaming ? 1 : 0,
101
+ entry.text.length,
102
+ entry.thinking.length,
103
+ entry.context.length,
104
+ entry.summary === null ? '' : entry.summary.length,
105
+ ].join(':'));
94
106
  }
95
107
  return `${entries.length}|${parts.join(',')}`;
96
108
  }
@@ -100,6 +112,8 @@ function pendingEntries(state) {
100
112
  return state.pending.map((item) => ({
101
113
  role: 'user',
102
114
  text: item.text,
115
+ thinking: '',
116
+ context: '',
103
117
  streaming: false,
104
118
  name: null,
105
119
  summary: null,
@@ -364,9 +378,9 @@ function renderPanelHtml({ cspSource, nonce, scriptUri, styleUri }) {
364
378
  <meta charset="utf-8">
365
379
  <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${cspSource} data:; font-src ${cspSource}; style-src ${cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}';">
366
380
  <link rel="stylesheet" href="${style}">
367
- <title>DSH 提问</title>
381
+ <title>DSH 对话</title>
368
382
  </head>
369
- <body>
383
+ <body class="dshcs-panel">
370
384
  <div id="root"></div>
371
385
  <script nonce="${nonce}" src="${script}"></script>
372
386
  </body>
@@ -290,11 +290,24 @@ function createClient(options) {
290
290
  approvals: body !== null && Array.isArray(body.approvals) ? body.approvals : [],
291
291
  approvalHoldMs: body !== null && Number.isSafeInteger(body.approvalHoldMs) ? body.approvalHoldMs : undefined,
292
292
  uiVersion: body !== null && typeof body.uiVersion === 'string' ? body.uiVersion : undefined,
293
+ // 能力探测(0.3.24):宿主支持 DSH 页面里的悬浮对话框 ⇒ 提问改走 ask-open 事件(不开 webview 面板)。
294
+ askDialog: body !== null && body.askDialog === true,
293
295
  };
294
296
  } catch (error) {
295
297
  return { ok: false, error: error.message, status: error.status, code: error.code };
296
298
  }
297
299
  },
300
+ /**
301
+ * 请宿主打开 DSH 页面里的「问 DSH」对话框(0.3.24)。
302
+ * 只上报警图(mode),上下文由宿主从它自己的编辑器状态缓存里取 —— 扩展不用重复发一份。
303
+ */
304
+ async askOpen(mode) {
305
+ return request(`${BRIDGE_BASE}/event`, {
306
+ method: 'POST',
307
+ headers: { 'content-type': 'application/json' },
308
+ body: JSON.stringify({ kind: 'ask-open', mode: mode === 'file' ? 'file' : 'selection' }),
309
+ });
310
+ },
298
311
  /** 把"选中内容 + 问题"投给 DSH 的当前会话。 */
299
312
  async ask(payload) {
300
313
  return request(`${BRIDGE_BASE}/ask`, {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dshcs-editor-bridge",
3
3
  "displayName": "DSH Editor Bridge",
4
- "description": "只读地把编辑器状态(未保存缓冲区、诊断、活动选区)提供给 DSH,并让编辑器里的动作驱动 DSH 会话;0.2.3 起「问 DSH」面板用 DSH 官方 markdown 渲染器显示该会话的新内容,并可就地回答授权请求(写工作区外文件 / 执行命令)。由 dsh-code-server-app 插件安装,可禁用。",
5
- "version": "0.2.3",
4
+ "description": "只读地把编辑器状态(未保存缓冲区、诊断、活动选区)提供给 DSH;右键提问打开 DSH 页面里的悬浮对话对话框(0.2.5;老宿主退回编辑器面板),正文/思考/上下文都用 DSH 官方渲染器,并可就地回答授权请求。由 dsh-code-server-app 插件安装,可禁用。",
5
+ "version": "0.2.5",
6
6
  "publisher": "dsh-code-server-app",
7
7
  "license": "MIT",
8
8
  "private": true,
@@ -11,9 +11,9 @@
11
11
  | react | 18.3.1 | MIT |
12
12
  | react-dom | 18.3.1 | MIT |
13
13
 
14
- 生成时间:2026-09-13T08:28:22.125Z
14
+ 生成时间:2026-09-13T09:15:47.607Z
15
15
  渲染器:@deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.2(DSH 部署的界面版本:0.1.5-rc.2)
16
- 设计令牌:@deepseek-ai/dsh-client-ui-theme@0.1.5-rc.2(5 段)
16
+ 设计令牌:@deepseek-ai/dsh-client-ui-theme@0.1.5-rc.2(11 段)
17
17
  KaTeX:已打包(公式排版与 DSH 界面一致)
18
18
  shiki 语法:typescript / shellscript / json(与 DSH 启动集一致)
19
19
  令牌自检:通过
@@ -26,6 +26,13 @@ const RENDERER_PACKAGE = typeof __DSHCS_RENDERER_PACKAGE__ === 'string' ? __DSHC
26
26
 
27
27
  const vscode = acquireVsCodeApi();
28
28
 
29
+ /** 宿主形态(0.2.5):`'dsh'` = 跑在 DSH 页面里的浮动对话框里,否则是 VS Code webview 面板。 */
30
+ const HOST = typeof window.__DSHCS_HOST__ === 'string' ? window.__DSHCS_HOST__ : 'webview';
31
+ /** 挂载点:对话框把它的容器交进来;webview 里就是 #root。 */
32
+ const MOUNT = window.__DSHCS_MOUNT__ instanceof HTMLElement
33
+ ? window.__DSHCS_MOUNT__
34
+ : document.getElementById('root');
35
+
29
36
  /** 面板外壳的初始视图状态。 */
30
37
  const INITIAL = panel.createViewState();
31
38
 
@@ -40,7 +47,17 @@ function App() {
40
47
  const stickRef = useRef(true);
41
48
  const boxRef = useRef(null);
42
49
 
43
- // 扩展 → 面板的消息。
50
+ // 对话框形态:壳子直接推状态(同一个 window,不走 postMessage —— DSH 页面里 postMessage 是公共广播)。
51
+ useEffect(() => {
52
+ if (HOST !== 'dsh') return undefined;
53
+ window.__DSHCS_ASK_PUSH__ = (payload) => {
54
+ if (payload === null || typeof payload !== 'object') return;
55
+ setView((prev) => panel.applyPayload(prev, { ...payload, type: 'state' }));
56
+ };
57
+ return () => { delete window.__DSHCS_ASK_PUSH__; };
58
+ }, []);
59
+
60
+ // 扩展 → 面板的消息(webview 形态)。
44
61
  useEffect(() => {
45
62
  const onMessage = (event) => {
46
63
  const data = event.data;
@@ -62,7 +79,9 @@ function App() {
62
79
  }, []);
63
80
 
64
81
  // 暗色主题:官方令牌表用 body[data-ds-dark-theme] 切换调色板。
82
+ // **只在 webview 里做**:在 DSH 页面里那个属性属于 DSH 自己,面板不能去改它(会翻掉整个界面主题)。
65
83
  useEffect(() => {
84
+ if (HOST === 'dsh') return undefined;
66
85
  const sync = () => {
67
86
  const dark = document.body.classList.contains('vscode-dark')
68
87
  || document.body.classList.contains('vscode-high-contrast');
@@ -143,8 +162,18 @@ function App() {
143
162
  return (
144
163
  <div className="dshcs-app">
145
164
  <header className="dshcs-header">
165
+ <span className="dshcs-title">DSH</span>
146
166
  <span className="dshcs-where">{view.contextText}</span>
147
167
  {RENDERER_VERSION === '' ? null : <span className="dshcs-version">渲染器 {RENDERER_VERSION}</span>}
168
+ <button
169
+ type="button"
170
+ className="dshcs-close"
171
+ title="关闭(Shift+Esc)"
172
+ aria-label="关闭"
173
+ onClick={() => vscode.postMessage({ type: 'close' })}
174
+ >
175
+ ✕
176
+ </button>
148
177
  </header>
149
178
 
150
179
  {view.threadError === null ? null : <div className="dshcs-warn">{view.threadError}</div>}
@@ -152,7 +181,7 @@ function App() {
152
181
 
153
182
  <main className="dshcs-log" ref={logRef} onScroll={onScroll}>
154
183
  {view.entries.length === 0
155
- ? <div className="dshcs-empty">在下面提问:DSH 的回答会像 DSH 界面那样显示在这里。</div>
184
+ ? <div className="dshcs-empty">在下面提问:DSH 的回答会像 DSH 界面那样显示在这里(思考折叠、正文按官方渲染)。</div>
156
185
  : view.entries.map((entry, index) => (
157
186
  <ThreadEntry key={`${entry.role}-${index}-${entry.callId ?? entry.approvalId ?? ''}`} entry={entry} />
158
187
  ))}
@@ -186,4 +215,8 @@ function App() {
186
215
  );
187
216
  }
188
217
 
189
- createRoot(document.getElementById('root')).render(<App />);
218
+ if (MOUNT === null || MOUNT === undefined) {
219
+ console.error('[dshcs] 面板挂载点不存在(#root 或 window.__DSHCS_MOUNT__)');
220
+ } else {
221
+ createRoot(MOUNT).render(<App />);
222
+ }
@@ -1,32 +1,41 @@
1
- // dshcs-editor-bridge / webview/src/approval.jsx —— 待决授权卡片(0.2.3)
1
+ // dshcs-editor-bridge / webview/src/approval.jsx —— 待决授权卡片(0.2.3;0.2.4 修"失效")
2
2
  //
3
3
  // 背景(见 lib/bridge-approval.mjs):DSH 的敏感动作(写工作区外的文件、执行命令)会走
4
4
  // `approval/request` 这条 agent 作用域的 waterfall。面板在旁边看的时候先接住它:
5
5
  // - 用户在这张卡片上点「允许一次 / 拒绝」→ 立刻生效(官方卡片不再出现);
6
- // - 窗口内没人答(倒计时走完)→ 交回官方链路,DSH 界面里照旧弹同一张卡片。
6
+ // - **面板关掉**(或等满宿主给的窗口,默认 5 分钟)→ 交回官方链路,DSH 界面里照旧弹同一张卡片。
7
+ //
8
+ // 0.2.3 的教训(用户实测:"授权框失效了"):卡片自己按 8 秒判过期 → 读完再点,按钮已经是灰的。
9
+ // 现在**不再由前端判过期**:只要卡片还在(宿主这边仍未决),按钮就可点;请求被交回官方链路时
10
+ // 它会从 `approvals` 里消失,面板改为显示审计行。倒计时只是"还有多久交回官方链路"的提示,
11
+ // 不再决定能不能点。
7
12
  //
8
13
  // 卡片只做两件事:**显示**这次请求要干什么(工具名 + 原因),**回答**它。
9
14
  // 没有任何"记住这次选择""以后都允许"的入口 —— 授权只对这一次动作有效。
10
15
 
11
- /** 毫秒 → 剩余秒数(向上取整,0 表示已过期)。 */
12
- function secondsLeft(item, holdMs, now) {
13
- const deadline = (Number.isSafeInteger(item.at) ? item.at : now) + holdMs;
14
- return Math.max(0, Math.ceil((deadline - now) / 1000));
16
+ /** 剩余时间文案(超过一分钟就 mm:ss,避免秒数跳动带来的紧张感)。 */
17
+ function remainingText(item, holdMs, now) {
18
+ const at = Number.isSafeInteger(item.at) ? item.at : now;
19
+ const left = Math.max(0, at + holdMs - now);
20
+ if (left === 0) return '已交回 DSH 界面';
21
+ const totalSeconds = Math.ceil(left / 1000);
22
+ const minutes = Math.floor(totalSeconds / 60);
23
+ const seconds = totalSeconds % 60;
24
+ return minutes > 0
25
+ ? `${minutes} 分 ${String(seconds).padStart(2, '0')} 秒后交回 DSH 界面`
26
+ : `${seconds} 秒后交回 DSH 界面`;
15
27
  }
16
28
 
17
29
  export function ApprovalCard({ item, holdMs, now, decided, onDecide }) {
18
- const left = secondsLeft(item, holdMs, now);
19
- const expired = left <= 0;
20
30
  const choice = decided ?? null;
21
- const locked = expired || choice !== null;
31
+ // 已提交(点了但宿主还没确认):锁住按钮避免连点;这张卡片何时消失由宿主决定。
32
+ const locked = choice !== null;
22
33
 
23
34
  return (
24
35
  <div className="dshcs-approval" data-decided={choice ?? undefined}>
25
36
  <div className="dshcs-approval-head">
26
37
  <span className="dshcs-approval-title">需要你的授权</span>
27
- <span className="dshcs-approval-timer">
28
- {expired ? '已交给 DSH 界面' : `${left}s 后交给 DSH 界面`}
29
- </span>
38
+ <span className="dshcs-approval-timer">{remainingText(item, holdMs, now)}</span>
30
39
  </div>
31
40
  <div className="dshcs-approval-body">
32
41
  <span className="dshcs-approval-tool">{item.toolName}</span>
@@ -39,14 +48,13 @@ export function ApprovalCard({ item, holdMs, now, decided, onDecide }) {
39
48
  <button type="button" disabled={locked} onClick={() => onDecide(item.id, 'rejected')}>
40
49
  拒绝
41
50
  </button>
42
- {choice === null ? null : (
43
- <span className="dshcs-approval-note">
44
- {choice === 'allowed-once' ? '已允许一次(DSH 继续执行)' : '已拒绝'}
45
- </span>
46
- )}
47
- {choice === null && expired ? (
48
- <span className="dshcs-approval-note">这条请求已交给 DSH 界面,请在那边处理</span>
49
- ) : null}
51
+ {choice === null
52
+ ? <span className="dshcs-approval-note">只对这一次动作有效;关掉面板即交回 DSH 界面</span>
53
+ : (
54
+ <span className="dshcs-approval-note">
55
+ {choice === 'allowed-once' ? '已允许一次(DSH 继续执行)' : '已拒绝'}
56
+ </span>
57
+ )}
50
58
  </div>
51
59
  </div>
52
60
  );