Ckarefulon/dsh-deepseek-webchat ↗★ 0

@ckarefulon/dsh-deepseek-webchat

在侧边栏嵌入官方网页版并绑定会话上下文 适合想免API Key直接在侧边栏使用官方网页版并推送上下文的用户。

套件
@ckarefulon/dsh-deepseek-webchat
相容性
待驗證
版本
0.3.2
授權
MIT
最近更新
2026年10月8日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:Ckarefulon/dsh-deepseek-webchat

dsh-deepseek-webchat

A DSH sidebar plugin that shows the real chat.deepseek.com web app next to your session, binds one DeepSeek conversation to each DSH Session, and lets you push selected messages into the DeepSeek composer as a blockquote — which you then send yourself.

No API key, no proxy, no scraping. It embeds the actual website through the desktop shell's native browser guest, the same channel DSH's built-in side-card browser uses.

What it does

Real web appchat.deepseek.com itself, in a sidebar tab. Not an iframe, not the API.
Login persistsSign in once; the plugin restores your session on later launches.
One conversation per DSH SessionEach DSH Session gets its own DeepSeek conversation, remembered by id.
Follows the session you switch toSwitch DSH Sessions and the sidebar follows to that Session's conversation.
Manual context pushTick rows, add a question, preview it, then fill and send.
Questions includedThe agent's questions to you, their options, and your answers are quotable too.
Todo list includedThe agent's todo list is quotable without turning anything on.
One click to send「填入并发送」 fills the composer and submits it; 「仅填入」 stops there.
Bulk selectionSelect all, clear, or take the last 5/10/20/50 rows in one click.
Advanced opt-inA switch reveals thinking, tool calls and results — off by default.
Explained contextAn optional note tells DeepSeek what the quotes are and where they came from.
No back-flowDeepSeek's answers are never written back into your DSH conversation.

Install

Requires DSH Desktop 0.2.0-rc.1 or newer, and a profile that is already initialised (the desktop profile is created by the app on first run).

dsh plugin --profile desktop add github:Ckarefulon/dsh-deepseek-webchat

Then restart DSH Desktop. The tab appears in the sidebar column's guide, and a chat icon in the conversation header opens it directly.

lib/ is committed to this repository on purpose, and there is no prepare script, so the install needs no build step and no pnpm allowBuilds approval.

Installing from a local clone instead

git clone https://github.com/Ckarefulon/dsh-deepseek-webchat
dsh plugin --profile desktop add /absolute/path/to/dsh-deepseek-webchat

Uninstalling

dsh plugin --profile desktop remove @ckarefulon/dsh-deepseek-webchat

Your bindings and saved login live in ~/.dsh/dsh-deepseek-webchat/ and are left in place; delete that directory to remove them too.

Troubleshooting: DSH will not start after installing

DSH shows "The application could not start or stopped unexpectedly" and refuses to open. The crash log (the path is shown in the dialog, under %APPDATA%\@deepseek-ai\dsh-desktop\logs\) contains client-modules: client bundle not found.

That means the installed copy's exports["./client"] does not point at a real file. Fix it by reinstalling the plugin from a commit that has the flat layout:

dsh plugin --profile desktop remove @ckarefulon/dsh-deepseek-webchat
dsh plugin --profile desktop add github:Ckarefulon/dsh-deepseek-webchat

To confirm the layout before restarting, from the installed package directory:

node -e "const p=require('./package.json');const r=p.exports['./client'].default;console.log(r, require('fs').existsSync(r))"

It must print ./lib/client.js true. If it prints false, do not restart DSH — the boot will abort again.

The dialog's "Disable third-party plugins, back up profile patch, and restart" button also works: it starts DSH with third-party plugins unmounted and saves your profile patch alongside as cordis.patch.yml.bak-. That backup is your configuration, not the plugin, so keep it.

Using it

  1. Open the tab. Click the chat icon in the conversation header, or pick 「DeepSeek 网页版」 from the sidebar column's guide.
  2. Sign in to DeepSeek the first time. It is a normal browser login, inside the panel.
  3. Send one message in the DeepSeek page. DeepSeek creates a conversation lazily, so this is the moment its id exists — the plugin notices and binds it to the current DSH Session. The toolbar dot turns green.
  4. Quote context. Press 「引用上下文」. The list opens scrolled to the newest rows. Tick what you want — or use 「全选」, 「清除」, or the number box plus 「选末尾」 to take the last few in one go — optionally type a question or instruction, and check the preview. The list holds your turns, the assistant's replies, the agent's todo list, the questions it asked you (with the options it offered), and your answers to them.
  5. Press 「填入并发送」. The text goes into DeepSeek's composer and is submitted for you; the panel returns to the page and the selection clears, so the next push starts from a clean slate. Use 「仅填入」 instead if you would rather read it over in the composer first.
  6. That is the send. The plugin watches the composer to confirm it went out, and says so — or tells you plainly that it did not, so you can press Enter there yourself.

