DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins

@sylvainatelier/dsh-agent-hub

Agent Hub

DeepSeek Harness plugin integrating multiple agents (Claude, Codex, MiniMax, ...): subscription usage today, multi-agent task coordination next

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add @sylvainatelier/dsh-agent-hub@0.1.0
READMECompatibilityVersions

Compatibility and provenance

Agent Hub is published as @sylvainatelier/dsh-agent-hub and currently resolves to version 0.1.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
11/4/2026

Versions

0.1.0stable
10/20/2026

Related plugins

Loading related plugins…

Latest
0.1.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
14.7 MB
Files
28
Surface
web
License
MIT
Source
npm
GitHub
★ 1
Weekly downloads
27
Last push
10/26/2026
View source ↗Project homepage ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in models-usage.

Experimental Cot Translation Bundle@deepseek-ai/dsh-experimental-cot-translation-bundleExperimental machine translation of expanded reasoningWhale Widgetdsh-whale-widgetDeepSeek balance whale widget in the bottom-right corner of the DSH Web interface: balance/today’s usage/peak-off-peak pricing, customizable bubble click sequence (text/balance/today/peak-off-peak/image/random phrases and parallel weighted selection), per-line styles and fonts, floating quick editinOur Free Model@ebony-vinyl/dsh-our-free-modelOur Free Model — Install this plugin in dsh to use cutting-edge models including DeepSeek V4.1 Flash and Kimi K3, with no login, registration, or API Key required. The desktop version also offers an EAC co-payment channel; starting with 2.0, it supports ten account channels: LobsterAI, Qoder, TRAE, Usage@linxin666/dsh-usageUsage statistics plugin for the dsh web GUI: per-provider balance and coding-plan quota detection plus a live token usage ledger, with a compact price glance card above the sidebar's Settings row

README

English | 简体中文

dsh-agent-hub

A DeepSeek Harness plugin that brings several different agents (Claude, Codex, MiniMax…) together in one place, with the long-term goal of coordinating multiple tasks across them.

Current modules: Connectors (Linear, Notion and other external services), Board (Sentry alerts, Linear tickets and local tasks), Reader (RSS and GitHub stars), Plan usage, Claude Agent, Cursor, Qoder & glasses, Hermes, Relay (view and take over Qoder and Hermes conversations held elsewhere), Phone access, Source Control (Git, with worktrees), Code editor, Bots and group chats (multi-agent collaboration), Settings sync (share settings across machines through a Git remote), Review (PRs waiting for my review, and answering reviews on my own PRs), Script tasks (restricted helpers driven by native scheduled tasks).

Of these, Reader, Relay, Clipboard window, Cardputer and Codex are optional modules, each switched on or off in the Modules card at the top of Settings → Agent Hub: only Codex is on by default; the other four are off. A module that is off stops its background work, and its sidebar panel or page and its settings group are hidden; switches apply immediately, with no restart. See Configuration: Settings → Agent Hub.

The settings page folds by module: each collapsed group takes a single line showing its name and a summary of its current state (e.g. "Linear connected · Notion sign-in expired"); click the line to expand it. Expanded state is remembered per browser, and the top of the page offers "Expand all / Collapse all". A group with a problem that needs attention (expired sign-in, invalid value) expands itself and is flagged.

[!IMPORTANT] Codex requires the separate dsh-codex-subscription plugin. Codex models, Codex plan usage and account switching are all provided by dsh-codex-subscription; this plugin only reads its account store and calls its interfaces. Install and enable dsh-codex-subscription from DSH's plugin manager, then sign in under Settings → Codex subscription. While it is missing or off, the Codex card in Plan usage and the Codex models setting under Settings → Agent Hub → Codex show a hint with a one-click Install (or Turn on, when it is installed but off) through DSH's plugin manager; restart DSH when asked, then sign in under Settings → Codex subscription.

Configuration: Settings → Agent Hub

The plugin's own options live on the Agent Hub page of DSH's settings (below "Plan usage"), with the Modules card on top and the groups under four headings: Agents & models (Claude Agent, Qoder & glasses, Model selector, Bots), Workflows (Connectors, Review, Script tasks, Board, Git commits), Conversations & interface (Conversations, Fonts and sizes), Access & devices (Phone access, plus Cardputer and Clipboard & translation while their modules are on). The page's toolbar stays pinned at the top: while any group is expanded the button reads "Collapse all" (clicking it further down the page also scrolls back to the top); once everything is collapsed it becomes "Expand all". On narrow screens only the icons show. Profile-wide settings sync and plugin upgrades are not on this page; see Sync & updates.

Modules: below the toolbar and above the four headings, a Modules card is pinned with one switch per optional module:

  • Reader, Relay, Codex: synced between machines with Settings sync.
  • Clipboard window, Cardputer: this machine only, not synced.

Turning a module off stops its background work (scheduled refreshes, daemons, the native helper, the device port and so on), and hides its sidebar panel or page and its settings group; turning it back on restores them. Switches apply immediately, with no restart. The configuration lives in the modules section of cordis.patch.yml; the old palette.enabled key is replaced by modules.palette.

The table below lists the options by module (configuration is stored where it was in earlier versions; the regrouping needs no migration):

ModuleOptionDefaultDescription
ModulesReaderOffReader in the left sidebar: feeds, scheduled refresh and the reading page; synced
RelayOffThe Relay panel in the left sidebar and its completion notifications; synced
Clipboard windowOffThe clipboard & translation floating window, its global hotkey and clipboard recording (modules.palette, replacing the old palette.enabled); desktop only, this machine only, not synced
CardputerOffCardputer pairing, port and dictation (modules.device); this machine only, not synced
CodexOnThe Codex models setting and the Codex card in Plan usage; needs the separate dsh-codex-subscription plugin; synced
ConversationsInstructions (Markdown)BlankInjected into every conversation with any model: DSH models get it at the end of the system prompt, Claude Agent appends it to Claude Code's system prompt, and bots get it too, after their persona. Blank injects nothing. Claude Agent sessions resume into a fresh process on their next turn to pick up the change
CanvasesOnHave every model deliver standalone charts, tables and dashboards as interactive, self-contained HTML files in ~/.dsh/canvases, linked in its reply and browsed together in the Canvases panel on the left
Archify diagramsOffWith canvases on, turning it on downloads a pinned Archify release (checked by its SHA-256, installed in ~/.dsh/dsh-agent-hub/archify/<version>) and every model draws architecture, workflow, sequence, data-flow, state, ERD and similar diagrams with it, still saved as canvases; the settings show the install state with a retry, and until it is installed canvases stay hand-written. It needs Node and a local Chrome or Edge, and is slower and costlier than a hand-written canvas
Selection discussionsOnSelecting chat text or a code block shows "Ask about this"; the temporary discussion runs no tools and does not read the whole main session
Discussion modelAutomaticReuses the background helper model route, or a chosen plain text model; Claude Agent, Cursor and Bots are not supported. Changes affect new discussions only

Changes save automatically: switches, model choices and resets save at once; typed text and numbers save when you pause typing or leave the field. Every option can be "Reset to default" on its own. Leaving the page saves valid changes and discards drafts from that visit that were invalid or failed to save, without affecting new edits after you come back. Saving goes through DSH's settings service into the current profile's cordis.patch.yml (for the desktop profile: $DSH_HOME/profiles/desktop/cordis.patch.yml) and applies immediately, with no restart. You can also edit that file directly (restart DSH afterwards):

- id: agent-hub
  config:
    modules:                        # Module switches (the defaults shown)
      reader: false                 # Reader; synced
      relay: false                  # Relay; synced
      palette: false                # Clipboard window; this machine only (replaces the old palette.enabled)
      device: false                 # Cardputer; this machine only
      codex: true                   # Codex; needs the dsh-codex-subscription plugin; synced
    prompt:
      instructions: |               # Global instructions (Markdown), injected into every conversation
        ## Communication
        - Answer in English; keep code and identifiers as they are
      canvas: true                  # Canvases: charts, tables and dashboards as HTML in ~/.dsh/canvases
      archify: false                # Install Archify and draw diagrams with it (needs canvases on)
    connectors:
      servers:                      # Only the id and URL; sign-ins live in DSH's credential store
        - id: linear
          url: https://mcp.linear.app/mcp
          repos: [C:\pro\app]       # Optional: which local repositories (main trees) this Linear account is for; omitted = all repositories
    claude:
      remoteControl: true
      fullAccess: false             # true = full access for new processes; once off, existing full-access processes restore approvals at the next safe turn
      keepOnline: 3
      idleHours: 2
      models: [opus, sonnet]        # empty = all
      helperProvider: deepseek-official   # Model for background requests (compaction, titles); both blank = automatic
      helperModel: deepseek-v4-flash
    codex:
      models: [gpt-6.1-sol]          # empty = all; ["(none)"] = hide all
    models:
      hiddenProviders: [deepseek-official, deepseek-account]
      effort: high                  # Default thinking level when picking a model; omitted = each model's own default
      efforts:                      # Per-model overrides, over effort
        - provider: claude-agent
          model: opus
          effort: max
    appearance:
      uiFont: default              # default / roboto-mono / jetbrains-mono / source-code-pro / fira-code / ibm-plex-mono
      codeFont: jetbrains-mono
      uiScale: 100                 # 80–130 (%); scales all interface text, keeping relative sizes; conversation text size is still set by DSH General → Font size
      regions:                     # Per-region adjustment, in percentage points on top of the whole (−20–20); 0 = follow the whole
        sidebar: 10                # sidebar / main / panel / settings / popup
    remote:
      httpsPort: 443
    git:
      provider: deepseek-official
      model: deepseek-v4-flash
      instructions: Use Conventional Commits, write the subject in Chinese
    team:
      agents:
        - id: reviewer
          description: Read-only diff review
          provider: claude-agent
          model: opus
          persona: You are a rigorous code reviewer…
        - id: coder
          provider: openai-codex
          model: gpt-5.6-terra
          tools: [read, edit, bash]
    sync:
      remote: git@github.com:me/dsh-settings.git
      branch: main
      fetchOnStart: true
      localSettings: [terminal-controller, ui-theme.config.preference]   # Whole entries, or settings within an entry
    review:
      repos: [/Users/vi/pro/app]    # Repository main folders on this machine; not exported
      at: "09:00"                 # First migration only; afterwards edit the time in the host automation task
      bot: reviewer                 # Model + persona (analysis prompt) both come from this bot
      commentBot: en                # Bot used by default when ✦ Process is clicked on a hand-written comment under the board's Changes; blank = not processed
      helperProvider: minimax-cn    # Automatic sync coordination, Dig in, Rewrite; both blank = Claude Agent's background request model
      helperModel: MiniMax-M3
      style: |
        Comments in English, praise the implementation first, short and casual, no dashes
      rules: |
        - Ignore files under generated/
      explain: |                    # Answer requirements for ✦ Explain under the board's Changes; blank = purpose / effect / how it takes effect
        Answer in three parts: **Purpose**, **Scope of impact**, **Risks**, at most two sentences each
      explainBot: reviewer          # ✦ Explain and follow-ups use this bot's persona and model; blank = the helper model
      maxPerRun: 10
    alerts:
      url: https://acme.sentry.io/issues/?environment=prod&project=1234567&statsPeriod=14d
      repo: C:\\pro\\app            # Local repository main working tree; not exported
      bot: reviewer

Under the hood, every section of Config is declared .volatile() (src/config.ts): DSH lets the settings interface edit only volatile fields, the Loader hands new values straight to the running plugin without remounting it, and each module reads the latest values every time it uses them; phone access rebuilds its forwarding when the port changes.

Export settings / Import settings… at the top of the page carry the settings to another machine (between Windows, macOS and Linux in any direction):

  • Export downloads agent-hub-config-<date>.json, containing only the settings the current profile has changed; options left at their defaults are not written, so after import they follow the target machine's defaults. Sign-ins and tokens live in DSH's credential store and are never written to the file; imported connectors must be connected again on the new machine.
  • A connector's linked repositories are exported in portable form: { "name": "app", "path": "C:/pro/app" } (paths always use /). On import, a registered repository with the same path is looked for first, then a unique repository with the same name; if neither is found, the original path is kept and you are told, so you can link it again under Connectors later. The Sentry alerts "Analysis repository path" is valid only on its machine and, like the review repository list, is not exported. Multi-line text (global instructions, personas, etc.) is normalized to LF line endings.
  • Before importing, you see how many settings will be applied, how many will go back to their defaults, and which settings this version does not know or would refuse were skipped; on confirmation the current profile's settings are replaced in one go, and unsaved drafts are discarded.

