xarleyn/dsh-plugins--plugins-dsh-qa-surface ↗★ 1

@yadsh/dsh-qa-surface

新增 /qa 路由的轻量问答界面,复用原生会话能力提供专属前端 适合需要独立客服或测试入口的团队,需在路由配置中挂载该面板

套件
@yadsh/dsh-qa-surface
相容性
待驗證
Harness 依賴範圍
catalog:dsh
Cordis 依賴範圍
catalog:dsh
版本
0.2.0
授權
MIT
最近更新
2026年9月13日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:xarleyn/dsh-plugins#fd3cdd95651ec4eafe52d510751ba3aba3c9248d&path:plugins/dsh-qa-surface

@yadsh/dsh-qa-surface

A focused, responsive QA/chat page for DeepSeek Harness at /qa. It replaces the presentation, not the harness: prompts, streaming, tools, skills, MCP, memory, persistence, permissions and telemetry continue through the native DSH Session and Agent Loop.

What it does

  • contributes one root-scoped entry to the additive shell.overlay slot;
  • stays invisible outside the configured route;
  • creates or restores one real DSH Session;
  • renders only user text, assistant-visible text and safe status messages;
  • supports streaming, Stop, optional New chat, safe Markdown, copy actions and a responsive first-party-style conversation layout;
  • optionally shows a minimal per-browser chat-history sidebar (ui.showSessionList) whose switching re-runs policy attestation;
  • blocks unsupported approvals/questions instead of auto-approving them;
  • pins locked sessions to read-only + approval=never before Send is enabled;
  • applies a Host-side tool allow-list plus a monotonic execution guard;
  • uses the existing same-origin DSH connection and trust boundary.

It does not add another HTTP server, provider proxy, permissive CORS rule, or custom session persistence.

Install

Install the package where the DSH Host can resolve it, then compose its Loader row. The included cordis.patch.yml is a minimal example:

- insert:
    - id: dsh-qa-surface
      name: "@yadsh/dsh-qa-surface"
      config:
        enabled: true
        route:
          path: /qa
          matchChildren: true

Some DSH patch operations replace a row's complete config instead of deeply merging it. When editing an existing row, provide every value that deployment needs.

DSH 0.1.1-rc.2 serves unknown frontend paths as 404 rather than falling back to index.html. The Host half therefore claims only the configured QA navigation path and redirects it through the canonical / document with a short-lived query marker. The browser restores the requested /qa URL with history.replaceState before mounting the overlay. No second server, duplicate HTML document, permissive route, or unauthenticated config endpoint is added.

Configure

Configuration is registered under the Host settings namespace qa-surface. Composition values form the base layer; normal DSH user settings can override them when the deployment provides writable settings.

config:
  enabled: true
  route:
    path: /qa
    matchChildren: true
  branding:
    title: Внутренний помощник
    subtitle: Отвечает на вопросы о внутренней платформе
    welcomeMessage: Чем могу помочь?
    placeholder: Задайте вопрос…
    logoUrl: null
  session:
    # Pin every chat to a directory (optional, one of):
    # cwd: "D:/qa-docs"        # direct absolute directory pin
    # workspaceId: ""    # or a registered DSH workspace
    policy: browser-persistent
    storageKey: dsh-qa-surface.session
    workspaceId: company-knowledge
    fixedSessionId: null
    agentPreset: qa-assistant
    provider: null
    model: null
    reasoningEffort: null
  ui:
    showHeader: true
    showReset: false
    showStop: true
    showTimestamps: false
    showToolActivity: false
    showReasoning: false
    renderMarkdown: true
    maxContentWidth: 900
    showSessionList: false
  suggestedQuestions:
    - Как запросить доступ?
    - Где лежит инструкция?
  interaction:
    approvals: blocked
    questions: unsupported
  lockdown:
    enabled: true
    enforceFixedAgentPreset: true
    enforceFixedWorkspace: true
    enforceFixedModel: true
    sandboxMode: read-only
    approvalPolicy: never
    permissionPreset: qa-read-only
    allowPermissionChanges: false
    allowSlashCommands: false
    allowSettingsMutation: false
    allowSessionReset: false
    allowSessionRename: false
    allowSessionDelete: false
    allowArbitrarySessionOpen: false
    toolPolicy:
      mode: allow-list
      allow: []

workspaceId is recommended for a deterministic assistant. Without it, DSH uses the Host's normal default working directory. Put the system prompt, tools, skills, knowledge connections and permission policy in agentPreset, not in this UI plugin.

Model override is opt-in: provider and model must be set together. Slash commands are rejected as plain QA input. Reasoning and tool details remain hidden by default. Enabling ui.showReasoning and ui.showToolActivity adds a turn-scoped work disclosure: it stays open while the assistant is working, then collapses to Worked for ... before the final answer. Tool capability is still controlled exclusively by lockdown.toolPolicy.allow; the display flags do not grant tools.

Session policies:

  • browser-persistent restores the id stored in this browser, replacing stale ids with a new session;
  • new-on-load creates a session each time the QA surface mounts;
  • fixed requires fixedSessionId, never creates a replacement, and is meant only for controlled single-user deployments.

New chat is disabled by default. To expose it, set lockdown.allowSessionReset: true plus either ui.showReset: true (header button) or ui.showSessionList: true (sidebar button). Pressing it opens a draft composer and creates nothing: the DSH Session is materialized lazily by the first prompt, so the chat list stays quiet until a message is actually sent, and the old session stays intact for operator inspection. The sidebar orders chats by the host's last update, so merely opening a chat never moves it.