The toolbar also has 「刷新」 (reload the page) and 「解除绑定」 (forget this Session's conversation, so the next visit starts a new one).

Switching to a different DSH Session moves the panel to that Session's conversation automatically.

What gets sent, and how

Every push is still something you asked for: the plugin fills the composer and submits it only when you click 「填入并发送」. There is no timer, no background upload, and nothing is sent merely because the panel is open.

Sending goes through DeepSeek's own composer handler rather than a synthetic shortcut invented here. Its composer runs an onKeyDown that, for a plain Enter, calls the same function its send button calls — so the plugin dispatches a bubbling Enter at the textarea and lets DeepSeek do the rest. (Its button's class name is a build-time hash, so guessing at it would break on any rebuild.)

Because a synthetic key press could in principle be ignored, the plugin does not assume it worked. DeepSeek clears its textarea once a send is accepted, so the plugin reads the composer back: cleared means sent. If it still holds text, the plugin falls back to clicking DeepSeek's own send button, and if that fails too it says so instead of pretending — leaving the text sitting in the composer for you to send by hand.

「附上说明」 (on by default) puts a short note in front of the quoted rows:

以下是我在另一个 AI 助手(DSH)里的对话片段,引用给你作为背景参考。以「> 」 开头的行是原文,【】里标出的是这段内容属于谁…

Without it, DeepSeek receives a wall of quotes and 【】 labels with no explanation of what they are. Turn it off if you would rather send the raw quotes.

The advanced switch

By default the picker lists the conversation as a person reads it, plus the agent's todo list. Tick 「高级:包含思考过程与工具调用」 to also list the machinery behind it: the model's reasoning blocks, its tool calls, the tool results, slash commands, and compaction summaries — including tool output that may contain file contents. It is off on every open, and the host does the filtering, so an advanced row is never even sent to the browser unless you asked for it.

Each row is marked with an icon from the shell's own icon set plus a short label, so a quoted answer and a quoted tool result stay distinguishable in the blockquote:

> 【提问】Confirm:Which layout?
>   · Sidebar — right column
>   · Center
> 【回答】Sidebar, please
> 【待办】[x] write the picker
> [~] test it

Privacy

This plugin is built so that nothing leaves your machine unless you press a button, and so that the things you would least want to leak are never even offered.

What is sent, and when. Only the rows you tick, and only when you click 「填入并发送」 (or 「仅填入」, which stops at the composer). Both are explicit actions; the plugin has no timer and no background upload.

What is never offered. The host half filters the session log before it reaches the picker, dropping in both modes:

  • system/message and developer/message events,
  • injected user/message events — agent instructions, skill catalogs, goal rounds, time context, compaction checkpoints, team and webhook deliveries, which DSH records as user messages but which you never typed. Only events whose source.kind is user (your turns) or user-question-reply (your answers to the agent) are treated as yours,
  • approval records, request headers, model and sandbox selections.

What is off by default. The model's private reasoning blocks, tool calls and tool results are not offered until you turn on the advanced switch. When you do, remember that a tool result can contain file contents the agent read — that is exactly the kind of thing the default set keeps out, and the switch is your explicit say-so. A todo list is the exception: it is the agent's own plan, which you are already reading on screen, so it is offered by default.

So a system prompt, an API key in your environment, or a file the agent read cannot be quoted by accident.

What is stored locally. Two JSON files in ~/.dsh/dsh-deepseek-webchat/:

  • bindings.json — the DSH Session id → DeepSeek conversation id map. No message content, ever.
  • session.json — the guest's DeepSeek login state (its site storage and non-HttpOnly cookie string), so you stay signed in. This is a credential; treat the file as you would a browser profile. It is written with the same permissions as any other file your user creates.

No telemetry. The plugin makes no network request of its own. The only traffic is the DeepSeek web app talking to DeepSeek, inside its own guest.

The page runs sandboxed. The desktop shell approves the guest with nodeIntegration: false, contextIsolation: true, sandbox: true and webSecurity: true, and refuses every permission request. The page cannot reach your files or your DSH process.

Known limitations

These are real, and worth knowing before you rely on the plugin.