Module: Script tasks

Settings → Agent Hub → Script tasks: binds trusted local scripts to DSH native scheduled tasks, where a dedicated session on a chosen model or compatible Bot calls the one restricted tool. Program, arguments and working directory are saved explicitly; by default no task is created and nothing runs. Time and time zone are edited in the native task manager. A A Qoder CN daily credits template is included: it appears only on Windows with the Qoder CN desktop app installed, only checks by default, allows claiming only when a claim argument is added explicitly, and never switches to the international edition. Runs use the current user's permissions; this is not a script sandbox.

See usage, result protocol and recovery boundaries.

Settings → Sync & updates

Profile-wide settings sync and plugin upgrades live on a separate Sync & updates page in DSH's settings (below Agent Hub). Both affect every plugin, and both may need a restart to take effect: when such a change is pending, a banner at the top of the page explains why, lists the upgrades queued for the restart, and offers Restart now (DSH Desktop only; under dsh web it asks you to restart by hand).

GroupOptionDefaultDescription
Settings syncRemote repositoryBlankAny URL git understands (GitHub, Gitee, a self-hosted server, a local or network path), using this machine's git sign-in; blank = no sync. See Module: Settings sync
BranchmainThe branch used on the remote
Pull at startupOnPulls the remote when DSH starts: applies the remote's changes directly when nothing conflicts (this machine's changes are not pushed); a conflict is only announced
Not syncedterminal-controller, review.auto, remote.phoneSettingsWhole entries, or settings within an entry, that each machine keeps for itself; picked from this machine's and the remote's configuration, no typing needed

These options keep the same storage keys as before (still under config.sync of the agent-hub entry), so moving them off the Agent Hub page needs no migration. On a phone only a notice is shown; settings sync can only be set up on a computer.

Plugin updates:

  1. Click Check updates to read the latest versions of installed plugins from the current profile's registry; nothing is installed automatically.
  2. Tick the plugins to upgrade (select all is supported), review the current and target versions, then confirm.
  3. Normal mode: installs through DSH's native installer one at a time, showing progress, install output and failure reasons live; failed items can be retried. Settings and existing enablement are kept, and build scripts are not approved automatically. Agent Hub itself goes last, so the queue is never replaced midway. Restart DSH afterwards to apply.
  4. HMR hot-reload mode: DSH refuses to replace installed plugins in place, so the button becomes Upgrade at restart. The exact chosen versions go into the pending-restart list ($DSH_HOME/dsh-agent-hub/pending-upgrades.json); you can keep queuing, or cancel items one by one. At restart, once DSH has fully quit, the restart helper runs pnpm add <name>@<version> --save-exact once in the profile folder; if it fails, package.json and the lockfile are restored. When DSH reopens, a toast in the bottom-right corner reports the result; this page keeps the pnpm output until you click "Dismiss".

Built-in, read-only, locally linked and Git-sourced plugins are not upgraded; the list explains why each is skipped. Upgrades requested by settings sync go through the same pending-restart list.

Module: Connectors

Connect external services (Linear, Notion, Sentry, GitHub…) as remote MCP servers once, here, and every model in DSH can use their tools: DeepSeek, Codex, MiniMax and other DSH models use them directly, and Claude Agent through a bridge. The entry point is Settings → Agent Hub → Connectors.

  • Add: click a preset (Linear, Notion, Sentry, GitHub), or "Other MCP server" and enter a Streamable HTTP URL. Connecting starts as soon as it is added, and each card shows only the next step:
    • Services with browser sign-in (OAuth per the MCP spec: RFC 9728 resource metadata → RFC 8414 authorization server → RFC 7591 dynamic registration → PKCE) open the authorization page; once done, the card turns into "Connected · N tools" by itself. The callback lands on a 127.0.0.1 port opened just for that sign-in (RFC 8252), so it works in both the desktop app and a browser. If the popup is blocked, click "Open the sign-in page again".
    • Services without browser sign-in (such as GitHub) ask for an access token straight away; services that do support it can also use an API key via "Use a token" (a Linear personal API key works). The token is tried with a real connection first and is not saved if refused.
    • When authorization expires the card shows "Sign-in expired", with one-click reconnect. OAuth tokens are refreshed automatically before they expire.
  • Disconnect / Remove: deletes the credentials and withdraws all of that service's tools.
  • Credentials: OAuth tokens and API keys live in DSH's credential store (dsh-agent-hub/connector-<id> in $DSH_HOME/.credentials.yaml), never in cordis.patch.yml, and are never sent to the browser. The configuration file holds only the id and URL. Credentials are bound to the exact MCP URL, so changing the URL means connecting again; legacy API-key / no-auth records are bound to the current URL the first time they are read, and legacy OAuth records are migrated after checking the resource identifier. Legacy records carry no URL history, so if you changed the URL before upgrading, disconnect and connect again.
  • Tool names: connector <id>'s tools are named mcp__<id>__<tool> in every model (e.g. mcp__linear__list_issues). Every call goes through DSH's tool policy and approvals.
  • Claude Agent: each connector is an in-process MCP server of the same name inside Claude Code, with the same tool names as on the DSH side, deferred behind Claude Code's tool search and loaded on demand. Claude Agent sessions use only the connectors here: the plugin starts Claude Code with --strict-mcp-config and turns off claude.ai connectors, so none of the MCP servers configured in Claude Code itself (~/.claude, plugins, claude.ai) load, and every model reaches external services through the same set of connectors. When connectors are added or removed, the session resumes into a fresh process on its next turn.
  • Linear and worktrees: with Linear connected, the branch-name field of the Git pane's "New worktree" dialog also searches issues: on opening it lists the unfinished issues assigned to me, and typing an identifier () or keywords filters instantly. Picking one fills in Linear's suggested branch name (e.g. ), and the worktree is created as usual; the new session's composer is prefilled with the issue identifier, title and link, ready to send, and the agent can read the latest content with . The link between worktree and ticket (Linear's own convention) and is not stored anywhere else: when a branch name contains an identifier with one of the workspace's team prefixes, the worktree list shows the issue identifier and status on that row; click it to open it in Linear.

Module: Board

