证据快照复核于 2026-09-16GitHub 数据核对日期: 2026-08-21
来源已审查应用自动化与智能体桌面端 Profileios Profileandroid Profile

DeepTail

用于跨 DeepSeek Harness 主机管理代理会话的桌面、iOS 和 Android 控制台。

快速了解

它能做什么

用于跨 DeepSeek Harness 主机管理代理会话的桌面、iOS 和 Android 控制台。

本站提供的是中文说明,不代表该项目或 Plugin 自身提供中文界面;语言支持请以上游文档为准。

能力
自动化与智能体智能体会话管理工作流自动化

选择前先看

DeepTail 将已配对的 DeepSeek Harness 主机中的会话汇总为一个实时列表。它可配对或切换主机、通过 Tailscale 发现设备、从代理预设创建会话、发送或引导消息,以及停止正在运行的轮次。打开某个会话时,DeepTail 会交给该主机的 Harness 客户端查看,而不会在自身中展示完整逐字稿。

适合谁

需要在多台 DeepSeek Harness 主机上统一查看和操作代理会话的用户,尤其是使用 Tailscale 网络的用户。

常见任务

  • 在一个列表中监控多台已配对主机上的活跃会话。
  • 发现 Tailscale 设备,并使用启动令牌配对选定主机。
  • 创建会话、发送或引导消息,或停止活跃轮次。

权限与数据

该应用会连接 Harness 主机,并可选连接 Tailscale;README 描述凭据由 Rust 核心处理,而非 Webview。

权限
  • 访问已配对 DeepSeek Harness 主机的网络权限。
  • 可选访问 Tailscale API 以列出 tailnet 设备。
  • 访问平台凭据存储,以保存主机令牌和 Tailscale 凭据。
数据处理
  • README 表示,每台主机的设备令牌和 Tailscale 凭据存放在平台原生密钥存储中。
  • README 表示,令牌保留在 Rust 侧,不会通过 Tauri IPC 传递给页面。
  • 主机和会话列表数据会返回给界面显示。
外部服务
  • DeepSeek Harness 主机。
  • 可选的 Tailscale API 与 tailnet 网络。
凭据
  • 配对 Harness 主机需要 dsh web 输出的启动令牌。
  • 使用 Tailscale 发现功能需要 Tailscale API 密钥或 OAuth 客户端凭据。

局限

  • 未提供可下载的已打包产物;仓库仅记录了基于源码的安装和开发命令。
  • 会话逐字稿、审批、项目和计划仍由各主机的 Harness 客户端处理。
  • README 表示读取会话仍依赖三个尚未进入上游的 Harness 改动。
  • 提供的证据未声明支持的 Harness 版本范围。

DSHub 已核对

  • 固定版本 README 记录了桌面、iOS、Android 目标平台、Harness 主机控制操作、Tailscale 发现和原生凭据存储的设计。
  • 仓库包含 MIT 许可证。

DSHub 未核对

  • 本次整理未执行安装、构建、测试、网络连接或移动端部署。
  • 未提供打包发布产物,也未提供与特定 Harness 版本的运行兼容性证据。

固定版本安装

主要操作

这个应用没有 DSH Plugin 安装操作,请根据源码文档使用真实交付方式。

访问源码项目

维护者原文

项目 README

查看 commit 5c1d143 对应的 README
维护者编写的上游内容原文于 2026/9/16README.md 获取,正文和仓库相对媒体固定到 commit 5c1d14358a56,内容哈希为 aef717586514。以下是未经 DSHub 翻译的上游原文,语言可能与当前页面不同;第三方托管的 badge 可能独立更新。

DeepTail

TypeScript 7.0.2 Bun 1.4 Tauri 2.11 Rust edition 2024 Vite 8 Playwright 1.63 Platforms: desktop, iOS, Android License: MIT

A Tauri 2 client — desktop, iOS, and Android — that connects to DeepSeek Harness hosts and gives you one control plane over the agent sessions running on all of them.