Regeneration: the last committed answer offers a retry action. The session log is append-only, so "regenerate" sends a hidden instruction as an ordinary prompt and the answer arrives as a follow-up turn; the projection hides that instruction and the consecutive turns read as variants of one question, navigable with a `` switcher (newest shown by default).

Sources: with ui.showToolActivity: true a header button opens a right-hand drawer listing the pages fetched, searches run and files read in this chat, projected from the same tool activity the work groups render - no extra prompting or tooling is involved. Clicking a source opens its full tool output with every http(s) link clickable, and web sources carry an "Открыть" button straight to the page (a Jira issue, a wiki article).

Images: the composer accepts PNG/JPEG/WebP/GIF via drag & drop onto the composer, paste, and the picker button, several at once (soft client caps: 8 images, 15 MB each). Images ride the prompt as base64 uploads the Host promotes to durable attachments, so they survive reloads; sent images render as clickable thumbnails on the message. Whether the model can see them depends on the deployment's model (vision).

Subagents: the deployment may opt the delegation family (subagent, subagent_fork, send_message, list_agents, interrupt_agent) into the lockdown allow-list; the preset must mount them. Launches then render as first-class work items (description, background flag, durable child id), settlement notices appear as status rows, an "Агенты" header drawer lists the chat's subagents with live status, and any subagent opens as a read-only live transcript (composer disabled, one click back to the chat) - viewing never attests or writes.

ui.showSessionList: true renders a minimal chat-history sidebar beside the conversation. It lists only the chats this browser has actually used: the client keeps a per-browser id index under :v1::chats in localStorage (capped at 50, most recently used first) and intersects it with the Host session list, so users sharing the deployment never see each other's chats. Switching re-runs the full policy attestation; a chat the Host no longer lists is pruned from the index. Each row carries a two-click delete control that removes the chat from this browser's index; deleting the chat that is currently open continues in a fresh attested session. Host-side sessions are not deleted — DSH 0.1.x exposes no session-deletion seam. The sidebar hides below 600px viewports.

Locked mode requires a deployment permission preset named qa-read-only. Extend the existing @deepseek-ai/dsh-permission-presets row without changing its process-wide default:

- id: permission
  name: "@deepseek-ai/dsh-permission-presets"
  config:
    presets:
      read-only:
        sandbox: read-only
        approval: ask
      workspace-write:
        sandbox: workspace-write
        approval: ask
      danger-full-access:
        sandbox: danger-full-access
        approval: never
      qa-read-only:
        sandbox: read-only
        approval: never
        name: QA Read Only
        description: No filesystem mutations and no permission escalation.

The shipped tool allow-list is empty. Add only reviewed read-only tool names from the actual deployment. A name that is not registered fails closed. The Host restriction retains exact allow-listed tools from the agent preset's ancestor scope, and the additional execution guard also denies session-scoped tools and run_code unless their exact names are allowed.

Security and deployment

/qa is a presentation boundary, not an authentication boundary. Protect it with the same deployment authentication and DSH browser trust policy as the operator UI. The plugin never auto-approves a permission request or answers an interactive agent question. Configure a least-privilege, non-interactive agent preset for end-user deployments. On a shared Host, a user who can open /, call the normal DSH APIs, or use another authenticated client can bypass the QA presentation. Use a dedicated DSH process/identity and network boundary when /qa must be a strong security boundary.

The embedding.frameAncestors field is reserved deployment metadata in 0.1.x; the plugin does not mutate CSP headers. Configure frame-ancestors at the trusted reverse proxy if iframe embedding is required.

Serving the QA rig over the LAN

The plugin itself needs no changes to be reachable from other machines; the web server's bind address is a deployment composition decision. DSH refuses dsh --host 0.0.0.0 on the command line on purpose, so network exposure is expressed with the shipped patch overlay instead:

pnpm dsh --profile qa-surface --no-open --port 3082 \
  --patch plugins/dsh-qa-surface/deploy/qa-lan.patch.yml

The overlay sets the webserver row to 0.0.0.0 while keeping the --port flag working. On an all-interfaces bind DSH derives trusted /api authorities from the machine's LAN IPv4 addresses, so http://:3082/qa works without extra configuration; users reaching a DNS name instead of an IP literal need --trusted-host on the same command line.

LAN browsers cannot read the DSH settings namespace (settings RPCs are pinned to loopback by the gateway). The plugin covers this itself: a browser that sees the namespace as unavailable reads the effective configuration through the qaSurface/describe Host Remote, so branding, session pinning and lockdown UI switches keep working over the LAN. Host-side enforcement was never dependent on that read path.

What a LAN deployment does not change:

  • DSH has no authentication or TLS on this port; anyone who can reach it can drive the harness, and the full operator UI at / stays reachable next to /qa. Scope the port's reachability (subnet-limited firewall rule, VPN or tailnet, authenticating reverse proxy) and use a dedicated process identity.
  • Loopback-only settings, directory picking and credential RPCs stay refused for LAN clients; the first-load welcome notice re-appears on reload because remote browsers have no settings persistence.

See Configuration for the config-channel details.

See Architecture, Configuration, and Compatibility.

Develop

pnpm --filter @yadsh/dsh-qa-surface check

The package publishes a classic DSH browser module whose loader id is the full package name, @yadsh/dsh-qa-surface.