Board, below "Automation tasks" in the left sidebar, opens in the main area. It is a lightweight take on the board in @linxin666/dsh-client-ui-task-board: just lanes, cards, details and local tasks, without scheduled runs, subtasks or permission prompts. The board puts task cards in one set of lanes (Backlog / Todo / In progress / Done / Canceled) and can show alerts and reviews on the far left:

  • Sentry alerts: with a Sentry Issues URL and a local repository configured, the leftmost lane shows unresolved prod issues; switch between 7d / 14d, and sort ascending or descending by Last Seen / Age / Events. Cards show the title, an error preview, a likely path:line, time and event count; a read failure (Sentry not connected, permissions, etc.) shows its reason right in the alerts lane. Details unfold into the analysis conclusion, source location, exception chain and event evidence; the source location opens the code in the issue's worktree.
    • Starting an analysis means choosing a bot (this device remembers the last choice; Seer is never offered). On click the card immediately enters "Preparing" (at most 2 analyses at once, the rest queue); if it cannot start, the reason shows on the card with a retry. It then fetches and creates or reuses a branch and worktree, named with the normalized issue ID, from the latest origin/<default branch>. Sentry evidence is capped at about 60 KB, and the record is written to <repo>/.reviews/sentry-<ID>.md; during analysis only the record may be written.
    • "Fix" continues in the same worktree, with the same bot and the same session context, allows code changes and asks for one commit containing Fixes <ID>, then goes back to read-only. When Last Seen is later than the time recorded at analysis/fix (after allowing for the rounding in the list's "3 hours ago"), the card is marked "New events" and can be fixed again.
    • "Resolve" is an explicit Sentry call made by the interface, with 10 seconds to undo; the model may never write to Sentry. After the undo window, only worktrees and branches the plugin created itself, that are still clean and have no new commits relative to their base, are cleaned up; everything else is kept.
  • Linear tickets: from every connected Linear account, "Mine" by default, switchable to "Everyone"; done and canceled ones are kept for the last 14 days only. Status is stored in Linear. Dragging a card to another lane moves it to the team's ordinary working status of that kind, e.g. dragging to "In progress" picks In Progress, not In Review; the details let you pick a specific status.
  • Local tasks: for work outside Linear, kept only in $DSH_HOME/dsh-agent-hub/board.json on this machine. Click + in a lane header or "New task" to type straight into the lane; tasks can be dragged, retitled, described, moved to another repository and deleted.
  • To review: when repositories are ticked under Settings → Agent Hub → Review, this lane appears after the alerts lane (leftmost when alerts are not configured), and its header switches between To review | Mine | All: PRs waiting for my review (opening one shows its review sheet), my own PRs (opening one shows its response sheet), and every open PR. Cards in this lane cannot be dragged. See .

Under the hood, each connected service is an instance of DSH's own @deepseek-ai/dsh-mcp-client plugin, mounted by this plugin as a child plugin (with an Authorization: Bearer header), so tool naming, result projection, MCP resources and server instructions, and reconnects all follow DSH's implementation; the instance is remounted when its token refreshes.

Module: Plan usage

See the remaining allowance and reset times of Claude, Codex (multiple accounts), Cursor, Qoder and MiniMax plans in one place.

  • Settings → Plan usage: one card per account. Each window (5 hours / weekly …) shows the remaining percentage and a reset countdown; the tick on the progress bar marks "how much should be left right now at an even pace". MiniMax also shows the count used and the plan's expiry date. Cursor shows what is left of the billing cycle's included allowance (the Auto and API pools fold under "More limits") and on-demand spend ("On-demand $219.48 · no cap", or "On-demand $12.30 / $50.00" with a cap, changing color near or over it). Qoder shows one row per credits pool (plan, add-on, team, dedicated packs) with its remaining percentage and "Used 84 / 1,800 credits".
  • Sidebar glance card (above "Settings"): one cell per account, showing its name, the remaining percentage of its tightest window and a thin progress bar (Qoder's pools are drawn down in turn, so its cell shows the remaining share of all credits; in the popover each pool has a row, with remaining credits in the last column). The detail popover opens to the right of the sidebar, with each account's windows, remaining amounts and reset countdowns aligned in columns: hovering opens it and moving away hides it; clicking pins it open until you click ×, press Esc or click the glance card again. "Open details" at the bottom of the popover opens the details page.
  • Quick account switching in the popover: below the Claude accounts the popover also lists Codex accounts (shown when dsh-codex-subscription is installed with two or more accounts signed in); "Switch" changes the active account through the Codex plugin's own interface, effective from the next Codex request, and usage refreshes right away. Switching never touches Codex's credentials.
  • When the Codex plugin is missing: if dsh-codex-subscription is not installed or not turned on, the Codex card shows a hint with a one-click Install or Turn on (restart DSH when asked, then sign in under Settings → Codex subscription). With Codex switched off in the Modules card, the Codex card is not shown.

Local token statistics are attributed to the provider and model a Bot actually used; Claude's running totals keep separate baselines per account and session. Upgrading rescans the session logs that still exist and keeps the historical totals of deleted sessions; deleted logs cannot have their old Bot attribution restored.

Data sources

PlanMethodNotes
Claudeclaude -p /usage --output-format json --no-session-persistenceA local command built into the CLI, costing no tokens; OAuth refresh is left to the CLI itself. Plan and email come from claude auth status
CodexReads the account store of dsh-codex-subscription read-only and calls wham/usage for each accountNever refreshes or writes credentials (refresh tokens rotate, and refreshing on its behalf would sign the Codex plugin out). Once a non-active account's token expires, its last snapshot is shown with the hint "switch to this account and use it once to update"
CursorCursor's DashboardService: GetCurrentPeriodUsage, GetPlanInfo, GetHardLimit (the same calls and arithmetic as the CLI's /usage)Called read-only with the Cursor CLI's own sign-in token (cursor-access-token in the Keychain on macOS, auth.json elsewhere), never refreshed or written; when the token is refused or you are not signed in, it suggests running agent login. With neither the CLI installed nor a sign-in, no card is shown. These interfaces are not public and may break when Cursor changes them
QoderReadings Qoder CLI CN records itself: every start of interactive qodercn (and /usage) requests /api/v2/quota/usage and writes the full response to ~/.qoder-cn/logs/runs/*/qodercli.logQoder's sign-in credentials and request signing are encrypted inside the CLI, so the plugin does not request on its behalf; it only reads the logs of interactive runs incrementally (--acp, -p, the remote-control daemon and subcommands do not read the quota and are skipped), takes the latest entry, and labels the data time from the log time (over 10 minutes shows "data from x hours ago"). With the CLI installed but no reading yet, it asks you to open qodercn once in a terminal
MiniMaxGET /v1/api/openplatform/coding_plan/remainsThe key is taken from pi-ai's credential records (minimax-cn / minimax), then MINIMAX_CN_API_KEY / MINIMAX_API_KEY; it is used only on the host and never reaches the browser

The host runs no background timer: the browser polls every 60 seconds (only while the page is visible), and each source has a TTL cache (Claude 5 minutes, Cursor 2 minutes, Qoder 30 seconds, the rest 60 seconds). Every Claude read cold-starts the CLI (seconds of CPU, hundreds of MB of memory), so it reads rarely when idle; after each Claude Agent turn, the next poll rereads it in the background (if over 60 seconds since the last read). Plan and email (claude auth status) are cached for 30 minutes; /usage runs with --strict-mcp-config, so it does not start your configured MCP servers. When the cache is stale the old value is returned first and refreshed in the background; only clicking "Refresh" waits for the latest result. When a read fails, the last successful result persisted in $DSH_HOME/dsh-agent-hub/usage.json is used as a fallback, labeled with its data time.

Module: Claude Agent

A Claude Agent group appears in DSH's existing model selectors (the composer's model slot and the /model dialog). Pick one of its models and every turn of that session is handed to a long-lived Claude Code session, using your Claude Code subscription allowance.

  • How it runs: the official Claude Agent SDK drives the claude CLI installed and signed in on this machine. Claude Code completes the turn with its own tools, skills, ~/.claude settings and permission rules; the reply streams back into DSH as text and thinking, and each internal tool call shows as a reasoning note line (→ Bash: ls, or ✗ … on failure).
  • Multiple accounts: Settings → Agent Hub → Claude accounts lets you add accounts and copy a dedicated login command; run it in a terminal, then complete the official browser sign-in with /login inside Claude. Pick the account directly in the sidebar usage popover; it applies from the next turn without interrupting a running one. Added accounts use the official CLAUDE_CONFIG_DIR to isolate sign-in and settings, while the default account keeps the original CLI folder. Switching accounts never resumes a Claude session across accounts; instead the DSH conversation becomes context for the new account. Removing only unregisters the account and does not delete Claude's own files. Each account's usage is cached separately, so switching does not cold-start again; when refreshing, at most two CLIs cold-start at once.
  • Approvals: tool calls Claude Code needs to ask about open DSH's native approval dialog; when the DSH session's approval policy is never, they are refused outright.
  • Session continuity: each reply records Claude Code's session id in replayState, and the next turn sends only what it has not seen yet (if you switched to another model midway, the conversation in between goes along as context); after a host restart it resumes from Claude Code's own records. A change in reasoning effort or in the bridged tools (connectors, team tools) resumes the session into a fresh process, while a model change switches in place.
  • Calling DSH skills with /: a DSH skill invoked with /name in the composer (including commands from .cursor) has its instructions sent to Claude Code with that turn's prompt, ahead of your own words, so /name is not taken as one of Claude Code's own commands; the reply starts with a note line ⚡ Skill /name. When a name clashes with a built-in Claude Code command the DSH skill wins, and a /command with no matching DSH skill is passed to Claude Code as usual.
  • Background requests: Claude Agent only answers the conversation itself. DSH's other requests for a Claude Agent session, such as compacting the context when the conversation gets too long or generating the session title, are answered by , so the turn no longer fails with . "Automatic" uses the model new agents get; when that is Claude Agent or a Bot (which may itself run on Claude Agent), the first other available model. Claude Agent's own context is still managed by Claude Code; these requests only affect the conversation record and title DSH keeps.

Sign-in failure hints: if a Claude Agent turn fails because Claude Code on this machine is not signed in or its sign-in expired, the chat states the reason and the fix (instead of a vague "invalid API key"), and a re-sign-in dialog lists the steps: open a terminal from the sidebar, run claude, type /login and finish in the browser, then click "Check again". Checking again really runs one local CLI command to confirm the sign-in (no tokens spent); once it passes, send again. These failures use their own error code, CLAUDE_AUTH, and are never retried automatically.

Configuration (Remote Control, sessions kept online, idle close time): see Settings → Agent Hub.

Compliance boundaries

  • The plugin never reads, stores or forwards any Claude credentials, and implements no sign-in; authentication and token refresh are left entirely to the official CLI.
  • When starting the CLI, ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL and CLAUDE_CODE_OAUTH_TOKEN are removed, so requests always go through the CLI's own claude.ai sign-in (also a prerequisite for Remote Control).
  • Claude Code's system prompt is not replaced, request headers are not changed, and it is not used as a bare API proxy: only turns the user starts in a session reach Claude Code; helper requests such as session titles and compaction summaries are always refused and handled by DSH itself.
  • For personal use on your own machine only. Anthropic does not allow third-party products to offer claude.ai sign-in or subscription allowance to others without approval; do not distribute this feature for others to use.
  • The display name is "Claude Agent" (brand guidelines rule out "Claude Code").

Module: Cursor

A Cursor group appears in DSH's model selectors (the composer's model slot and the /model dialog). Pick one of its models and every turn of that session is handed to the agent of this machine's Cursor CLI, on your Cursor account's own allowance. Install the Cursor CLI and run agent login first.

  • How it runs: the plugin starts a long-lived agent acp process (the Cursor CLI's Agent Client Protocol server), and each DSH session maps to one ACP session in it. Cursor completes the turn with its own tools, rules, MCP servers and sign-in; the reply streams back into DSH as text and thinking, each internal tool call shows as a reasoning note line (→ Shell: npm test, or ✗ Shell: npm test: exit 1, … on failure), and the plan checklist shows as a note too. The process closes after 30 minutes without conversation, and the next turn loads the session again. On Windows the node bundled in the CLI's latest version folder is run directly, bypassing agent.cmd's PowerShell wrapper.
  • Models and reasoning effort: the model list and each model's reasoning options come from the CLI (parameterized model selection). Cursor's models name their reasoning options differently (effort, reasoning, reasoning_effort, some with a thinking switch); they are folded into one column of DSH's reasoning effort picker: Off (thinking off, or none) plus the model's own levels; models with only a switch show Off / On. So the default thinking level under "Settings → Agent Hub → Model selector" applies to Cursor too. Speed (Fast), context length (1M) and Auto's optimize_for keep the CLI defaults and are not adjusted in DSH. A change of model or level switches in place in the current session, without restarting the process.
  • Your Cursor settings stay untouched: the CLI saves the model parameters chosen over ACP as its own defaults (~/.cursor/cli-config.json). The plugin writes that file back exactly as it was after each change, so the session's choice still applies while the default model and level you get with agent in a terminal are unaffected by DSH.
  • Model catalog: the first read opens a probe session in a temporary folder (no prompt sent, no cost); the model list is usable as soon as it is listed, and each model's reasoning options are read one by one in the background (about 1.5 seconds each), the selected model first. The catalog persists in $DSH_HOME/dsh-agent-hub/cursor-models.json and refreshes in the background after 12 hours; the probe session is deleted from Cursor's session records once used.
  • Approvals: tool calls Cursor needs to ask about (commands not on its allowlist, file writes, etc.) open DSH's native approval dialog, which only ever offers "allow this once" and never clicks "always allow" for you. When the DSH session's approval policy is never, or there is no approval channel (such as headless), they are refused outright. The global blocked commands apply too: a blocked command is refused without a dialog.

Known issue (Cursor CLI 2026.10.01, Windows): the Cursor CLI loads Claude Code's hooks (~/.claude/settings.json). It wraps hook commands in PowerShell syntax but hands them to Git Bash, causing a syntax error, so every shell tool call is blocked (the reply says "Hook blocked … syntax error near unexpected token &'"). This has nothing to do with DSH; using agent directly in a terminal does the same. The CLI has an internal switch to stop loading third-party configuration (thirdPartyExtensibility`), but it is not yet confirmed which user-facing setting exposes it; until Cursor fixes this, Cursor cannot run shell commands on Windows machines with Claude Code hooks configured, while reading and writing files is unaffected.

Module: Cardputer

First turn on Cardputer in the Modules card at the top of Settings → Agent Hub (off by default, this machine only, not synced; turning it off stops the port and the dictation process). Settings → Agent Hub → Cardputer adds devices, checks the pairing digits, renames and removes them. A paired device uses one TLS-PSK WebSocket to view sessions, send messages, stop/continue tasks, handle approvals, and upload PCM audio for speech recognition. With no device and no pairing in progress, no port is opened; with no device online, no heartbeat timer runs. Keys are stored only on this machine and are not synced.

  • State updates are coalesced to 250 ms, approvals are pushed immediately, and duplicate snapshots are not resent. Connections, messages, audio, commands and the file name cache are all capped; a disconnect cancels speech recognition, and unloading the plugin cleans up approvals and dictation processes.
  • Dictation uses a separate Rust dsh-type, started on demand and exiting after five idle minutes. Both Automatic and Type use Unicode events and never touch the clipboard; Paste is not supported for now, so old settings need changing to Automatic. Failed input is not retried automatically, to avoid typing twice.
  • Run node scripts/build-native.mjs on the target system to build the helper for the current platform; the output is packaged with the plugin. The macOS Intel/ARM64 sources pass Rust type checking, but native macOS linking, signing, permissions and input method behavior have not been verified on real hardware; Windows tests have not injected text into real applications either.
  • This repository has only the host implementation and a Node simulator, no flashable firmware. ESP32 PSK/WebSocket, screen interaction, microphone and the hardware long-press guard still need firmware work and integration. High-risk detection is a conservative heuristic, not a general command safety parser; the device must not replace the security policy on the computer.

Protocol, build and verification boundaries · Interactive simulator

Module: Clipboard & translation

Turn on Clipboard window in the Modules card at the top of Settings → Agent Hub (off by default, desktop only, this machine only and not synced; not shown or changeable on a phone); the hotkey and other options are under Settings → Agent Hub → Clipboard & translation. Once on, a global hotkey (Ctrl+Alt+V by default; press a new combination in the settings to record it) summons a floating window with two modes in one window, switched with Tab:

  • Clipboard: keeps a history of copied text in a local SQLite database (~/.dsh/dsh-agent-hub/clipboard.db; the history is not synced with the settings, only pinned items travel to other devices with Settings sync). The left column, "Pinned", is ordered by when items were pinned, with fixed numbers, and Ctrl/⌘ 1–9 types the corresponding pinned item directly; the right column, "Recent", is ordered by time; a fixed-height preview area below shows the selected item's full text and source. The search at the top filters both columns: when a pinned item matches, the selection lands on the first "Pinned" item, otherwise in "Recent", and clearing the search returns to "Recent". ↑↓ select, ← → switch columns (with text in the search box, only when the caret is at either end), Enter types into the app that was in front before the window opened, Ctrl/⌘ C copies and closes, Ctrl/⌘ P pins / unpins, Del deletes (undoable for 6 seconds, no confirmation dialog). While the window is open, new copies appear live at the top of "Recent" with a flash; if you have not moved the selection yet, the new item becomes the selection. In narrow windows (< 600 px) the two columns stack vertically.
  • Editing: the preview box is itself the editor: click the text in the preview (the caret lands where you click), or press Ctrl/⌘ E, or click the pencil button on the row or the preview bar, and the preview expands upward over the list and becomes editable, folding back after saving. Ctrl/⌘ Enter (or Ctrl/⌘ S) saves and Esc cancels (discarded edits can be undone from the toast). Saving updates the item in place, keeping its position, source, pinned state and number; when a pinned item's content changes, other devices follow at their next settings sync. Editing text to match another existing entry merges the two (keeping the pin). Long text over 8000 characters, whose preview is truncated, is read in full before editing is allowed; if the window loses focus and closes midway, unsaved content is kept and can be resumed with "Continue editing" next time. Images cannot be edited.
  • Images: copied images (screenshots, "Copy image" in a browser, etc.) also go into "Recent", with a thumbnail, dimensions, source and time left. Images are temporary: cleared automatically if not copied or used again within 5 hours, at most 100 kept, not pinnable and not synced, so they take no space in the sync repository and never bloat the database. Enter puts the image back on the clipboard and sends one ⌘V / Ctrl+V to the target app (an image cannot be "typed", so this is the only place the window simulates a paste; dictation still never touches the clipboard); only copies; or clicking the preview shows it large, / closes it; searching "image" or a size filters images. A copy carrying both text and an image (such as a spreadsheet cell) is recorded as text; the icon attached when copying files in Finder is not recorded. Everything is stored as PNG (converted from DIB on Windows when there is no PNG format), and on Windows both PNG and DIB are offered when put back on the clipboard; images over 16 MB are not recorded.

Implementation and resource use:

  • The window, hotkey, clipboard listening and history all live in the native helper dsh-type --palette (the same binary as Cardputer dictation, in two run modes and two processes, with input serialized by a cross-process lock that refuses when busy). The host only starts it, restarts it (exponential backoff from 1 s; after 5 crashes within 10 minutes it stops and shows the reason in the settings), sends it settings and handles translation; opening the window, searching, pinning and typing never go through the host.
  • Clipboard listening: Windows uses AddClipboardFormatListener, waking only on system notifications, so idle CPU is 0, and multiple notifications from one copy are merged within 80 ms. Sync tools such as phone link and remote desktop often use delayed rendering (announcing formats first and fetching the data from the network only when read), so when the clipboard cannot be read yet or is locked, it retries after 120 ms, 300 ms, 0.7 s, 1.5 s and 3 s, and only counts a copy as handled once it is sure nothing needs recording (not text, sensitive, ignored); each time the window is summoned it also checks the clipboard sequence number again, so a missed copy is caught up when the window opens. macOS has no change notification, so it reads only changeCount (an integer): every 250 ms right after a copy, relaxing to 1 s and then 2 s. Content the system marks as sensitive (Windows' ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory=0; macOS's ConcealedType, TransientType) is skipped before its content is read; programs can also be ignored by name. A single text over 64 KB is not recorded; duplicates (images compared byte by byte) just move to the top.
  • The window size is taken in the screen's logical pixels: 50% of the screen width (640–860) and 56% of its height (420–600), always the same on a given screen; when moving to a screen with a different scale factor it moves there first and then sizes itself, and it handles the system's DPI-change callback, so the window no longer jumps between sizes. Row height, the preview area, header and footer are all fixed heights, so selection and data changes never shift the layout.
  • The helper sends the page only increments: the database remembers which rows the last 128 changes touched, so a page a few steps behind receives just those rows (and deleted ids); only after old records are cleaned up, or when far behind, is the whole list sent. The list is virtualized and reuses row nodes, updating only rows whose content changed.
  • The interface is a single-file page of about 25 KB (no framework), created only on first use, hidden after use with WebView2 suspended (Windows), and released after 30 idle minutes, leaving only the small native process. Measured on a Windows machine: with just the hotkey and listening, the helper uses about 2 MB of private memory; with the page hidden and suspended, the combined working set is about 60 MB (the WebView2 processes still commit around 130 MB, paged out by the system). The first summon after a cold start is slower (creating the WebView); 5 seconds after the feature is turned on, the host tells the helper to warm up.

Module: Qoder & glasses

Install Qoder CLI CN first (irm https://qoder.com.cn/install.ps1 | iex; on macOS / Linux curl -fsSL https://qoder.com.cn/install | bash), then run qodercn and /login.

  • Model routing: a Qoder group appears in the model selector (Qwen, GLM, Kimi, DeepSeek, MiniMax and more, each name followed by its credit multiplier per call, e.g. GLM-5.3 · 0.5×). Picking one hands the turn to a long-lived qoderclicn --acp process, through the same generic ACP layer as Cursor (src/acp/): tool approvals open DSH's dialog, replies stream, sessions continue with session/load, and the process closes after 30 minutes without conversation. The model list comes from a probe session that sends no prompt, cached in $DSH_HOME/dsh-agent-hub/qoder-models.json and refreshed every 12 hours. When not signed in it reports QODER_AUTH and does not retry automatically.
  • Tasks from the glasses: each repository ticked in the settings keeps a qoderclicn remote-control --spawn same-dir daemon running while DSH runs. Say "Hey Quark, open Qoder" on the glasses, then "start a task on my computer…", and the task runs directly in that repository on this machine, without creating a worktree automatically; only when the task explicitly asks to "open a worktree" does the agent create one with Qoder's EnterWorktree. Each new task is a fresh session created by Qoder's server, and the daemon cannot fold it into an old one; to continue a previous task, open it in the panel, the app or the web and ask a follow-up there.
  • Worktrees in one place: Qoder puts worktrees under <repo>/.qoder/worktrees/ by default, and the CLI has no option to change that. Before each daemon start, DSH writes two hooks, WorktreeCreate / WorktreeRemove, into the repository's .qoder/settings.local.json (exec form, running $DSH_HOME/dsh-agent-hub/qoder-worktree.cjs with the host's bundled runtime, no shell, the same on Windows and macOS; your other settings and hooks in the file stay untouched). So a worktree a task asks for is created at <repo>/.worktrees/qoder/<name> on branch qoder/<name>, alongside DSH's other worktrees (Source Control, the board, reviews and alert fixes all create theirs at <repo>/.worktrees/<branch>), where Source Control's worktree list shows, opens and removes it. Using qodercn --worktree directly in this repository goes to the same place. When Qoder removes a worktree it calls the remove hook: a worktree with no changes is deleted along with its branch; one with uncommitted changes is kept so you can look at the diff; a branch with commits of its own is kept too. /.worktrees/ and /.qoder/settings.local.json are written into the repository's own .git/info/exclude (the tracked .gitignore is not changed), so they never show up in git status or diffs.

Module: Hermes

Install Hermes first (iex (irm https://hermes-agent.nousresearch.com/install.ps1); on macOS / Linux curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash).

  • One entry per persona: in the model selector's Hermes group, each entry is a Hermes persona (default is the root folder itself, the rest are the profiles under profiles/), followed by its configured model, e.g. qweno · gpt-5.6-terra. The list is read straight from the persona folders (config.yaml and profile.yaml) with no probe session, so a newly created persona appears at once. The root is %LOCALAPPDATA%\hermes on Windows and ~/.hermes elsewhere; HERMES_HOME overrides it.
  • Running: once a persona is picked, the turn goes to hermes-acp with the process's HERMES_HOME pinned to that persona's folder (set explicitly for default too, so the usual persona set by hermes profile use cannot pull it away), one long-lived process per persona. Answers use that persona's own model, memory, SOUL and skills; to change the model, run hermes -p <persona> model. Dangerous commands and file changes open DSH's approval dialog. It goes through the same generic ACP layer as Qoder: sessions continue with session/load, and the process closes after 30 minutes without conversation.
  • Cold start: starting Hermes plus creating a session takes about 20 seconds altogether. For a turn when the process is not running yet, the reply first shows a line ⏳ Starting Hermes… (Qoder does too).
  • Memory is shared: conversations in DSH are written into that persona's memory and state.db, so the same persona's Telegram bot will know about them later. To keep them apart, tick only personas dedicated to writing code.

Module: Relay

The Relay panel in the left sidebar: what Qoder and Hermes do outside DSH, listed in one place, where you can read the full record or carry on the conversation in DSH. Off by default; turn on Relay in the Modules card at the top of Settings → Agent Hub (synced).

  • Where it comes from: for Qoder, the CLI's own session files (~/.qoder-cn/projects/*/<session>.jsonl), so tasks from the glasses, the Qoder app and qoder.cn, and qodercn sessions in a terminal, are all there, and still there after DSH restarts; glasses tasks additionally carry the daemon's live state (the tool being called, the worker's memory and CPU, failures reported by the server). For Hermes, each persona's state.db is opened read-only (with Node's built-in SQLite), listing conversations from Telegram, the terminal, cron jobs and the API. Sessions DSH itself started over ACP, Hermes' one-off probes and subagents are not listed (they are already in DSH, or are not conversations). While the panel is open, these files are checked for changes every 3 seconds and reread only when they changed.
  • List: grouped into Today, Yesterday, Last 7 days and Older, filterable by "All / Qoder / Hermes" and searchable. Each row has the source icon (glasses / cloud / terminal / Telegram / cron job…), the title, which agent (Qoder with the folder name, Hermes with the persona), and the start of the answer or the tool being called; running, failed, ended and cloud each have their own marker, and those taken over are tagged DSH. Pinned at the top is "Glasses workspaces & cloud", the content of the former Qoder glasses panel.
  • Reading: the full record is on the right: questions, answers (rendered as Markdown, raw HTML shown as text), and consecutive tool calls folded into one line, "N tool calls". The header shows status, source, folder, branch, model and number of turns; "More actions" has Open on qoder.cn, Continue in a terminal here, Copy resume command and Copy session ID.
  • Taking over: type in the composer at the bottom and send, or click "Take over in DSH" in the header. This creates a new DSH session (with that agent as the model: Qoder uses the original session's model, or auto when DSH does not offer it; Hermes uses that persona), keeps the original conversation's title, and jumps there. Clicking the same conversation again later opens that same session (the index is in $DSH_HOME/dsh-agent-hub/relay.json; once the DSH session is archived, taking over again creates a new one).
    • Qoder: continues in place. The new session's first turn has qoderclicn --acp session/load the original session directly, without repeating any of the context; after that it is an ordinary Qoder session (approval dialogs, phone access and Cardputer all work). A task still running cannot be taken over (two processes would write one session at once), so take it over once it has answered; after a takeover, follow-ups from the glasses or the app and the DSH side go on separately. If the original session fails to load (e.g. its folder was deleted), a new session starts with the record instead.

Module: Phone access

Serves DSH over Tailscale to the Tailscale account this computer belongs to. What the phone opens is DSH itself: you can see every session's live progress, keep any session going (cold sessions resume on demand) and approve on the phone, across all models (Claude Agent, Codex subscription, DeepSeek, MiniMax…).

  • Entry: "Phone access" at the bottom of the sidebar (with the sidebar collapsed only a phone icon shows, with a green dot when on). The panel gives exactly one next step for each state: not installed → Download Tailscale; not connected → open Tailscale; HTTPS not enabled on the tailnet → open the DNS page of the Tailscale admin console and turn on HTTPS Certificates (once); ready → Turn on. Coming back from another window re-checks automatically.
  • Once on: the panel shows a QR code and the URL (https://<machine>.<tailnet>.ts.net); scan it with the phone to open it, no pairing needed. The URL contains no credentials, so it is safe to show anywhere.
  • The phone interface: on narrow touch devices, the plugin adjusts DSH's official interface with a pure CSS layer: 16px input fields to avoid iOS zoom, 44px touch targets, safe-area padding, the right sidebar (Source Control, terminal, files) filling the whole screen when expanded (DSH's own narrow-screen layout only knows the old right column and squeezes it to zero height), hover tooltips and the desktop-only "Open in file explorer" hidden, composer popup menus turned into bottom sheets, and settings in a single column. The desktop is unaffected.
  • Settings: DSH only opens settings reads and writes to pages opened on this machine, so "Settings → Agent Hub" on a phone only shows a notice; change settings on the computer.
  • Lifecycle: when DSH exits the serve rule is withdrawn, and it is restored automatically at the next start. Clicking "Stop" withdraws the rule and remembers that it is off.

How it works:

  1. tailscale serve serves <machine>.ts.net over HTTPS on the tailnet and forwards requests to a forwarding port the plugin opens on 127.0.0.1. The plugin only rewrites the rule for its own port; if that port is already taken by another serve rule, it reports an error rather than overwriting it.
  2. The forwarding port hands requests to DSH's own web service as loopback. Because the Host is rewritten, DSH's existing DNS-rebinding and cross-site checks no longer apply, so the request is checked again against the public domain before forwarding: the Host must be this machine's ts.net name, the Origin may only be same-origin, and requests with sec-fetch-site: cross-site are refused (except top-level navigations that open the page from a link in another app or web page, i.e. GET with sec-fetch-mode: navigate: these are not forwarded to DSH but answered with a tiny page that reloads itself, turning the navigation into a same-origin one so DSH's SameSite=Strict session cookie is sent). In addition, only requests whose Tailscale-User-Login equals the account this machine belongs to are let through; that header is written by tailscaled.
  3. Account identity only decides "whether you may come in"; signing in still uses DSH's native mechanism: when the phone opens it for the first time without a DSH session cookie, the plugin redirects through DSH's own launch token URL (connection.authenticatedUrl), and DSH sets its standard session cookie (30 days). The plugin stores and checks no credentials.