The fleet roster across two hosts

Explain it like I'm five

You have robot helpers working on several computers at once. Each computer keeps its own list of what its helpers are doing, and to check on them you would have to walk over to every computer in turn.

DeepTail is one window that shows every list at the same time. You open it and see every helper on every computer, which ones are busy right now, and when each one last did something. You can start a new helper, tell one what to do next, or tell one to stop — without going to that computer.

When you want to read everything a helper wrote, DeepTail opens that computer's own reader, because that computer is the one holding the whole story.

Three rules it keeps:

  • One computer answers for itself. If one is switched off, its row says so and offers a Retry. The others keep working — one broken computer never blanks the window.
  • The password never touches the page. The part you see is just drawing. A separate Rust part holds the key for each computer and does the talking.
  • It can find the computers for you. If they are on your Tailscale network, DeepTail lists them by name instead of asking you to type an address.
  • One scrollbar per direction. Panes do not nest inside other panes, so the wheel always moves the thing you are pointing at.

How it works

flowchart TB
  subgraph device["Your device — one DeepTail app"]
    direction LR
    ui["<b>Webview</b><br/>shell · roster · dialogs<br/>draws only, opens no socket"]
    rust["<b>Rust core</b><br/>host registry · reqwest · mux client"]
    store[("Keystore<br/>device tokens<br/>tailnet credential")]
    ui -->|"Tauri IPC"| rust
    rust -->|"reads a secret"| store
  end

  subgraph network["Reached over the network, only ever from Rust"]
    direction LR
    tailscale["<b>Tailscale API</b><br/>which machines exist"]
    hosts["<b>Every paired dsh host</b><br/>list · spawn · message · stop"]
  end

  rust -->|"lists the tailnet"| tailscale
  rust -->|"unary calls"| hosts
  hosts -.->|"roster events"| rust
  ui ==>|"open a session"| client["<b>That host's harness client</b><br/>transcripts · approvals · plans"]

  classDef inside fill:aliceblue,stroke:indigo,color:midnightblue
  classDef host fill:honeydew,stroke:seagreen,color:darkgreen
  classDef hand fill:lemonchiffon,stroke:chocolate,color:maroon
  class ui,rust,store inside
  class hosts,tailscale host
  class client hand

Two things the chart is drawn to show. Every arrow that crosses the device boundary starts at Rust, never at the webview — the solid one is the unary call DeepTail makes, the dashed one is the stream that keeps the roster current without polling, and the device token stays on the Rust side of the IPC in both. And the thick arrow is the product boundary: DeepTail owns the fleet, the harness client owns one conversation, and choosing a session is where one hands over to the other.

What it does

DeepTail is the control plane; the harness client is the reader. Everything DeepTail does is a unary call that works over its own carrier today. Reading a conversation is a stream, and that belongs to the client already built for it.

DeepTail The harness client
Pair, switch and unpair hosts Transcripts and tool cards
Find hosts on your tailnet
One roster across every paired host, live Approvals, projects, plans
Spawn a session from an agent preset Everything session-local
Message or steer a session
Stop a running turn

Choosing a session hands off: DeepTail boots that host's own client. It opens at the client's default view — nothing in its boot surface takes a session id — and the copy says so rather than implying a deep link it cannot deliver.

Tailscale

Typing a URL is not how anyone finds their own machines. DeepTail lists your tailnet through Tailscale's own API and shows which machines are already paired, so a host is chosen from a list rather than transcribed.

Both credentials Tailscale issues work, because they are not interchangeable: an API key is one secret that expires on a fixed date, authenticated as the username of an HTTP Basic pair; an OAuth client is the id and secret pair Tailscale documents for long-running integrations, exchanged at /api/v2/oauth/token for short-lived bearers and needing the devices:core scope. Either lists /api/v2/tailnet/{tailnet}/devices?fields=all, where the tailnet defaults to - — the one that credential belongs to. The credential is filed in the same platform keystore as every device token and never crosses the IPC boundary; the page receives machines, never the key that listed them.

