dsh-vscode-bridge-he 1.0.4 → 1.0.5

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 (4) hide show
  1. package/README.md +100 -53
  2. package/client.js +76 -2
  3. package/index.js +6 -0
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,53 +1,100 @@
1
- # DSH VSCode Bridge
2
-
3
- Adds a bridge between VS Code and the current DSH conversation.
4
-
5
- - VS Code right-click on a selection → `DSH: 发送选中代码到 DSH 对话`
6
- - VS Code right-click on a file in the Explorer → `DSH: 发送文件路径到 DSH 对话`
7
- - DSH receives an `@absolute/path` reference (plus `(line N)` / `(lines A-B)` for a selection) and inserts it at the current composer caret. It does **not** auto-send the message.
8
-
9
- ## Architecture
10
-
11
- ```text
12
- VS Code extension
13
- └─ HTTP POST /dsh-vscode/attach (Bearer token)
14
- └─ DSH Host plugin (this package)
15
- └─ SSE /dsh-vscode/events
16
- └─ DSH browser plugin
17
- └─ conversation.input.left → InputActions.insertText()
18
- ```
19
-
20
- The Host half writes its endpoint and token to `~/.dsh/vscode-bridge.json` (override with `DSH_VSCODE_BRIDGE_FILE`). The VS Code extension reads that file automatically, so no manual token copying is needed. The token file is regenerated when the DSH process/plugin starts.
21
-
22
- When no DSH conversation is open, attachments are queued in memory and flushed to the next browser session that connects.
23
-
24
- ## DSH-side install
25
-
26
- This directory is a local DSH bundle. Install it with:
27
-
28
- ```text
29
- plugin_manager install_bundle target=C:\...\dsh-vscode-bridge
30
- ```
31
-
32
- After installation, refresh the DSH page once (the client plugin and its injected bridge config are added to the page boot). The bundle is enabled in the profile and survives restarts.
33
-
34
- ## Endpoints
35
-
36
- | Method | Path | Auth | Purpose |
37
- | --- | --- | --- | --- |
38
- | POST | `/dsh-vscode/attach` | Bearer token | Queue/deliver one reference |
39
- | GET | `/dsh-vscode/events` | `token` + `channel` query | SSE stream for the browser half |
40
- | GET | `/dsh-vscode/config` | same-origin page (or Desktop's authenticated forwarded request) | Supplies the browser half with the current token/channel |
41
- | GET | `/dsh-vscode/health` | none | Connection/queue status for the VS Code status command |
42
-
43
- The DSH Desktop renderer runs at `dsh-app://app/` and forwards non-asset
44
- requests to the Host with its authentication cookie, so the browser half uses
45
- relative `/dsh-vscode/*` URLs and works both in the Desktop window and in a
46
- normal browser tab.
47
-
48
- ## Notes
49
-
50
- - Paths are sent as absolute paths so DSH does not depend on its workspace matching the VS Code workspace.
51
- - The prompt section `vscode-references` teaches the model that `(lines A-B)` means an inclusive 1-based range to read.
52
- - The browser half renders nothing; it only subscribes and inserts.
53
- - The bridge is loopback-only by design.
1
+ # DSH VSCode Bridge
2
+
3
+ Send a VS Code selection or a file path into the conversation you already have open in
4
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) — as an `@path`
5
+ reference, inserted at the composer caret. It does **not** send the message for you.
6
+
7
+ > **This package is only the DSH half.** It needs the companion VS Code extension to do
8
+ > anything; installing this alone gives you an idle bridge. Both halves are in the
9
+ > [repository](https://github.com/heyitaowml-ctrl/dsh-vscode-bridge):
10
+ > `dsh-vscode-bridge/` (this package) and `vscode-dsh-bridge/` (the extension).
11
+
12
+ ## Install
13
+
14
+ **1 · DSH half** — one command, then refresh the DSH page once:
15
+
16
+ ```sh
17
+ dsh plugin --profile <profile> add dsh-vscode-bridge-he
18
+ ```
19
+
20
+ **2 · VS Code half** — pick one:
21
+
22
+ - **Extensions view:** open VS Code → Extensions → `…` → **Install from VSIX…** → choose
23
+ [`dist/dsh-vscode-bridge.vsix`](https://github.com/heyitaowml-ctrl/dsh-vscode-bridge/raw/master/dist/dsh-vscode-bridge.vsix)
24
+ - **Terminal:**
25
+ ```sh
26
+ code --install-extension dsh-vscode-bridge.vsix
27
+ ```
28
+
29
+ Reload the VS Code window afterwards (`Ctrl+Shift+P` → **Developer: Reload Window**); the
30
+ commands appear under **DSH** in the editor and Explorer context menus.
31
+
32
+ The extension discovers the Host by itself: DSH writes its endpoint and token to
33
+ `~/.dsh/vscode-bridge.json` (override with `DSH_VSCODE_BRIDGE_FILE`) on every start, so
34
+ there is no token to copy by hand.
35
+
36
+ ### Finding out whether the extension is connected
37
+
38
+ The composer shows a small `⚠ VS Code 未连接` pill until the extension has sent its first
39
+ reference. It disappears on first use and stays hidden afterwards; the `×` dismisses it for
40
+ the current page.
41
+
42
+ Zero references is the only signal the Host has, so the pill cannot tell "extension not
43
+ installed" apart from "installed but never used" — it deliberately says *not connected*
44
+ rather than *not installed*. If you have just installed the extension and the pill stays,
45
+ reload the VS Code window.
46
+
47
+ ## Usage
48
+
49
+ - Select lines, right-click in the editor → **DSH: 发送选中代码到 DSH 对话**
50
+ - Right-click a file or folder in the Explorer → **DSH: 发送文件路径到 DSH 对话**
51
+ - Switch back to DSH: the reference is already at the caret, and nothing has been sent.
52
+
53
+ ```text
54
+ @C:\project\src\main.ts (lines 12-28)
55
+ @C:\project\src\main.ts
56
+ @"C:\project\my folder\main.ts" (line 7)
57
+ ```
58
+
59
+ Line markers are inclusive and 1-based; the prompt section `vscode-references` tells the
60
+ model to read that range rather than guess.
61
+
62
+ ## Architecture
63
+
64
+ ```text
65
+ VS Code extension
66
+ └─ HTTP POST /dsh-vscode/attach (Bearer token)
67
+ └─ DSH Host plugin (this package)
68
+ └─ SSE /dsh-vscode/events
69
+ └─ DSH browser plugin
70
+ └─ conversation.input.left → InputActions.insertText()
71
+ ```
72
+
73
+ When no DSH conversation is open, attachments are queued in memory and flushed to the next
74
+ browser session that connects.
75
+
76
+ ## Endpoints
77
+
78
+ | Method | Path | Auth | Purpose |
79
+ | --- | --- | --- | --- |
80
+ | POST | `/dsh-vscode/attach` | Bearer token | Queue or deliver one reference |
81
+ | GET | `/dsh-vscode/events` | `token` + `channel` query | SSE stream consumed by the browser half |
82
+ | GET | `/dsh-vscode/config` | same-origin page | Supplies the browser half with the current token/channel |
83
+ | GET | `/dsh-vscode/health` | none | Connection, queue and `attachments` count for the status command |
84
+
85
+ `/health` and the SSE `hello` frame both report `attachments` (how many references the
86
+ extension has ever sent to this Host instance) and `lastAttachAt`. That pair drives the
87
+ composer hint above.
88
+
89
+ ## Notes
90
+
91
+ - Paths are sent absolute, so DSH does not have to share a workspace with VS Code.
92
+ - The browser half renders nothing except the connection hint.
93
+ - **The bridge is loopback-only by design.** The Host binds locally, every request carries a
94
+ per-process bearer token, and the token file is regenerated on each start.
95
+ - This package is unrelated to [`dsh-vscode-bridge`](https://www.npmjs.com/package/dsh-vscode-bridge)
96
+ on npm, which is a different plugin by a different author.
97
+
98
+ ## License
99
+
100
+ MulanPSL-2.0
package/client.js CHANGED
@@ -1,10 +1,12 @@
1
1
  window.__ModuleLoader__.load({
2
- id: '@local/dsh-vscode-bridge',
2
+ id: 'dsh-vscode-bridge-he',
3
3
  factory(require) {
4
4
  const React = require('react');
5
5
 
6
6
  const RETRY_MS = 500;
7
7
  const MAX_SEEN = 200;
8
+ // Where the install hint points. Kept in sync with the README's Install section.
9
+ const INSTALL_DOC_URL = 'https://github.com/heyitaowml-ctrl/dsh-vscode-bridge#install';
8
10
 
9
11
  /**
10
12
  * Invisible composer participant: it owns the SSE subscription for the
@@ -26,6 +28,14 @@ window.__ModuleLoader__.load({
26
28
  // insertion add a separator only when the current draft needs one.
27
29
  const draft = props.useInput((snapshot) => snapshot.draft);
28
30
 
31
+ // How many references the VS Code extension has ever sent to this Host
32
+ // instance. `null` means the SSE handshake has not answered yet.
33
+ // Zero is the only honest signal we have: it cannot tell "extension not
34
+ // installed" apart from "installed but never used", so the hint says
35
+ // 未连接 (not connected) rather than 未安装 (not installed).
36
+ const [attachments, setAttachments] = React.useState(null);
37
+ const [dismissed, setDismissed] = React.useState(false);
38
+
29
39
  const queueRef = React.useRef([]);
30
40
  const seenRef = React.useRef(new Set());
31
41
  const timerRef = React.useRef(null);
@@ -130,6 +140,7 @@ window.__ModuleLoader__.load({
130
140
  }
131
141
  }
132
142
  queueRef.current.push(item.text);
143
+ setAttachments((current) => (current === null ? 1 : current + 1));
133
144
  flushRef.current();
134
145
  } catch (error) {
135
146
  console.warn('[dsh-vscode-bridge] invalid attachment event', error);
@@ -139,6 +150,12 @@ window.__ModuleLoader__.load({
139
150
  try {
140
151
  const hello = JSON.parse(event.data);
141
152
  console.info('[dsh-vscode-bridge] connected', hello);
153
+ const reported = Number(hello?.attachments ?? 0);
154
+ setAttachments((current) => {
155
+ const known = Number.isFinite(reported) && reported > 0 ? reported : 0;
156
+ if (current === null) return known;
157
+ return Math.max(current, known);
158
+ });
142
159
  } catch {
143
160
  // A malformed hello is not worth surfacing.
144
161
  }
@@ -163,7 +180,64 @@ window.__ModuleLoader__.load({
163
180
  };
164
181
  }, [sessionId]);
165
182
 
166
- return null;
183
+ if (dismissed || attachments === null || attachments > 0) return null;
184
+
185
+ // Small, theme-neutral pill: it inherits the surrounding text colour and
186
+ // only lowers its own opacity, so it stays readable in light and dark.
187
+ return React.createElement(
188
+ 'span',
189
+ {
190
+ style: {
191
+ display: 'inline-flex',
192
+ alignItems: 'center',
193
+ gap: 2,
194
+ height: 22,
195
+ padding: '0 2px 0 8px',
196
+ marginRight: 4,
197
+ fontSize: 11,
198
+ lineHeight: '22px',
199
+ borderRadius: 999,
200
+ border: '1px solid currentColor',
201
+ opacity: 0.6,
202
+ whiteSpace: 'nowrap',
203
+ },
204
+ },
205
+ React.createElement(
206
+ 'a',
207
+ {
208
+ href: INSTALL_DOC_URL,
209
+ target: '_blank',
210
+ rel: 'noreferrer noopener',
211
+ title: '还没收到过 VS Code 扩展发来的引用。没装过就点这里看安装步骤;装过的话,请在 VS Code 里执行 Developer: Reload Window。',
212
+ style: { color: 'inherit', textDecoration: 'none', cursor: 'pointer' },
213
+ onClick: (event) => event.stopPropagation(),
214
+ },
215
+ '⚠ VS Code 未连接',
216
+ ),
217
+ React.createElement(
218
+ 'button',
219
+ {
220
+ type: 'button',
221
+ title: '不再提示',
222
+ 'aria-label': 'dismiss the VS Code extension hint',
223
+ onClick: (event) => {
224
+ event.preventDefault();
225
+ event.stopPropagation();
226
+ setDismissed(true);
227
+ },
228
+ style: {
229
+ border: 'none',
230
+ background: 'transparent',
231
+ color: 'inherit',
232
+ cursor: 'pointer',
233
+ fontSize: 13,
234
+ lineHeight: '18px',
235
+ padding: '0 4px',
236
+ },
237
+ },
238
+ '×',
239
+ ),
240
+ );
167
241
  }
168
242
 
169
243
  return {
package/index.js CHANGED
@@ -206,6 +206,7 @@ export function apply(ctx) {
206
206
  let sequence = 0;
207
207
  let lastClientId;
208
208
  let lastSessionId;
209
+ let lastAttachAt;
209
210
  let published = false;
210
211
  let publishing = false;
211
212
  let publishAttempts = 0;
@@ -386,6 +387,7 @@ export function apply(ctx) {
386
387
  ...attachment,
387
388
  };
388
389
  item.text = formatAttachmentText(item);
390
+ lastAttachAt = item.receivedAt;
389
391
  const outcome = deliver(item);
390
392
  void publishBridgeFile();
391
393
  info(`attachment ${item.id}: ${item.kind} ${item.absolutePath}${item.startLine === undefined ? '' : ` lines ${item.startLine}-${item.endLine}`} delivered=${String(outcome.delivered)}`);
@@ -455,6 +457,8 @@ export function apply(ctx) {
455
457
  connected: true,
456
458
  sessionId: sessionId ?? null,
457
459
  queued: queue.length,
460
+ attachments: sequence,
461
+ lastAttachAt: lastAttachAt ?? null,
458
462
  })}\n\n`);
459
463
  } catch {
460
464
  removeClient(client.id);
@@ -518,6 +522,8 @@ export function apply(ctx) {
518
522
  queued: queue.length,
519
523
  sessionId: lastSessionId ?? null,
520
524
  configFetches,
525
+ attachments: sequence,
526
+ lastAttachAt: lastAttachAt ?? null,
521
527
  });
522
528
  };
523
529
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "dsh-vscode-bridge-he",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "Send a VS Code selection or file path into the open DSH conversation as an @path reference, without sending the message.",
6
6
  "license": "MulanPSL-2.0",