The port can be changed in Settings → Agent Hub (443 by default, giving a URL without a port).

How this differs from the Claude app's Remote Control: phone access covers every session of every model; Remote Control covers only online Claude Agent sessions, but through the official client (see above).

Codex: Codex models in DSH call the subscription interface directly through dsh-codex-subscription and create no Codex threads of their own, so the Codex app's Remote Control cannot see them; use phone access to follow them. This plugin only reads from Codex and never writes, and does not affect Codex's own Remote Control.

Module: Source Control (Git)

A tab in the right sidebar (alongside "Files" and "Terminal") that works on the current session's workspace folder. It opens three ways: the Git button at the top right of the session title bar (its badge shows the number of uncommitted changes; clicking again while the Git tab is in front collapses the sidebar; it is also on the page of a new session that has not sent its first turn, left of the sidebar toggle), Git on the empty sidebar's guide card, and Ctrl+Shift+G (⌘⇧G on macOS).

  • Changes: the commit box is at the top (Ctrl+Enter commits; its dropdown has "Commit & push" and "Amend last commit"). A line above the commit button shows whose identity this commit will use, and where that identity comes from (this repository, a matching rule, the global default, environment variables). The identity is written in the repository's own .git/config, shared by all worktrees, so terminals, agents and other editors commit with it too. When the repository has no identity of its own yet, the first commit from here asks you to choose one first (a preset identity, the former global identity or a new one) and binds it before committing. Before committing, the host checks with git var: if the identity actually in effect is no longer the one shown in the interface (e.g. it was changed in a terminal), the commit is refused. When amending, if the original author is not the current identity, you are told the original author will be kept, with one click to switch it to the current identity (--reset-author). When nothing is staged, the button reads "Stage all & commit". Below come merge conflicts, staged, changes and stashes. Row buttons open the file, stage/unstage and discard changes; right-click gives the full menu. Keyboard: ↑↓ move, Enter shows the diff, Space stages or unstages, Delete discards changes.
  • List / tree: the toggle in the top bar shows changes as a folder tree: paths containing a single subfolder merge into one row (e.g. src/client/git), and folders come before files. A folder row can be staged, unstaged or discarded as a whole, and ←→ fold or unfold it. The file list in commit details can be toggled too, and the choice is remembered globally; previous/next file in the diff follows the order currently shown.
  • Diffs: opening any file (from the changes list, an expanded commit, uncommitted changes) shows the diff over the agent window in the middle, covering the conversation area; the Source Control panel keeps its size, position and scroll, and only highlights the file currently open. Closing it (Esc or ✕) brings the conversation back as it was. Diffs are side by side by default (inline when narrower than 640px, toggleable), with word-level highlighting, "Show full file", and Alt+↑↓ to move to the previous/next file. On a phone, or when the middle area is too narrow, the diff opens full screen. Diffs of workspace files refresh automatically as the agent changes them. Conflicted files are colored as "current / incoming", and you can take either side as a whole or mark them resolved.
  • Graph: all branches laid out as swimlanes in time order, with a dashed node at the top for uncommitted changes.