Listing a tailnet is not pairing. Tailscale can say which machines exist; only dsh web can mint the launch token a harness accepts. Choosing a machine therefore opens the pairing form for it — with the origin already fixed, so the one thing left to supply is the token that machine printed.

Discovery is also what makes http:// admissible. A tailnet peer is reached over WireGuard, so the packets are encrypted and peer-authenticated before the URL scheme has any say; dsh web on a tailnet terminates no TLS and does not need to. canonical_origin admits plaintext to a tailnet peer and to loopback and refuses it everywhere else, reading Tailscale's own ranges — 100.64.0.0/10 minus the 100.115.92.0/23 slice ChromeOS uses for its containers, fd7a:115c:a1e0::/48, and MagicDNS names under .ts.net.

Why the wire lives in Rust

The webview never opens a network connection. Tauri serves the page from a secure-context origin, so it cannot open a ws:// socket at all and cannot attach an Authorization header to a WebSocket handshake. Holding the wire in Rust removes both limits, keeps the device token out of JavaScript, and lets the app ship a CSP that names no host:

default-src 'self'; script-src 'self' blob:; connect-src ipc: http://ipc.localhost

Design

The UI is plain DOM — it paints before any harness bundle loads — but not a separate design language. src/styles/tokens.css holds the harness's own resolved --dsw-alias-* and --dsw-specific-* values and its body[data-ds-dark-theme] mechanism; geometry follows ui-sidebar, ui-primitives/Menu, ui-primitives/Modal and ui-workspace/rows.

No inline styles. Every visual is a class, enforced by scripts/check-no-inline-styles.ts. It reads a parse rather than lines — oxc for scripts, the parser oxlint uses, and parse5 for markup — so the rules are stated about nodes and there is no spelling to get around them: the dotted property, an indexed write, a bulk assign, destructuring, the CSS Typed OM, a style attribute in markup written anywhere in the source, and any call that sets an attribute or a property under that name. Names are constant-folded, so one held in a constant, split with +, built in a template, case-shifted, joined or spelt from character codes is read as the name it produces; a name the gate cannot read at all is refused rather than allowed. The two calls that set an attribute without naming it — setAttributeNode and setNamedItem — are refused outright.

The files it reads are the files git says the repository ships, so its reach cannot drift from a hand-written list and its count does not move with whether a build has run. scripts/ban-gate.ts reads the same parse for idioms the project has moved past and for directives that switch a checker off; a directive is only ever a comment, so the comments are what it searches, and Rust is searched too. Both gates are driven by tests/gates.spec.ts, which runs every rule against a source that breaks it and a source that merely resembles it.

A stylesheet is not an inline style and is not banned: injections.ts builds one because the host's boot table says to. Neither is color-scheme, a CSS property keyed off the same body[data-ds-dark-theme] attribute as the palette, so native UA chrome cannot drift from the theme. Beyond those, the gate has no allowances.

A sheet's values are read against the token sheet too. scripts/sheet-gate.ts refuses a bare length outside tokens.css — spacing, radius, type and grid rungs — a raw colour in any spelling (a function-written or named colour, or a hex literal), a cascade override flag, a legacy float, a bare stacking number, a second breakpoint in a second syntax, a rule that hides the focus ring without painting one back, and a rule set declared twice. Every value the shipped sheets carry goes through the tokens, and tests/sheet-gate.spec.ts drives each rule both ways.

The markup gate reads every element, whatever writes it: besides the style attribute, an inline event handler, an inline script body and an inline style block are each a per-page one-off that no module ships and no gate reads once it sits inside a tag, so none may ship. The ban gate carries the same shape of rules for idioms the project has moved past: the React 19 removals (ReactDOM.render and its siblings, findDOMNode, string refs, defaultProps, legacy context), the Tauri v1 API paths and the __TAURI__ global, alongside the older bans on var, require, markup-rewriting properties, eval and the rest. tests/gates.spec.ts and tests/style-gate.spec.ts drive both gates against a source that breaks each rule and a source that merely resembles it.

