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.
- package/README.en.md +24 -11
- package/README.md +16 -8
- package/assets/extensions/dshcs-editor-bridge/extension.js +32 -3
- package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +18 -4
- package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +13 -0
- package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
- package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +2 -2
- package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +36 -3
- package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +28 -20
- package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +573 -7
- package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +108 -11
- package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +89 -10
- package/assets/extensions/dshcs-editor-bridge/webview/thread.css +1 -1
- package/assets/extensions/dshcs-editor-bridge/webview/thread.js +66 -63
- package/lib/bridge-approval.mjs +48 -14
- package/lib/bridge-session.mjs +40 -0
- package/lib/bridge-thread.mjs +80 -24
- package/lib/client.js +2 -2
- package/lib/index.js +240 -14
- package/package.json +2 -2
- package/vendor/VENDOR.json +1 -1
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- 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
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
- **
|
|
225
|
-
|
|
226
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
-
|
|
216
|
-
|
|
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` | 授权窗口长度(默认
|
|
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) 没有面板在看 / 窗口超时(默认
|
|
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
|
-
- **面板里的授权窗口是
|
|
736
|
-
—— 交回之后这一条**只能**在 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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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(
|
|
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
|
|
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
|
|
5
|
-
"version": "0.2.
|
|
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-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
// -
|
|
6
|
+
// - **面板关掉**(或等满宿主给的窗口,默认 5 分钟)→ 交回官方链路,DSH 界面里照旧弹同一张卡片。
|
|
7
|
+
//
|
|
8
|
+
// 0.2.3 的教训(用户实测:"授权框失效了"):卡片自己按 8 秒判过期 → 读完再点,按钮已经是灰的。
|
|
9
|
+
// 现在**不再由前端判过期**:只要卡片还在(宿主这边仍未决),按钮就可点;请求被交回官方链路时
|
|
10
|
+
// 它会从 `approvals` 里消失,面板改为显示审计行。倒计时只是"还有多久交回官方链路"的提示,
|
|
11
|
+
// 不再决定能不能点。
|
|
7
12
|
//
|
|
8
13
|
// 卡片只做两件事:**显示**这次请求要干什么(工具名 + 原因),**回答**它。
|
|
9
14
|
// 没有任何"记住这次选择""以后都允许"的入口 —— 授权只对这一次动作有效。
|
|
10
15
|
|
|
11
|
-
/**
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
43
|
-
<span className="dshcs-approval-note">
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
);
|