Performance: the change list, graph and diffs render only visible rows, and rows whose position did not change are not re-rendered while scrolling, so thousands of files and tens of thousands of diff lines stay smooth. The graph loads 400 commits per page, loading the next page when you scroll to the bottom. Status is polled quickly only while the Git tab is in front and the page is visible; with the tab in the background, only the title bar badge checks every 10 seconds. On Windows and macOS the host answers polls with file watching: when nothing changed, git does not run; when only workspace files changed, only git status reruns, reusing refs, stashes and in-progress operations; only changes in .git (commits, fetches, branch switches) reread everything, with a full recalibration at least every 30 seconds, and the watcher closes after 3 minutes unused. So with focus it polls every 1.5 seconds (2.5 seconds on Linux, which has no watching), otherwise every 10 seconds. The plugin's own operations, commands run on the command line and the "Refresh" button always reread everything. Unchanged results trigger no re-render, the graph is reread only when refs change, and an open diff is not re-parsed when its content has not changed. Reads use GIT_OPTIONAL_LOCKS=0, so they never fight the agent's own git commands for index.lock.

Security: git runs on the host, through DSH's authenticated /api channel (agent-hub/git). The folder must be inside a workspace registered in DSH; branch, tag, commit and other arguments are validated on the host, names starting with - are refused, and paths always go after --. Network operations never show interactive prompts (GIT_TERMINAL_PROMPT=0).

Module: Code editor

View and change code in the workspace beside the session. The editor is a kind of tab in the right sidebar, so multiple tabs, two-pane split, floating window, full screen, side by side with the session and per-session layout memory all come from DSH's own sidebar; when the window is narrower than 768px (phones) the sidebar goes full screen automatically.

  • Opening: the file tree, file links in the session, line references in tool calls and search results all open text files in a code tab, at the matching line. Markdown and HTML still render first in DSH's preview; Edit on the right of the preview title bar switches to the editor, and Preview in the editor switches back. Images, PDFs, Office files and spreadsheets keep their existing previews.
  • Editing and saving: built on CodeMirror 6, with syntax highlighting for 40-odd common languages; multiple cursors (Alt+click, Ctrl+D), folding, bracket matching, and auto-indent that detects spaces or tabs from the file's existing style. Saving is manual only, with Ctrl/⌘+S, keeping the file's original line endings (LF/CRLF) and BOM. Tab titles and the path bar mark unsaved changes with a dot; closing a tab with unsaved changes asks first: save and close / don't save / cancel. Unsaved content is also backed up in the browser and restored, with a notice, after a refresh or crash.
  • When the agent edits the file at the same time: with no unsaved changes, the editor follows the disk content live, keeping the cursor and scroll position; with unsaved changes it does not overwrite them, but shows a conflict at the top, where you can Compare (show the diff against the disk version in the editor, keeping or reverting each change), Use the disk version or Overwrite with mine. Saving sends the content hash from when editing started, and the host refuses to write if the file was changed by someone else, so the agent's changes are never silently overwritten.
  • Change markers: right of the line numbers, lines added (green), modified (blue) and deleted (red triangle) relative to HEAD are marked; they refresh after commits and when the window regains focus.
  • Formatting: Shift+Alt+F. The project's own formatter comes first, with the project's configuration applied: the project's Biome (when there is a biome.json) or Prettier, then ruff/black, gofmt, rustfmt, clang-format, shfmt, stylua, taplo and others on PATH, and finally, for web languages, the Prettier bundled with the plugin. The result is applied as a minimal diff, the cursor stays put, and one undo reverts it.
  • Add to session: Ctrl/⌘+L inserts the selected range into the current session's composer as @path L12-30 (multiple selections separated by commas; with no selection just @path), and the model reads it with its file tools. When the file has unsaved changes you are told that the model reads the version on disk.
  • Definitions and references: F12 or Ctrl/⌘+click (an underline appears when hovering with Ctrl/⌘ held) goes to the definition, and Shift+F12 lists references in the search tab. Definitions are found by search, without a language server: the identifier at the cursor is matched against each language's declaration forms (, , , , and so on), the same for every language, with no index. Results are sorted by closeness to the current file; a single hit jumps directly, several show candidates; pressing F12 on a declaration lists references instead.

Performance: CodeMirror and the language packs are a separate chunk (lib/client.editor.js), loaded only when a code tab is first opened, so DSH startup is unaffected. The editor renders only visible lines and parses syntax incrementally; files over 2 MB or with very long lines turn syntax highlighting off, and files over 10 MB go to DSH's paged preview. The same file open in several tabs shares one copy of its content, and switching tabs keeps undo history and scroll position. Workspace search uses ripgrep (@vscode/ripgrep, the same as VS Code; git grep stands in when it cannot be installed), honors .gitignore, returns at most 2000 results and times out after 20 seconds; a new search cancels the one in progress, and the result list renders only visible rows.

Security: reading, writing, searching and formatting all run on the host, through DSH's authenticated /api channel (agent-hub/files). Both the session folder and the files read or written (after resolving symlinks) must be inside workspaces registered in DSH; writes are serialized per file and preconditioned on the content hash.

Module: Settings sync