Connection menu Dark
Host switcher — selection is a trailing check, never a fill The harness dark palette
Compose Mobile
Message or steer a session 390×844, sidebar as a drawer
Partial failure Pairing validation
One host down, the rest still listed, Retry on the row that failed The refusal names what to fix, below the field it is about
Pairing Chinese
Paired hosts, before a shell exists The empty picker in Chinese — every string comes from the dictionary
Connect a tailnet Tailnet machines
Either credential Tailscale issues, and an optional tailnet Your machines, with the one awaiting approval dimmed rather than hidden
Loading Nothing paired
Before the first roster read settles Nothing paired: pair a host, or choose one from Tailscale
Dark picker Unauthorized
Nothing paired, in the dark palette Paired hosts whose device tokens the harness no longer accepts

Every state is built and screenshotted: loading, empty-after-settled, error, partial failure (one host down, the rest still listed), unauthorized, and offline, and both tailnet screens. apps/deeptail/tests/screenshots/ holds all 22, and each is written by the case that asserts the state, so none can drift from the code.

Layout

apps/deeptail/
  src/            shell, connection menu, roster, dialogs, carrier, stream client
  src-tauri/      the entire network and credential surface, Tailscale included
packages/host-fleet/   Cordis plugin: sessions_* orchestration tools
profile/               the dsh profile DeepTail installs on a host

Credentials

One device token per host in the platform's own store. The all-in-one keyring crate is desktop-only in its default feature, so this uses keyring-core with one native store per target — which is what makes iOS and Android work: apple-native-keyring-store, android-native-keyring-store (Keystore + SharedPreferences), windows-native-keyring-store, dbus-secret-service-keyring-store.

Pairing spends the launch token dsh web already prints. Plaintext pairing is refused off loopback.

Harness seams

Reading a conversation needs the harness client, and that client cannot boot until three changes land upstream. The control plane does not depend on them.

Seam Why
A socket factory on ClientTransportHooks remoteStreamUrl() derives the mux authority from location.origin, and a secure-context page cannot open ws://
collectIndexInjections() over an authenticated /api route the boot table is reachable only as injected HTML
Promote applyIndexInjections out of packages/experimental/ a non-served shell needs it, and that package is not published

Development

bun install
bun run validate          # biome, oxlint, styles gate, typecheck, tests, knip, clippy, cargo test, browser, axe
bun run tauri dev         # desktop
bun run tauri android dev # Android Studio, JAVA_HOME, ANDROID_HOME, NDK_HOME
bun run tauri ios dev     # macOS + Xcode + CocoaPods only

Browser tests drive the real built bundle in Chromium and substitute only the Tauri IPC boundary, which no browser provides. They assert on rendered text and roles and write every screenshot above. The scripted IPC covers the mux socket as well as unary calls, so the live roster — a row arriving over $events, a removal, a status flip, a dropped stream — is exercised rather than assumed.

Two suites read the rendered page rather than the source. a11y.browser.spec.ts runs axe over wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa and best-practice, and treats incomplete as a violation, so a rule axe could not settle is a failure rather than a pass. structure.ts covers what no rule engine reports: a duplicate id, an interactive element inside another, a skipped heading level, an ARIA reference pointing at nothing, an item outside its list, a document that scrolls sideways, text clipped by its own box, a pane that scrolls inside a pane that also scrolls, and a target under the floor for its pointer — 24 CSS px on any pointer per WCAG 2.2 SC 2.5.8, and 44 on a finger, which is what Apple's Human Interface Guidelines and SC 2.5.5 ask. The geometry half lives in structure-layout.ts; both are shipped into the page as their own source text, so nothing they measure against can be left behind.

