dsh-github-router
English | 中文
Read-only GitHub access for a DeepSeek Harness project — load PRs, issues, files, and API data through tools that route internally (API, gh CLI, git protocol, page HTML, mirrors), so an agent never burns turns fighting shell-side TLS or proxy failures.
A DeepSeek Harness plugin bundle that gives agents GitHub reads without shell retries:
- Five read-only tools —
github_probe,github_pr,github_issue,github_file,github_api— plus thegithub-routerskill and a system-prompt guidance section. No write, push, comment, or mutation capability exists anywhere in the package. - In-tool routing — every request runs host-side (outside the sandbox's TLS/proxy restrictions) and falls through a route ladder:
api.github.com(direct → proxy) →ghCLI (read-only subcommands) → git protocol (plugin fetch cache, or a local clone read with log/diff/show only) → PR/issue page HTML (strict JSON island extraction) → user-configured raw mirrors. - One call, structured outcome — PRs come back with metadata, discussion, reviews, commits, changed files, and the diff, each part annotated with the route that served it; failures return a route matrix instead of a dozen retried shell commands.
- Connectivity probe —
github_probereports which routes are live from the host in one call, with timings and a recommended route chain. - Zero runtime dependencies — peers resolve from the DSH profile; proxied requests travel through the plugin's own CONNECT tunnel (no third-party HTTP stack), so the package installs fully offline.
- Settings page — an independent Settings entry ("GitHub Router", like the 通知 section): the
dsh-github-routersettings namespace (secret token, proxy, route switches, cache TTLs, byte caps) with staged edits, save/discard, and override badges. - Context efficiency — response caching with per-kind TTLs, byte caps everywhere, list caps with truncation notes, and rate-limit surfacing.
Requirements
- Node.js >= 20.18
- A DSH profile composed from
@deepseek-ai/dsh-base(it provides thetools,subprocess,skills,settings, andcredentialsservices the plugin uses) - Optional: the
ghCLI (authenticated) for the gh route;gitfor the git route
Install
From npm:
dsh plugin --profile web add dsh-github-router
From a local checkout (development):
dsh plugin --profile web add link:<absolute-path-to-this-repo>
From a git host:
dsh plugin --profile web add github:<owner>/dsh-github-router
Then restart the DSH backend — the host composition loads at process start. The tools appear in new sessions: github_probe, github_pr, github_issue, github_file, github_api, plus the github-router skill.
Compatibility
Choose the plugin version that matches your DeepSeek Harness release:
| DeepSeek Harness | Install |
|---|---|
| 0.1.1 or earlier | dsh-github-router@0.2.1 |
| 0.1.2-alpha or later | the latest dsh-github-router |
Usage
Agent side:
| Tool | What it does |
|---|---|
github_probe |
One-shot connectivity matrix (api direct/proxy, gh installed/authed, git ls-remote, page direct/proxy, mirrors, token presence) with timings and a recommended route chain. Call first when access fails or is slow. |
github_pr |
Full PR view: metadata, description, discussion (issue + inline review comments), reviews, commits, changed files, and the unified diff, each with route attribution. Parts toggle (includeDiscussion/includeReviews/includeCommits/includeFiles/includeDiff) and cap (maxDiffBytes/maxItems). localRepo (or session-cwd auto-detection) reads commits/diff from a local clone with zero network. |
github_issue |
Issue metadata, body, labels, and comments, with route attribution. |
github_file |
File content (or directory listing) at a branch/tag/sha via api contents → raw → mirrors → git; returns size, truncation state, and the serving route. |
github_api |
Validated GET-only escape hatch for any api.github.com endpoint; query values sanitized, responses cached, rate-limit headers surfaced, errors carry stable codes. |
github_probe # which routes are live right now
github_pr { owner: "o", repo: "r", number: 12 } # full PR view
github_pr { owner: "o", repo: "r", number: 12, localRepo: "C:/src/r" } # commits/diff from a local clone
github_issue { owner: "o", repo: "r", number: 34 }
github_file { owner: "o", repo: "r", path: "src/index.js", ref: "main" }
github_api { path: "/repos/o/r/commits", query: { per_page: 5 } }
Behavior notes:
- Route order is fixed per part type: API first (direct then proxy), then gh, then page HTML (proxy-first — machines with reset direct TLS usually reach pages through the proxy), then git, then mirrors. Each part records the route that served it.
- Anonymous API use is rate-limited (60 requests/hour per IP); configure a token (Settings or
GITHUB_TOKEN) for 5000/hour. Responses are cached to save quota;forceRefreshbypasses the cache. - Mirrors are off by default — they are third parties that see requested paths; enable them in settings only if you accept that.
- The git route never writes to user repositories: local clones are read with
git log/diff/showonly, and fetches happen exclusively in the plugin-owned cache under<DSH_HOME>/storages/dsh-github-router/.
Configuration
Settings → Plugins shows the GitHub Router card on the configurable
tab (the framework's settings.plugin.item card slot keyed by the settings
namespace; requires DSH ≥ 0.1.0-rc.7): edits are staged locally and written
only on save, fields overridden by the user are badged, and blank fields
fall back to the defaults below. The token is a write-only field — a blank
save clears a configured token. The same values can be set in the
composition (profile cordis.patch.yml) as the plugin's base config; the
Settings UI overrides per user.
| Field | Default | Meaning |
|---|---|---|
token |
— | Literal GitHub token (secret; redacted on the wire, write-only input). Prefer tokenEnv. |
tokenEnv |
GITHUB_TOKEN |
Environment variable / credential ref naming the token. |
proxy |
'' |
Proxy URL for proxy attempts. '' inherits ambient HTTP(S)_PROXY; direct never proxies. |
directTimeoutMs / proxyTimeoutMs |
8000 / 15000 | Per-attempt timeouts. |
retries |
1 | Retries for idempotent GETs on 429/5xx (honors Retry-After). |
routesApi / routesGh / routesGit / routesHtml / routesMirror |
on / on / on / on / off | Route switches (the card shows them as checkboxes). |
mirrors |
[] |
Raw-content mirror bases, e.g. ["https://ghproxy.net"]. |
cacheTtlMeta / cacheTtlContent |
300 / 86400 | Response cache TTLs (PR/issue metadata vs immutable-ish content), in seconds. |
maxBytes |
1048576 | Byte cap for every response body read by the plugin. |
repos |
[] |
Local repositories granted for read-only git-route reads. |
gitCacheDir |
'' |
Plugin fetch-cache dir; '' = <DSH_HOME>/storages/dsh-github-router/git. |
How it works
- Host-side execution — plugin code runs in the host process, so the sandbox's TLS credential resets and proxy misrouting never apply. The unrestricted token is compensated by the confinement model in SECURITY.md — not by weakening the sandbox.
- Explicit proxy decisions — global
fetchdoes not inherit ambient proxy env; each attempt gets an explicit direct/proxy choice, and proxied requests use a zero-dependency CONNECT tunnel (node:http/node:tls) with target-hostname TLS validation andaccept-encoding: identity. - Strict parsing — the page-HTML route extracts only
react-app.embeddedDataJSON islands and runsJSON.parse(never evaluated); a bounded BFS copies a whitelist of fields, so CSRF tokens and the raw payload never reach the model. - Argv-only subprocesses — every
gh/gitinvocation is an argv array with fixed flag lists; user input reaches argv only after regex validation, and nothing is shell-interpolated. - Tool contract — canonical values are lossless JSON (arrays carry no side properties), byte-capped, and rendered as compact text with route attribution.
- Skill & guidance — the
github-routerskill teaches tool-first usage and the "never escalate for GitHub reads" rule; one system-prompt section (dsh-github-router:guidance, order 118) reminds every session that the github_* tools are the sanctioned path.
Project layout
| Path | Purpose |
|---|---|
cordis.patch.yml |
Profile patch layer inserting the dsh-github-router row |
lib/index.js |
Host plugin: settings namespace, five tools, skill, guidance |
lib/client.js |
Browser half: the settings card (hand-written factory bundle, no build step) |
lib/config.js |
Settings schema, defaults, runtime option resolution |
lib/net.js, lib/tunnel.js |
Route-aware HTTP layer; zero-dependency CONNECT proxy tunnel |
lib/routes/ |
One module per route: api (GET-only REST), gh (CLI), git (protocol), html (page parse), mirror (raw mirrors) |
lib/core/ |
Per-call runtime assembly and the pr/issue/file/probe aggregators |
lib/tools/ |
The five model tools |
lib/cache.js, lib/render.js, lib/util.js |
TTL cache, text renderers, guards and shaping |
lib/skill.js, lib/guidance.js |
Skill content and prompt-injection section |
test/ |
Runtime-free behavior tests (see Development) |
docs/ |
Design and analysis documents |
Development
No build step: the plugin is plain ESM and the tests run with Node directly
(a mock ctx stands in for the DSH services; the real defineTool validates
every schema):
npm test
# or: node --test --test-isolation=none "test/*.test.js"
The tests are fully offline — they cover JSON-island extraction, input guards, URL builders, the TTL cache, commit-log parsing, the tunnel request head, and the apply() wiring. See CONTRIBUTING.md for the development loop, including offline peer resolution.
Security
Read-only by construction: no write verb exists, tokens attach only to
api.github.com and are redacted on every boundary, page payloads are
whitelist-extracted, and the only disk writes are the two plugin-owned
caches under <DSH_HOME>/storages/dsh-github-router/. See
SECURITY.md for the complete threat model and mitigation
list.
Documentation
- docs/design.md — architecture, route ladder, cache and confinement model, known limitations
- SECURITY.md — threat model and compensating controls
- CHANGELOG.md — release history
License
MIT
No comments yet. Be the first to write one.