Syncs the current profile's entire plugin configuration (cordis.patch.yml, including each plugin entry's disabled) and its plugin enablement list (dsh.profile.bundles in package.json, ordered) across machines through one Git remote. Each machine keeps a complete git repository in $DSH_HOME/dsh-agent-hub/sync/<profile>/; the remote is only a relay, and every machine holds the full history.

  • What is synced: the repository holds only cordis.patch.yml, plugins.json (bundle order + install sources), pins.json (the clipboard window's pinned items), reader-feeds.json and reader-favorites.json (Reader feeds and favorites), and .gitattributes. Paths under the home folder are written as ~/… (/Users/vi/pro/app and C:\Users\vi\pro\app are the same value), and text is normalized to LF; comments and !!js expressions are kept as they are. Install sources meaningful only on one machine, such as link: and file:, are not shared, and the repository keeps other machines' sources. Not synced: sign-ins, build-script approvals, version exemptions, lockfiles, and this plugin's board tasks and Claude account list.
  • Pinned clipboard: synced by default. A pinned item's identity is its text, so the same text pinned on two machines counts as one item, and when applied locally it is pinned in place in the local history without adding another row. Unpinning on one machine unpins it on the others at their next sync (the item goes back to "Recent" and is not deleted); when the two sides pinned at different times the earlier one wins, so Ctrl/⌘ 1–9 numbers match everywhere. Pins have no conflicts and need no choices. A machine whose clipboard window is not on (or whose helper is not running) takes no part: it neither reads nor changes local pins, remote pins are kept as they are, and turning it on later never mistakes "not received yet" for "deleted". Pinned content goes into your Git remote, so do not keep passwords there; to keep one machine's pins local, add agent-hub.config.palette.pins to config.sync.localSettings.
  • Reader feeds and favorites: merged the same way as pins (src/sync/collection.ts): a feed's identity is "type + address", a favorite's is "link + quoted text", so the same one added on two machines counts once; deleting on one machine deletes it on the others after they sync. Entries, read state and article text stay on each machine. To keep them local: agent-hub.config.reader.feeds, agent-hub.config.reader.favorites.
  • Not synced: some settings only make sense on one machine, such as the terminal's default shell (C:\Program Files\PowerShell\7\pwsh.exe in terminal-controller) or this machine's light/dark choice. What is listed under "Not synced" can be a whole entry (terminal-controller) or a setting within an entry (ui-theme.config.preference); these are not uploaded or compared, and applying the remote configuration keeps this machine's existing values, with whole entries left where they were. The defaults are , (automatic review sync) and (allowing a phone to change settings); the Clipboard window and Cardputer module switches (, ) are on the list by default too. The list itself is synced with the settings, so every machine excludes the same set.

Module: Review

Every day, reads the PRs waiting for my review on GitHub, analyses each in a read-only session of its own, and writes the result into a review sheet; I tick, edit and preview it on the board, and once I confirm, the plugin posts the review. The model never posts anything itself. Needs gh installed and signed in on this machine (gh auth login). The entry points are Settings → Agent Hub → Review (ticking repositories turns it on) and the To review lane at the left of the board.

  • The review sheet is the only state: <repo>/.reviews/pr-<number>.md, whose folder ships its own .gitignore containing *, so the whole folder stays out of git. The front matter holds the PR, the head at analysis time, the status, the decision (approve / request-changes / comment), the summary, the session id, and the review id after posting; the body has the change description, must fix, nice to have, this PR's rules, and notes. Each finding is one line, - [x] F1 \path:88` description, with the English comment to post below it, starting with > EN:`. The agent writes it with its own editing tools, while the interface's edits are applied by the host on the file's latest content (serialized per file, atomically replaced), so neither overwrites the other; hand edits work too, and reading is lenient. The card status (not analysed / analysing / ready / new commits / submitted / unreadable) is derived from GitHub's list and the review sheet, with nothing stored separately.
  • Sync: one GraphQL search (review-requested:@me) gets the PRs of every repository, matched to the ticked repositories by their GitHub remote. The daily automatic task starts analyses for new PRs and those with new commits (drafts excluded, at most "Analyses per sync" per run); the sync button in the lane header and cache refreshes while the board is visible only read the list and start no analyses.
  • Automation task: the daily sync uses DSH's official Schedule, with no second timer; the first migration takes the daily time from the old setting, after which time and time zone are edited only in the host's "Automation tasks". Settings → Agent Hub → Review shows the status, next run and a way to manage it; turning it on creates the native task, and after turning it off or deleting the task in the host it is not recreated automatically. The official Schedule service must be enabled. The task is linked to a dedicated session, where the settings page's "Helper model" calls one restricted sync tool; PR analysis is still done by the review bot.
  • Each run's result: the sync tool writes a summary from the actual state, kept in the linked session; the task title is updated to a one-line result such as "Board review · last run N analysed". Failed, cancelled and still-analysing PRs are counted separately; it waits at most 20 minutes, and past that limit only records the snapshot at that moment, never counting queued as done. The host's "Records" tab still only means delivery receipts, and the full result is in the linked session; a model that did not run or a failed delivery should not be taken as a successful sync. Failures set up no extra retry timer; retry by hand on the board.
  • Analysis: the PR is fetched to refs/reviews/pr-<number> (no worktree, no branch switch, the working tree untouched), and a new session opens in the workspace of the repository's main folder, using the review bot by default; right-click, long-press or on a board card and "Choose bot…" assigns another Bot to this one task (an explicit choice opens a new session for that Bot). The session is titled ; the review sheet's front matter is written first, then the first turn is sent: the diff range, changed files, unresolved review threads (resolved bugbot threads are removed before reaching the model) and the three layers of rules (global, , the sheet's own rules). At most 2 run at once, the rest queue. New commits start round 2: the previous round's sheet is kept, renamed to , and the new round looks only at the increment and says which old findings were resolved. After changing a sheet's rules, "Analyse again with these rules" has the original session look again directly when the head is the same.

This README was shortened. Open the source for the full text.

ConnectorsConnected servicesBlankRemote MCP servers (Linear, Notion, Sentry, GitHub or any URL); connect once and every model can use them. See Module: Connectors
BotsBot listBlankEach bot is a name, a one-line intro, a model, a persona and (advanced) its tools. See Module: Bots and group chats
ReviewRepositoriesBlankPRs waiting for my review in the ticked repositories are analysed; blank = off. This machine only, not exported. See Module: Review
Automatic review syncOnManaged by DSH's official automation task; the first migration defaults to 09:00, after which time and time zone are edited in the host. The settings page shows status and a way to manage it
Review botreviewerThe analysis uses this bot's model, and its persona is the analysis prompt
Helper modelAutomaticThe model for coordinating automatic sync and for "Dig in" and "Rewrite" on review sheets (cannot be Claude Agent); "Automatic" uses Claude Agent's background request model. PR analysis still uses the review bot
Comment styleBuilt-inHow the English comments and summary are written; both analysis and rewrites follow it
Extra rulesBlankAdded to every review; the repository's .reviews/RULES.md and a sheet's own rules follow
Analyses per sync10At most this many PRs start an analysis per sync; at most 2 run at a time
BoardSentry Issues URLBlankhttps://<org>.sentry.io/issues/?project=<id> or https://sentry.io/organizations/<org>/issues/; only the organization/project are read, and the data region (US/EU) is detected per organization by the Sentry connector. The environment is pinned to prod; the board offers 7d/14d. Blank = hide the alerts lane
Analysis repository pathBlankThe local repository's main working tree; this machine only, not exported
Default analysis botreviewerCan still be switched before an analysis starts; this device remembers the last choice, and Seer is never offered
Claude AgentRemote ControlOnTurns on Remote Control for every session; applies to sessions started afterwards
Full access for new sessionsOffNewly started sessions run with full permissions (Claude Code's bypass mode): tools run without asking for approval, including file writes and commands; turn it on only in trusted workspaces. Turning it on only changes the default for newly started sessions; turning it off restores approvals for existing full-access sessions at their next safe DSH turn, in a fresh process, without interrupting turns that are running or being followed through Remote Control
Sessions kept online3With Remote Control on, this many of the most recently active sessions are never closed for idleness; 0 closes them all by idle time
Close when idle (hours)2A session idle longer than this has its Claude Code process closed; the next turn resumes it from the transcript (0.25–168)
Background requestsAutomaticDSH's non-conversation requests for Claude Agent and Cursor sessions (context compaction, session titles) are answered by this model. "Automatic" uses the model new agents get; when that is Claude Agent, Cursor or a Bot, the first other model
Qoder & glassesWorkspaces the glasses can useNoneEach ticked repository keeps a qoderclicn remote-control daemon running while DSH runs, so tasks sent from the Qwen glasses, the Qoder app and qoder.cn run on this machine. Can only be changed on the computer
Task slots per workspace8How many tasks one workspace keeps at once (1–32). A task that has answered still holds its slot for follow-ups until it is archived in the app
Start with DSHOnOff: start them by hand under "Glasses workspaces & cloud" in the Relay panel
Model selectorProviders shownAllUntick a provider (such as the built-in DeepSeek) to hide all of its models from the model selectors; DSH's background calls and Git commit messages can still use them. If the model new agents get belongs to a hidden provider, pick another default first
Models offered per agentAllFor Claude Agent, Codex, Cursor and Qoder models and Hermes personas, tick which ones appear in DSH's model selectors; with everything ticked (the default), models added later appear too. Only affects the model lists, not existing sessions or background calls
Default thinking levelEach model's own defaultPicking a model in a model selector sets its reasoning effort to this level, for every model. A model without that level gets the nearest lower level it has (off < minimal < low < medium < high < xhigh < max), else the nearest higher one. Existing sessions keep their level and apply it only when switching models
Per modelShared settingA model's own default thinking level, over the shared one; Bots take the level of the model they run on
Shortcut: open model menuNot setWith the cursor in a session's composer, opens the model list directly: ↑/↓ browse models, Enter picks one, Esc backs out; the cursor then returns to the composer. Click the key field and press a combination to bind it (it must include Ctrl, Alt/⌥ or ⌘); Esc cancels. Registered in DSH's own shortcut service: checked for conflicts together with DSH's shortcuts, and listed in DSH's Shortcuts cheat sheet (Mod+/). Bindings are saved per device in DSH's keybindings, not in cordis.patch.yml, and are not synced
Shortcut: cycle thinking levelNot setWith the cursor in a session's composer, moves the current model to its next thinking level, wrapping from the highest back to the lowest; bound the same way
Fonts and sizesInterface fontDefaultThe default font, or one of the ui-ux-pro-max recommendations Roboto Mono, JetBrains Mono, Source Code Pro, Fira Code, IBM Plex Mono; the fonts ship with the plugin and load on demand
Code fontDefaultFont of code blocks, the editor, diffs and the Git terminal; missing glyphs fall back to the system font
Interface text size100%80–130%, in 5% steps; scales the hard-coded px font sizes of the sidebar, menus, buttons, settings and the rest of the interface by one ratio, keeping each place's relative size, and leaving spacing and layout alone. Conversation text size is not affected; it is still set by DSH's General → Font size
Per regionFollowsThe left sidebar, main column, right panel, settings window, and menus and dialogs can each go ±20% on top of the whole; each can be reset on its own, and "Reset all" restores everything at once
Phone accessHTTPS port443443 gives a URL without a port; changing it while phone access is on moves it to the new port
Git commitsStrict identitiesOnRefuses to commit in any repository without an identity of its own (from terminals and agents too). Turning it on sets the global user.useConfigOnly and moves the global user.name/email/signingkey aside to agent-hub.* in ~/.gitconfig; turning it off restores them exactly, and never undoes a useConfigOnly you set yourself
Preset identitiesBlankName, email and an optional signing key, synced to your other devices with the settings. The Git pane offers these when choosing an identity for a repository
ModelDefaultThe dropdown lists every model currently registered in DSH (the same catalog as the model selector, grouped by provider; excluding Claude Agent, which only answers conversations). "Default" uses the model new agents get; a saved model that is not registered right now is kept and marked
Extra instructionsBlankAppended to the prompt, e.g. "Use Conventional Commits, write the subject in Chinese"
ENG-123
vi/eng-123-fix-login
mcp__linear__get_issue
lives only in the branch name
  • Linear accounts and repositories: the Linear connector card has "Repositories", where you tick which local repositories this account is for (recognized by main folder, so every worktree of the same repository counts). Creating a worktree in a repository, or looking up a worktree's ticket, uses the account linked to that repository; an account with no links is used for every other repository. Several Linear accounts can be connected at once (recognized by URL; the id need not be linear).
  • Module: Review
  • Pairing PRs and tickets: when a PR's branch name contains the identifier of a Linear ticket on the board, the PR card shows that ticket's chip (Linear logo, identifier and a small dot for its status); clicking it opens the ticket and scrolls its card into view. Hovering or keyboard-focusing either card outlines the other in its lane. No Linear round trip, no extra request.
  • Filter by repository: picking a repository in the top bar shows only that repository's local tasks, and tickets from the accounts linked to that repository (or to none).
  • Start: "Start" at the bottom right of a card (appears on hover or keyboard focus, always visible on touch screens), with the same button in the details. The repository is determined automatically: a local task uses its own; a repository picked in the top bar is used; an account linked to exactly one repository uses that one; when several are possible a menu pops up (a bottom sheet on narrow screens). Then:
    • Worktree: an existing one is looked for first, with the same branch name or a branch name containing the ticket identifier (registering it as a workspace if it is not yet); only if none exists is a new one created on Linear's suggested branch name (the ticket is read once more before the worktree is created; local tasks use task/<title>). A new worktree opens a session with the composer prefilled with the ticket's content; an existing one just jumps there, with no new session.
    • Ticket sync: a ticket not yet started moves to the team's "In progress" status; when Linear says explicitly that it has no assignee, it is assigned to me, and if someone has it or the assignee cannot be read it is left alone. Both changes are saved in one update.
    • Reliability: starting the same card in the same repository several times at once (double clicks, several windows, the board and Git pane together) runs once, and later calls get the same result; when Linear is unreachable, the worktree is still created with the branch name already on the board, with a notice that the ticket was not updated; if the worktree is created but the ticket update fails, only a warning is shown, never a pretend move to "In progress".
    • Picking a ticket in the Git pane's "New worktree" (without changing the branch name) goes through the same flow and syncs the status too.
  • Worktrees on cards: which card already has a worktree is derived in the browser from registered workspaces by branch name, without Linear and without storing any mapping. Create or remove a worktree from the sidebar, the Git pane or anywhere else and the card updates within about 300 ms; the card shows the branch name, and clicking it goes straight to that workspace (no new session).
  • Moves show up in the interface at once and are rolled back with a notice if Linear refuses them. While the page is visible it refreshes every minute, and also when the window regains focus.
  • Layout: on wide screens lanes sit side by side and details slide in from the right; the Sentry alerts, Done and Canceled lanes fold independently (the old canceled-lane preference migrates automatically). Below 640px (phones or narrow windows) one lane shows at a time: status tabs with counts at the top, swipe left or right to switch, and details open as a bottom sheet.
  • Settings → Agent Hub → Claude Agent → Background requests
    UNSUPPORTED_REQUEST
  • MCP: only the MCP DSH provides (connectors and team tools) is used; the MCP servers configured in Claude Code itself are not loaded.
  • Remote Control (can be turned off in Settings → Agent Hub): each session's Claude Code process starts with Remote Control on (the same call as the official VS Code extension), and the first reply starts with a claude.ai/code link, so you can follow and continue the session from the Claude app or claude.ai.
    • Turns started from the phone sync back into DSH: the plugin reads the prompt from Claude Code's transcript by the turn's user message uuid and delivers it to the DSH session as a plugin-originated follow-up; the DSH turn that starts from it joins the turn Claude Code is already running without sending the prompt again. So the phone turn's question and streamed reply appear in DSH as they are, and serve as context if you later switch models. When the prompt cannot be read (e.g. slash commands, internal turns without a user message), DSH falls back to noting "N turns ran remotely" on its next turn. The transcripts of non-default accounts live in separate folders, and the SDK's getSessionMessages does not yet take a per-call config folder; to avoid reading the wrong account, the content of such Remote Control prompts is not synced, and only the count of remote turns is noted on the next turn.
    • Approvals: for a turn DSH is following, the approval appears both in DSH's dialog and on the phone, and whichever answers first wins; phone turns DSH is not yet following are approved on the phone.
    • Time online: the process closes after a period of idleness (2 hours by default), and the next turn resumes it from the transcript. The few most recently active sessions (3 by default) are exempt and stay online, convenient for continuing from the phone. After a host restart, send one turn in DSH to bring a session back online.
  • Model catalog: from Claude Code's supportedModels() (initialization only, no model call; the probe process starts no MCP servers), along with each model's effort levels. The catalog persists in $DSH_HOME/dsh-agent-hub/claude-models.json; once stale the old catalog is used while it refreshes in the background (asking an online session directly when there is one), so a turn never waits for the catalog.
  • Session continuity: each reply records the ACP session id in replayState, and the next turn (including after a host restart) continues it with session/load, sending only what Cursor has not seen yet; if you switched to another model midway, the conversation in between goes along as context. When it cannot be loaded (the session was deleted), a new one is opened with the whole conversation as context. Cursor does not read DSH's system prompt, so the global instructions (and a Bot's persona) are written at the top of the first message, and sent again on the next turn after they change.
  • Calling DSH skills with /: as with Claude Agent, the invoked skill's instructions go at the top of the prompt sent to Cursor, and the reply starts with ⚡ Skill /name.
  • Usage: after each turn, the plugin reads the usage events Cursor recorded for this ACP session's id (usually readable after 1–2 seconds, waiting at most 6 seconds) and appends a note to the reply: Cursor usage: 7.81K in · 38 out · 7.26K cache read · $0.028 (composer-2.5-fast), the parenthesis naming the model variant actually billed. Events that arrive late are picked up by the next turn, with no duplicates or gaps. Each session's cumulative tokens are written in replayState and counted by the local token statistics by difference (as with Claude Agent); the billing cycle's allowance and on-demand spend are in Plan usage. DSH's built-in usage statistics count output tokens only: DSH treats the input part of usage as context occupancy, while Cursor manages its own context.
  • Sign-in failure: when the CLI is not signed in, the turn fails with "run agent login in a terminal", error code CURSOR_AUTH, and is never retried automatically.
  • Bots: a Bot's model can be a Cursor model, and its persona is written into the first message along with the global instructions. As a team member it cannot use DSH's team tools yet (send_message and so on); the Lead has to read its session to get its report.
  • / commands: commands and skills in local .cursor folders are registered as DSH skills, so typing / in a session on any model offers them, with no need to pick a Cursor model. The session folder's project (the nearest ancestor with .git) and the home folder are scanned for .cursor/commands/*.md and .cursor/skills/<name>/SKILL.md (skills/<name>.md is accepted too). The name comes from the frontmatter name; if missing or invalid, from the file name (the folder name for skills) in lowercase kebab form (Fix_Lint.md → /fix-lint); the description comes from the frontmatter description, else the first line of the body. Commands are for manual invocation only and stay out of the model's skill catalog; skills honor disable-model-invocation and user-invocable. On a name clash DSH's own skills win (after the project's .dsh and .agents, and the user's ~/.dsh and ~/.agents). Cursor's bundled ~/.cursor/skills-cursor operate the Cursor editor and are not registered. The folders are watched: adding, renaming or deleting a file, or editing its frontmatter, updates the next / menu without a restart.
  • Ctrl/⌘ C
    Space
    Space
    Esc
  • Translation: Chinese / English / Français; "Auto" translates Chinese into English and other languages into Chinese. Translation uses the chosen bot's model, led by its persona (if none is chosen, the helper model with no persona); the last language and bot are remembered. Enter translates first, and once the translation is the latest result, Enter again types it into the target app; Ctrl/⌘ Enter translates and types directly. When typing fails (the target window is gone, blocked by a program running as administrator, a modifier key still held, over 4000 characters), the translation is copied to the clipboard instead with a notice, and is never typed into another window. Closing the window mid-translation or translating again cancels the previous one, and a cancelled result never triggers typing; the last 50 translations are cached in memory.
  • The hotkey works only while DSH is running (when the host exits, the helper exits with it). The old combination is released only after the new one registers successfully; if another program holds it, the settings show the reason and the old hotkey stays in force. On a French keyboard Ctrl+Alt is AltGr, so choose another combination.
  • Paste statistics only count entries "used through the window" (typed or copied); system-wide Ctrl+V is not monitored.
  • Windows needs the WebView2 runtime (bundled with Win11); the macOS build is the same as for the Cardputer module and needs Accessibility permission to type, and newer macOS versions may ask once about reading the clipboard in the background. The macOS part passes Rust type checking but has not been verified on real hardware.
  • macOS development builds: npm run build:native signs and verifies at a temporary path first, then atomically replaces the helper, keeping the old version on failure. It prefers to keep the installed helper's signing certificate and never silently switches to another certificate or ad-hoc signing; set DSH_SIGN_IDENTITY only when you mean to change certificates, and the first migration may need permissions granted again. Building is not restarting: after building, switch Clipboard window off and on again in the Modules card to load the new version. Whom TCC grants permission to depends on the launch chain; launched by DSH it is usually DeepSeek Harness.app, launched from a terminal it may be the terminal, not just the helper's signature. If it still fails with permission granted, reload the helper first, and if needed restart the app that launched it, rather than resetting all permissions outright. For troubleshooting, send {"id":1,"op":"perm"} to the helper's standard input; it returns the two independent statuses AX and PostEvent, its PID and its actual path, without sending any keyboard events.
  • Following up: in the Relay panel in the left sidebar (see Module: Relay). The "Glasses workspaces & cloud" item shows whether each workspace is online, its processes' memory and CPU, and idle reclaim; you can start or stop daemons, reclaim now, and create a task on the web there. Tasks sent in are listed in the panel together with other Qoder and Hermes conversations, where you can read the full record and take them over in DSH. A daemon that exits abnormally is restarted automatically after increasing delays of 5 seconds, 15 seconds, 1 minute and 5 minutes.
  • Idle workers and reclaim: every glasses task has a worker process of about 250 MB, which stays after answering, waiting for follow-ups. Workers hold no state of their own: when a task without a worker gets a follow-up, the server dispatches it again and a new worker restores the conversation from the server within seconds. So before every daemon start (DSH start, abnormal restart, reclaim), it first waits for the previous run's workers to exit, then clears its workers record (~/.qoder-cn/remote-control/…/workers/remote-control-workers.json, touching only format version 4 and only when no other daemon is using it), and restores no idle worker after starting. Idle reclaim is therefore just a restart: it happens once every task has been answered and has been idle for the set number of minutes, and is postponed while a task is running or a hand-off or notification is waiting. When the workers record's format is unknown or cannot be changed, no more reclaims happen in this run, and the panel states why.
  • Glasses handing work to DSH's models: when the daemon starts, DSH puts a dsh command on its PATH ($DSH_HOME/dsh-agent-hub/bin/, running dsh.cjs with the host's bundled runtime) and writes a Qoder skill into ~/.qoder-cn/skills/dsh/. Tell the glasses "have Claude fix the login bug in vimental", and Qoder runs dsh ask --agent claude --project vimental "…" in the background and first replies "handed to Claude"; DSH opens a new session (its title starts with "Glasses ·", visible in the sidebar and open for continuing), and when the turn ends Qoder is woken up to tell the glasses the result. --agent can be claude, codex, cursor, qoder, hermes or bot:<id>, --model takes part of a model name (e.g. opus), and --project takes a DSH project name or path; without them, DSH's default model and the current folder are used. When a name is ambiguous the command lists candidates and Qoder asks back. dsh agents, dsh projects, dsh status [session] and dsh wait <session> show the choices and progress. Approvals in the DSH session pop up as usual on the computer, phone or Cardputer, and a notification is also pushed to the glasses.
  • Notifying the glasses: things DSH needs to tell the glasses (the bell, a handed-off session waiting for approval, dsh notify) are sent as a "[DSH notice] …" message into the glasses' most recent Qoder session: preferably one that answered most recently in an online workspace and still has its worker, otherwise one whose worker has been reclaimed. It works like a follow-up from the Qoder app (qoderclicn --remote-session, which only runs in a terminal, so DSH's bundled node-pty is borrowed to run $DSH_HOME/dsh-agent-hub/qoder-post.cjs with the host runtime in a child process); once the worker answers, the glasses announce it, with no preparation needed on the glasses. A message counts as delivered only once it appears in that session's transcript; if that session's environment is offline, the next one is tried. When no session is available yet, or sending fails, notifications are held (at most 20) and retried when a session answers or after 1 minute, with several arriving together merged into one. Each notification costs that session one turn of allowance. While sending, that workspace's idle reclaim is paused.
  • Notify the glasses when done: the bell to the right of the session composer, next to scheduled send. When lit, the end of this turn (finished, failed or stopped) sends the glasses one sentence with the start of the answer, once only; an approval wait midway also sends one. When the glasses have no session available yet, the bell gets an orange dot, and the notification is sent once it can be delivered. Any script or model in DSH can also send one with dsh notify "…". dsh accepts only requests on the loopback address that carry this run's token ($DSH_HOME/dsh-agent-hub/bridge.json, readable only by the current user), so web pages cannot call it.
  • Known limitations (Qoder CLI CN 1.1.73): remote tasks run in auto permission mode, decided by the server, so file writes do not ask for approval and changes land directly in this repository (or in the worktree, when one was asked for); concurrent tasks share the same folder; answered tasks hold their slots indefinitely, and when the slots are full the glasses report "scheduling service error"; large tasks occasionally fail on the server (Remote projection queue is unavailable), which the panel marks as failed.
  • Hermes: continues with the record. Hermes' ACP can only load sessions it created itself over ACP, not those from Telegram, the terminal and the like, so the new session's first turn puts the original conversation's record (the latest ~24,000 characters, one line per tool call) ahead of the question. The original conversation itself is not updated.
  • Terminal: "Continue in a terminal here" opens a terminal window on this computer running the resume command (Qoder qodercn -r <session>, cloud sessions qodercn --teleport <session>; Hermes hermes -p <persona> --resume <session>). It only appears when DSH is opened on this machine; on a phone, use "Copy resume command".
  • Sending to the cloud: under "Glasses workspaces & cloud" you can hand a task to Qoder's cloud (qodercn --remote, run in the chosen workspace), which keeps going even with the computer off; the session it creates is listed, marked "Cloud", and you follow its progress on the web, bringing it back here with the terminal command. Cloud availability depends on your account; when the CLI reports it unavailable its message is shown as is. It is done once the CLI prints the session link; if nothing is printed within 8 seconds, the panel says "Sent" and the session shows up in the list once created.
  • Completion notifications: while DSH is open, when a glasses / app task, a cloud task or a Hermes cron job goes from running to answered or failed, a toast appears in the window's bottom-right corner (at the top on a phone); "View" opens the Relay panel on it. No toast while the panel itself is on screen. The Relay icon in the sidebar shows a blue dot while a task is running, and a red dot when there is an unseen failure from the last 24 hours.
  • Narrow screens: the list and the record become two steps, with Back at the top left of the record; the composer and search box use 16px text to avoid iOS zoom.
  • Click a commit node to expand its details in place (as in Git Graph), without switching pages: the details are inserted below the row, the lanes keep running down the left, and the list scrolls so the whole block is visible. In a wide panel the left side has the message, refs, hash, parents (click to jump to and expand a parent), author and date, and the right side the changed files (list or tree); in a narrow panel they stack vertically. Click again, ✕ or Esc to collapse; while expanded, ↑↓ move the details to the adjacent commit. The "Uncommitted changes" row expands the same way, listing the workspace's files.
  • Double-click a commit to switch the working branch (as in VS Code's Git Graph): if the commit has a local branch it is checked out, with a choice when there are several; if it has only remote branches, a tracking branch is created and checked out; with no branch, it checks out a detached HEAD after confirmation. Double-clicking a branch badge checks out that branch directly.
  • Right-click (or the ⋯ button, or a long press on a phone) opens the action menu: checkout, new branch or tag, cherry-pick, revert, reset (soft/mixed/hard), copy hash, and for each local branch on that commit Push, Force push (--force-with-lease) and Force push (--force). Both force pushes ask for confirmation and explain their risks: lease refuses the push if someone else updated the remote, while --force overwrites it outright. Right-clicking a branch badge offers push, force push, merge, rebase, rename and delete.
  • Command line: the terminal button in the top bar opens a command-line drawer at the bottom of the panel. On reopening, terminals still running come back as they were; terminals whose process no longer exists (e.g. after DSH restarts, or the process exited) are removed automatically; if none remain, a new terminal is created automatically.
    • Commands run in the project folder (repository root), and the prompt shows the current branch; output keeps its colors.
    • While a command runs you can keep typing in the input box, and what you type goes to that command, so prompts that need line-by-line answers work. Ctrl+C interrupts (child processes included), ↑↓ browse history, Ctrl+L clears the screen.
    • When a command finishes, status and graph refresh immediately. The drawer height can be dragged, and double-clicking the divider restores the default height.
    • Right-click after selecting text in the terminal to copy it (the selection clears and "Copied" shows, as in Windows Terminal); right-click without a selection does nothing.
    • Windows uses PowerShell 7 (Windows PowerShell if it is missing); other systems use the login shell. Pagers and credential prompts are turned off, so commands never hang waiting for a terminal.
    • For programs that need a real terminal, such as full-screen editors, the button at the drawer's top right opens DSH's full terminal, also started in this project folder.
  • Top bar: a branch switcher (searchable; type a name and press Enter to create a branch), a sync button (↓ pull ↑ push; "Publish branch" when there is no upstream), and a "More" menu with fetch, pull, push, the two force pushes and stash. While a merge, rebase or cherry-pick is in progress, a banner offers "Abort" and "Continue".
  • Worktrees: the third tab lists all worktrees of this repository (current, main folder, locked and missing folders are all marked), each row showing the branch, location, number of uncommitted changes and number of sessions.
    • Left sidebar grouped by repository: when the left workspace list uses View options → Group by → Workspace tree, all worktrees of each registered repository nest under the repository's main folder, wherever they live: git decides what is the same repository (a shared .git), so worktrees created under <repo name>.wt/ by older versions, or created by hand, nest too. The nesting is where DSH places native child workspaces: after the main folder's title row and before the main folder's own sessions, indented one level; titles show only the branch name (titles you renamed by hand are kept). Folding the main folder folds its worktrees with it, and the title row shows how many worktrees are folded. The main folder's (original repository's) own sessions are gathered under one node named root, a sibling of the worktrees and listed first, which folds on its own (the fold state is remembered per repository), its title row showing the number of sessions while folded. Repositories without worktrees, and repositories whose main folder is not registered as a workspace, stay flat as usual. By workspace and the flat view stay exactly as DSH has them. The plugin never moves or removes nodes in the host's list (it only adds the root row) and does not change the workspace order; it only adjusts display order and indentation with styles, and turns itself off when the host's list structure is not what it expects.
    • Every worktree is a DSH workspace: new ones go inside the repository at <repo>/.worktrees/<branch> (e.g. proj/.worktrees/feat/login, automatically written into the repository-local .git/info/exclude so it never shows up among the main folder's changes), and are registered as workspaces right away, titled repo name · branch. Sessions, the Git pane, terminals and Claude Agent opened there all work in that folder, leaving the main folder alone. "The same project" is decided by git itself (a shared .git), with nothing stored separately.
    • New: enter a branch name, and the dialog shows the folder to be created live. With Linear connected you can also search for and pick an issue directly; see Connectors. An existing local branch is checked out directly; with only a remote branch, a tracking branch is set up; otherwise a new branch is created from "Based on" (blank for the current branch). By default a new session opens in it after creation.
    • Opening a project opens all its worktrees: when a workspace is added in DSH (from anywhere) and it is inside a git repository, the repository's other worktrees listed by git worktree list (main folder included) are registered as workspaces along with it, titled by folder name (as when adding a folder by hand, not by branch name), nesting directly under the repository in the "Workspace tree" view; worktrees whose folders no longer exist are skipped, and ones already registered keep their titles. This is inferred only once, at the moment of adding: if you later unregister a worktree by hand, it is not added back automatically unless you add a folder of that repository again. The host's "Add workspace" puts new workspaces at the top of the list, so in the "By workspace" and flat views these worktrees come first.
    • Refresh workspaces: next to the search button in the left workspace title bar there is a refresh button that registers every worktree of each registered repository again, covering what automatic registration missed: repositories added before the plugin was upgraded or while it was not running, and worktrees created later on the command line. The button spins while it looks, and the hint text then says how many were registered.
    • Open: clicking a row (or "New session") creates a session in that worktree; worktrees created on the command line and not yet registered are registered at that point.
    • Delete: the confirmation states which folder will be deleted, how many sessions will be archived and how many uncommitted changes will be discarded, and optionally deletes the branch too. Before deleting, its sessions are archived (refused if a session is still running, undoing the archiving already done), Claude Agent processes running in that folder are closed, and then the folder and its workspace registration are removed. The main folder and the worktree you are in cannot be deleted.
    • Worktrees can also be locked/unlocked, have their path copied, and missing ones pruned.
    • When a branch is already checked out in another worktree, the branch switcher marks it "in another worktree"; choosing it, double-clicking its commit or right-clicking "Open its worktree" opens that worktree directly; right-clicking a branch also offers "Check out in a new worktree".
  • Layout follows the panel's own width: below 760px details slide in from the right over the list (phones and narrow sidebars); from 760px list and details sit side by side; from 1200px it splits into graph, commit and diff columns. On touch screens tap targets are larger, hover-only buttons are always visible, and menus and dialogs become bottom sheets.
  • function X
    class X
    def X
    fn X
    X = …
  • Search: Ctrl/⌘+F finds, Ctrl/⌘+H replaces and Ctrl/⌘+G goes to a line within a file. Workspace search is the sidebar's Search tab (also reachable from the start page; at the top right of the session title bar, left of the Git button, there are two shortcut buttons, Workspace files and Search, which work like the Git button: one click opens that tab, and clicking again while it is in front collapses the sidebar). Its shortcut is Ctrl/⌘+Shift+F on the desktop and Ctrl/⌘+Alt+F in a browser (in a browser, Ctrl+Shift+F is DSH's "Fork session"); pressed from the editor, it takes the selected text or the word at the cursor along. Case sensitivity, whole word, regular expressions, and files to include/exclude (comma-separated globs) are supported. Results are grouped by file; clicking one opens it in the pane of the code tab, and the search results stay beside it.
  • Layout: path and action buttons share one 32px row; the status bar at the bottom shows line and column, selection count, indentation, line endings, encoding and language; when the sidebar is narrow, secondary buttons fold into "More actions". On touch screens buttons are larger, the status bar is hidden, and selecting text brings up an action bar at the bottom with "Go to definition / Find references / Add to current session / Copy".
  • terminal-controller
    agent-hub.config.review.auto
    agent-hub.config.remote.phoneSettings
    agent-hub.config.modules.palette
    agent-hub.config.modules.device
    • Choosing: excluded items show as tags; click × to sync them again. "Choose from the settings…" lists every entry in this machine's profile and in the last fetched remote version, searchable and expandable two levels under config; ticking an entry stops the whole entry from syncing, and the settings in it then show as ticked. Entries only the remote has are labeled "Remote only"; items neither side has any more are labeled "gone".
    • Former "per-system entries": merged into this list, each now a whole entry that is not synced; old copies in the repository marked platform: are no longer read and are cleaned up at the next push.
  • Sync flow (Settings → Sync & updates → Settings sync) has a single Sync button. The status line shows ↓N ↑M: N remote changes to take and M local changes not pushed yet (as of the last fetch). After clicking "Sync":
    1. The remote is fetched and, with "the common ancestor of the version this machine last applied and the remote" as the base, merged three-way item by item: each configuration item, each plugin's enablement, the plugin order and each plugin's version count as one item. What only one side changed is taken from that side; what both sides changed to the same value needs nothing. Lists with an id, such as Bots and connectors, merge entry by entry, so when two machines each add a Bot, both are kept.
    2. No conflicts: the merged result is applied to this machine and pushed directly, without listing the full diff or asking which side wins.
    3. Conflicts (both sides changed the same item to different values): nothing is changed; only the conflicting items are listed. Each item is a pair of radio cards, "This machine / Remote", showing their values, with nothing selected by default; you can choose item by item or in bulk with "All from this machine / All from the remote". Once all are chosen, "Apply and sync" merges the remaining non-conflicting changes along with them; "Later" drops this round of choices. Choices hold only for the remote version at that moment; if the remote gets a newer version, you are asked again.
    4. "Add a version note" can be expanded to give this push a commit message.
  • Checks after applying:
    • Applying first writes this machine's profile cordis.patch.yml and bundle list, then installs plugins newly added to the synced list and upgrades plugins whose versions changed (under HMR they go into the pending-restart list).
    • Then the running DSH takes the new configuration: under HMR the whole profile is reconciled again; in normal mode the configuration patch is applied through the Loader, and plugin changes take effect after a restart.
    • Finally the actual state of this machine is read back: items that did not take effect are listed separately, and when a restart is needed the top of the page says so.
    • The result line shows how many items were applied here and how many pushed; "Show changes" lists each item's old and new values, and "Undo" goes back to this machine's configuration from before this sync (creating a new version, pushed at the next sync).
  • Nothing is lost: a new version always has both sides as parents, so both sides' previous configurations stay in the history; pushes are fast-forward, with no force push needed; if the remote happens to get a new version while pushing, it merges again automatically, up to three retries. When reading this machine's state, upgrades pending a restart count as "already upgraded", so syncing again before the restart never pushes the remote's newer version back as a local downgrade.
  • Plugins: bundle enablement, disablement and order follow the merged result (bundles plugin management needs are never disabled). Plugins not installed on this machine are installed automatically through DSH's plugin manager, with install progress and pnpm output shown; plugins no longer in the synced list are only reported, never uninstalled automatically.
  • Versions: "Version history" lists the last 50 versions; "Restore this version" creates a new version and applies it here immediately, pushing it to the remote at the next sync.
  • At startup: with "Pull at startup" on, DSH merges the remote the same way after starting: with no conflicts it applies the remote's changes directly (without pushing this machine's), and a toast at the bottom right says how many items were applied and whether a restart is needed; with conflicts it only notifies you, and "Open" takes you to the Sync & updates page to resolve them. It pulls only once per DSH start: the plugin reloads when settings change or a sync applies this plugin's own configuration, and that does not pull again; a sync in progress, its result and its undo survive the reload.
  • ⋯
    Review #number · title
    .reviews/RULES.md
    pr-<number>.round1.md
  • Read-only: the review session's system prompt has a fixed section (read-only rules, reading code by ref, the sheet format, comment style) that survives context compaction. The guard applies to every model: git may not checkout, switch, reset, commit, stash or run other commands that change branches, the index or the working tree (fetch and all reads work as usual); gh may only read, so gh pr review/comment and non-GET gh api are refused; files may