The suites split the same way. structure.browser.spec.ts walks the surfaces and asserts each is clean; structure-geometry.browser.spec.ts measures what a finger reaches and, because a check that never reports is indistinguishable from one that cannot, makes the shell scroll inside itself and asserts the pane rule names it. The 44px floor is only measured on a page opened with hasTouch: pointer: coarse does not hold on a narrow desktop window, so measuring one against it would test a pairing that does not exist.

Every dependency is held at the newest version this workspace can install. check:outdated reads bun outdated --filter "*" — the package manager's own report, which already knows about ranges and workspaces — rather than asking the registry itself, and refuses any pin behind a published version. Nothing withholds a release from resolution: bunfig.toml sets no release hold, and tests/stack.spec.ts refuses one, so a pin behind is a pin to move rather than a policy to explain. The floors in tests/stack.spec.ts are held equal to the pins, so a downgrade fails there and an upgrade has to be written down.

Playwright resolves the Chromium it installed.

Pipeline

Two checks decide a merge, and branch protection requires both.

gate (.github/workflows/ci.yml) runs the repository's own gate scripts unmodified across four jobs — static, types-and-tests, browser and rust — and a fifth, gate, that waits on all four and exits non-zero unless every one reported green. A job outside that aggregate would run, report, and block nothing, so aggregationViolations in scripts/pipeline-guard-rules.ts refuses one. Every action is pinned to a full commit sha, every job is bounded by a timeout, every checkout drops its token, and every install is frozen to the lockfile.

pipeline-guard (scripts/pipeline-guard.ts) reads the workflow definitions, the manifest and the code-owner list the way a reviewer would, and fails closed: a definition it cannot read is a violation, never an absence of one. The gates that decide ship-worthiness are pinned by name in MERGE_GATES, held to the same list by the validate chain and by ci.yml, so dropping one turns this check red on the same commit that drops it. tests/pipeline-guard.spec.ts drives every rule against a definition carrying the cheat, then drives all of them at once against the pipeline this repository actually ships.

mutation (.github/workflows/mutation.yml) is an audit on a clock rather than a merge decision. Five Stryker scopes cover every source file the repository ships — scripts/, apps/deeptail/src/ and packages/host-fleet/src/ — each breaking below a score of 99. tests/mutation-config.spec.ts reads the scopes against the tree file by file, so a file inside no scope is named rather than answered for by a sibling, and refuses the four edits that move a score without touching product code: a lowered threshold, a narrowed mutate, a file left outside every scope, and the in-place disable comment.

Toolchain

TypeScript 7.0.2 · Bun 1.4.2 · Node 24 LTS · Tauri 2.11 · Rust edition 2024 · Vite 8 · Playwright 1.63.0.

tests/stack.spec.ts reads the versions on that line back against the manifests, so a toolchain line that says something the repository does not install is a failure rather than a paragraph nobody re-read.

License

MIT

有意识地管理

安装与管理

前置条件与目标 Profile

目标 桌面端 Profile, ios Profile, android Profile

交付方式 外部应用 — https://github.com/d4551/DeepTail

兼容性与访问范围

Requires DeepSeek Harness hosts; session reading has upstream seams Not declared in supplied evidence

检查兼容性证据

风险事实

凭据

Uses per-host device tokens and optional Tailscale credentials

证据
network-access

Connects to paired Harness hosts and can query the Tailscale API

证据
证据与编辑审查Manifest、Bundle patch、分发与新鲜度

不可变证据

审查状态与源码活动

AI 已审查

这是应用源码仓库,不是 Harness 插件包或可复用 Skill。在使用凭据或生产主机前,请先审查源码和构建说明。

AI 审查于 2026/9/16 UTC 14:15GitHub 事实核对日期: 2026/9/16 UTC 14:15

自当前证据基线以来,没有记录到重要源码变化。

下一步

比较生态 Artifact 类型

订阅重要变化: DeepTail