HttpOnly cookies cannot be restored. The DSH Host runs as a separate Node child process, not inside Electron, so the plugin cannot reach Electron's cookie store. It can only read document.cookie from inside the guest, and HttpOnly cookies are invisible there by design. In practice DeepSeek keeps its credential in localStorage.userToken and sends it as a bearer token, so replaying site storage is what restores your login — but if DeepSeek ever moves its credential to an HttpOnly cookie, this plugin will start asking you to sign in again after each restart.

The guest partition is per-run. The shell hands out a process-lifetime session partition and the plugin cannot ask for a different one. Login survival is therefore the snapshot-and-replay above, not a persistent browser profile.

One DeepSeek login, shared. Every DSH Session shares a single DeepSeek account and browser session; the per-Session split is the conversation, not the login. Separate DSH Sessions do not get separate DeepSeek accounts.

Binding happens on the first message. DeepSeek creates a conversation only when you send something, so a DSH Session stays unbound until you send at least one message in the page. Until then the toolbar dot is hollow.

DeepSeek can change its page. The composer fill targets textarea.ds-textarea__textarea, and the conversation id is read from DeepSeek's /a//s/ route. If DeepSeek redesigns either, the fill or the binding stops working until this plugin is updated. The rest of the plugin — embedding, login, per-Session tabs — is unaffected.

Desktop only. The embedded page needs the desktop browser guest channel. In a plain dsh web profile the panel says so instead of falling back to an iframe (which DeepSeek would refuse anyway, via frame-ancestors 'none').

One DeepSeek tab per DSH Session. The tab kind is single-instance, so you cannot open two DeepSeek panels side by side for the same Session.

How it works

Architecture

Two halves, as DSH plugins are:

Host half (lib/index.js, exports .) runs in the DSH Host process and serves five routes under /api/dsh-deepseek-webchat/:

RoutePurpose
stateWhat the panel needs to describe itself.
messages?sessionId=[&advanced=1]The Session's quotable rows, filtered.
bindingRead/write/forget the DSH→DeepSeek conversation map.
session/restoreHand back the saved login state.
session/saveStore the guest's login state.

It reads messages through ctx.sessionQuery.readSession(), which is live-preferred, and reduces the raw event log with the same text-extraction rules the harness itself uses.

The advanced flag is honoured host-side, so the default response never contains a reasoning or tool row at all — the browser half cannot accidentally reveal one it was never given. The picker re-requests the list when the switch flips rather than filtering locally.

Rows carry a kind alongside role: user and assistant for dialogue, question and answer for the agent's questions and your replies (default), then reasoning, tool-call, tool-result, todo, command and summary (advanced). A question row is read from the tool/call event whose name === 'ask_user_question', parsed with the same questionsOf shape the harness's own user-questions service uses; the answer arrives as an ordinary user/message tagged source.kind === 'user-question-reply', which the plain human filter would otherwise drop.

Browser half (lib/client.js, exports ./client) registers the sidebar tab type, its body, and a header door.

The guest, and why it is not an iframe. chat.deepseek.com sends Content-Security-Policy: frame-ancestors 'none', so an is refused by the page. Instead the bundle asks `globalThis.dshDesktop.browser.acquire(...)` for a reservation and attaches a whose src is about:blank# and whose partition is the issued one. The shell's will-attach-webview matches the lease, applies hardened preferences, and lets the navigation through.

Why the guest lives outside React. A `` is destroyed when it leaves the document, so a slot-owned guest would reload on every tab switch — losing a half-typed question. The element instead lives in a container the plugin owns at the document root, and the panel body only measures its area; the guest is positioned onto that rectangle while the tab shows and hidden when it does not. The tab type declares keepMounted: true so the body survives switches.

Why the conversation URL works. DeepSeek's own routes are /a/:agentId/s/:sessionId, and the default agent is chat, so a conversation is addressable as https://chat.deepseek.com/a/chat/s/ — no in-page JavaScript is needed to reopen one.

Development

Plain JavaScript ESM. No TypeScript, no bundler, no dependencies.

npm run build   # copy src/ -> lib/
npm test        # node --test

lib/ is a byte-for-byte copy of src/ and is committed, because a git-hosted plugin must not need a build step on install. npm test asserts the two still match, so they cannot drift.

After editing src/, run npm run build and commit both trees.

src/index.js          host half    -> lib/index.js
src/client/index.js   browser half -> lib/client.js   (note: flat, not lib/client/index.js)
scripts/build.mjs     the copy
test/index.test.js    host: u