dsh-github-router 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +80 -0
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/README.zh.md +156 -0
- package/SECURITY.md +112 -0
- package/cordis.patch.yml +8 -0
- package/docs/design.md +198 -0
- package/lib/cache.js +91 -0
- package/lib/client.js +553 -0
- package/lib/config.js +109 -0
- package/lib/core/file.js +166 -0
- package/lib/core/issue.js +202 -0
- package/lib/core/pr.js +429 -0
- package/lib/core/probe.js +121 -0
- package/lib/core/runtime.js +89 -0
- package/lib/guidance.js +24 -0
- package/lib/index.js +52 -0
- package/lib/net.js +213 -0
- package/lib/remote.js +169 -0
- package/lib/render.js +154 -0
- package/lib/routes/api.js +136 -0
- package/lib/routes/gh.js +109 -0
- package/lib/routes/git.js +429 -0
- package/lib/routes/html.js +250 -0
- package/lib/routes/mirror.js +55 -0
- package/lib/skill.js +44 -0
- package/lib/tools/api.js +101 -0
- package/lib/tools/file.js +48 -0
- package/lib/tools/issue.js +46 -0
- package/lib/tools/pr.js +56 -0
- package/lib/tools/probe.js +42 -0
- package/lib/tunnel.js +247 -0
- package/lib/util.js +235 -0
- package/package.json +79 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-08-19
|
|
11
|
+
|
|
12
|
+
First release.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Five read-only model tools: `github_probe`, `github_pr`, `github_issue`, `github_file`, `github_api`
|
|
17
|
+
- In-tool route ladder: api.github.com (direct → CONNECT-tunnel proxy) → gh CLI (read-only subcommands) → git protocol (plugin fetch cache + read-only local repo reads) → PR/issue page HTML (strict `react-app.embeddedData` JSON extraction) → user-configured raw mirrors (off by default), with per-part route attribution on every result
|
|
18
|
+
- `github_probe` connectivity matrix with per-route timings and a recommended route chain
|
|
19
|
+
- TTL response cache with atomic writes and corruption-tolerant reads
|
|
20
|
+
- Settings: the `dsh-github-router` namespace on the official settings seam (secret token / `tokenEnv` credential ref, proxy, route switches, cache TTLs, byte caps, granted local repos), an independent Settings page (the dsh-notification `settings.section` mechanism; common fields up top, the long tail in a collapsed "Advanced settings" disclosure), and plugin-owned `/dsh-github-router/config` web routes (the dsh-market pattern) with same-origin enforcement and revision fencing
|
|
21
|
+
- `github-router` skill and a system-prompt guidance section (order 118)
|
|
22
|
+
- Zero runtime dependencies: zero-dependency CONNECT tunnel for proxied requests; peers resolve from the DSH profile
|
|
23
|
+
- 72 offline tests (`node --test`) covering extraction, guards, cache, tunnel, tool wiring, client bundle, config routes, and localization compliance
|
|
24
|
+
- Documentation: bilingual README, SECURITY, design notes, and a development guide
|
|
25
|
+
|
|
26
|
+
### Security
|
|
27
|
+
|
|
28
|
+
- Read-only by construction: no write/push/comment/mutation verb exists anywhere
|
|
29
|
+
- Tokens attach only to api.github.com and are redacted on every wire boundary
|
|
30
|
+
- Page HTML is parsed with `JSON.parse` only (never evaluated); a bounded BFS copies a whitelist of fields
|
|
31
|
+
- Subprocess calls are argv arrays with fixed flag lists — no shell interpolation
|
|
32
|
+
- Configuration writes require same-origin POSTs and are body-capped
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in dsh-github-router. This plugin is small,
|
|
4
|
+
plain-ESM, and dependency-free at runtime — the development loop is
|
|
5
|
+
correspondingly simple.
|
|
6
|
+
|
|
7
|
+
## Development loop
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm test
|
|
11
|
+
# or: node --test --test-isolation=none "test/*.test.js"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The tests run with Node directly, with zero network access. A mock cordis
|
|
15
|
+
context stands in for the DSH services, while the real
|
|
16
|
+
`@deepseek-ai/dsh-tools` `defineTool` validates every tool schema — so
|
|
17
|
+
schema mistakes fail in `test/apply.test.js` rather than at host boot.
|
|
18
|
+
|
|
19
|
+
After changing anything in `lib/`, re-run the tests and then reinstall the
|
|
20
|
+
plugin into a local profile:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
dsh plugin --profile <profile> add link:<absolute-path-to-this-repo>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Restart the DSH backend to pick up host-side changes (the host composition
|
|
27
|
+
loads at process start).
|
|
28
|
+
|
|
29
|
+
## Offline peer resolution
|
|
30
|
+
|
|
31
|
+
The plugin's imports (`@deepseek-ai/dsh-tools`, `dsh-settings`,
|
|
32
|
+
`dsh-credentials`, `schemastery`, `cordis`) are peer dependencies and
|
|
33
|
+
resolve from the DSH profile in production. To run the tests without a
|
|
34
|
+
registry round-trip, create a local junction inside the checkout:
|
|
35
|
+
|
|
36
|
+
```powershell
|
|
37
|
+
New-Item -ItemType Junction -Path node_modules\@deepseek-ai -Target <dschome>\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
(`node_modules/` is git-ignored; the junction is a local convenience only.)
|
|
41
|
+
|
|
42
|
+
## Conventions
|
|
43
|
+
|
|
44
|
+
- **Plain ESM, no build step.** No TypeScript, no bundler.
|
|
45
|
+
- **One module per concern** — `lib/routes/*` are transport adapters with no
|
|
46
|
+
knowledge of the tools; `lib/core/*` aggregate routes; `lib/tools/*` are
|
|
47
|
+
the only modules that touch `defineTool`.
|
|
48
|
+
- **No shell interpolation.** Every subprocess is an argv array; anything
|
|
49
|
+
user-derived must pass a guard in `lib/util.js` first.
|
|
50
|
+
- **Lossless JSON.** Tool execute results must round-trip
|
|
51
|
+
`JSON.parse(JSON.stringify(value))` unchanged — never attach enumerable
|
|
52
|
+
side properties to arrays, never return `undefined`-valued fields.
|
|
53
|
+
- **Tests for every guard and parser.** Input guards, the JSON-island
|
|
54
|
+
walker, the tunnel request head, and the TTL cache all have offline
|
|
55
|
+
tests; add one whenever behavior changes.
|
|
56
|
+
|
|
57
|
+
## Security rules for contributions
|
|
58
|
+
|
|
59
|
+
This plugin runs host-side with the host token. Before adding anything,
|
|
60
|
+
re-read [SECURITY.md](SECURITY.md). Hard rules:
|
|
61
|
+
|
|
62
|
+
1. **No write capability, ever.** No POST/PATCH/PUT/DELETE, no push, no
|
|
63
|
+
comments. If a mutating capability is ever proposed, it must be a
|
|
64
|
+
separate tool behind an explicit user-facing consent surface — and it
|
|
65
|
+
still needs a strong justification to exist here.
|
|
66
|
+
2. **No execution of remote content.** Page payloads are `JSON.parse`-only;
|
|
67
|
+
never introduce `eval`/`Function` or any HTML/script interpretation.
|
|
68
|
+
3. **Secrets stay out of outputs.** Tokens attach only to api.github.com;
|
|
69
|
+
proxy URLs in error text must be credential-stripped.
|
|
70
|
+
4. **Bounded everything.** Every response body, walk, list, and retry needs
|
|
71
|
+
an explicit cap with a visible truncation note.
|
|
72
|
+
|
|
73
|
+
## Releasing
|
|
74
|
+
|
|
75
|
+
1. Bump `version` in `package.json`, update `CHANGELOG.md` (Keep a
|
|
76
|
+
Changelog), commit.
|
|
77
|
+
2. `npm test` (runs via `prepublishOnly` as well).
|
|
78
|
+
3. `npm publish` and verify the package through
|
|
79
|
+
`dsh plugin --profile <profile> add dsh-github-router` in a fresh
|
|
80
|
+
profile.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-github-router contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# dsh-github-router
|
|
2
|
+
|
|
3
|
+
**English** | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
> 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.
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-github-router)
|
|
10
|
+
|
|
11
|
+
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin bundle that gives agents GitHub reads without shell retries:
|
|
12
|
+
|
|
13
|
+
- **Five read-only tools** — `github_probe`, `github_pr`, `github_issue`, `github_file`, `github_api` — plus the `github-router` skill and a system-prompt guidance section. No write, push, comment, or mutation capability exists anywhere in the package.
|
|
14
|
+
- **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) → `gh` CLI (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.
|
|
15
|
+
- **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.
|
|
16
|
+
- **Connectivity probe** — `github_probe` reports which routes are live from the host in one call, with timings and a recommended route chain.
|
|
17
|
+
- **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.
|
|
18
|
+
- **Settings page** — an independent Settings entry ("GitHub Router", like the 通知 section): the `dsh-github-router` settings namespace (secret token, proxy, route switches, cache TTLs, byte caps) with staged edits, save/discard, and override badges.
|
|
19
|
+
- **Context efficiency** — response caching with per-kind TTLs, byte caps everywhere, list caps with truncation notes, and rate-limit surfacing.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Node.js >= 20.18
|
|
24
|
+
- A DSH profile composed from `@deepseek-ai/dsh-base` (it provides the `tools`, `subprocess`, `skills`, `settings`, and `credentials` services the plugin uses)
|
|
25
|
+
- Optional: the `gh` CLI (authenticated) for the gh route; `git` for the git route
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
From npm:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dsh plugin --profile web add dsh-github-router
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
From a local checkout (development):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dsh plugin --profile web add link:<absolute-path-to-this-repo>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
From a git host:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
dsh plugin --profile web add github:<owner>/dsh-github-router
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
Agent side:
|
|
52
|
+
|
|
53
|
+
| Tool | What it does |
|
|
54
|
+
| ---- | ------------ |
|
|
55
|
+
| `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. |
|
|
56
|
+
| `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. |
|
|
57
|
+
| `github_issue` | Issue metadata, body, labels, and comments, with route attribution. |
|
|
58
|
+
| `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. |
|
|
59
|
+
| `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. |
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
github_probe # which routes are live right now
|
|
63
|
+
github_pr { owner: "o", repo: "r", number: 12 } # full PR view
|
|
64
|
+
github_pr { owner: "o", repo: "r", number: 12, localRepo: "C:/src/r" } # commits/diff from a local clone
|
|
65
|
+
github_issue { owner: "o", repo: "r", number: 34 }
|
|
66
|
+
github_file { owner: "o", repo: "r", path: "src/index.js", ref: "main" }
|
|
67
|
+
github_api { path: "/repos/o/r/commits", query: { per_page: 5 } }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Behavior notes:
|
|
71
|
+
|
|
72
|
+
- 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.
|
|
73
|
+
- 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; `forceRefresh` bypasses the cache.
|
|
74
|
+
- Mirrors are **off by default** — they are third parties that see requested paths; enable them in settings only if you accept that.
|
|
75
|
+
- The git route never writes to user repositories: local clones are read with `git log`/`diff`/`show` only, and fetches happen exclusively in the plugin-owned cache under `<DSH_HOME>/storages/dsh-github-router/`.
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Settings → **GitHub Router** opens an independent settings page (a nav
|
|
80
|
+
entry registered into `settings.section`, the same mechanism as the 通知
|
|
81
|
+
section): edits are staged locally and written only on save, fields
|
|
82
|
+
overridden by the user are badged, and blank fields fall back to the
|
|
83
|
+
defaults below. The same values can be set in the composition (profile
|
|
84
|
+
`cordis.patch.yml`) as the plugin's base config; the Settings UI overrides
|
|
85
|
+
per user.
|
|
86
|
+
|
|
87
|
+
| Field | Default | Meaning |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `token` | — | Literal GitHub token (secret; redacted on the wire, write-only input). Prefer `tokenEnv`. |
|
|
90
|
+
| `tokenEnv` | `GITHUB_TOKEN` | Environment variable / credential ref naming the token. |
|
|
91
|
+
| `proxy` | `''` | Proxy URL for proxy attempts. `''` inherits ambient `HTTP(S)_PROXY`; `direct` never proxies. |
|
|
92
|
+
| `directTimeoutMs` / `proxyTimeoutMs` | 8000 / 15000 | Per-attempt timeouts. |
|
|
93
|
+
| `retries` | 1 | Retries for idempotent GETs on 429/5xx (honors `Retry-After`). |
|
|
94
|
+
| `routesApi` / `routesGh` / `routesGit` / `routesHtml` / `routesMirror` | on / on / on / on / **off** | Route switches (the card shows them as checkboxes). |
|
|
95
|
+
| `mirrors` | `[]` | Raw-content mirror bases, e.g. `["https://ghproxy.net"]`. |
|
|
96
|
+
| `cacheTtlMeta` / `cacheTtlContent` | 300 / 86400 | Response cache TTLs (PR/issue metadata vs immutable-ish content), in seconds. |
|
|
97
|
+
| `maxBytes` | 1048576 | Byte cap for every response body read by the plugin. |
|
|
98
|
+
| `repos` | `[]` | Local repositories granted for read-only git-route reads. |
|
|
99
|
+
| `gitCacheDir` | `''` | Plugin fetch-cache dir; `''` = `<DSH_HOME>/storages/dsh-github-router/git`. |
|
|
100
|
+
|
|
101
|
+
## How it works
|
|
102
|
+
|
|
103
|
+
- **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](SECURITY.md) — not by weakening the sandbox.
|
|
104
|
+
- **Explicit proxy decisions** — global `fetch` does 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 and `accept-encoding: identity`.
|
|
105
|
+
- **Strict parsing** — the page-HTML route extracts only `react-app.embeddedData` JSON islands and runs `JSON.parse` (never evaluated); a bounded BFS copies a whitelist of fields, so CSRF tokens and the raw payload never reach the model.
|
|
106
|
+
- **Argv-only subprocesses** — every `gh`/`git` invocation is an argv array with fixed flag lists; user input reaches argv only after regex validation, and nothing is shell-interpolated.
|
|
107
|
+
- **Tool contract** — canonical values are lossless JSON (arrays carry no side properties), byte-capped, and rendered as compact text with route attribution.
|
|
108
|
+
- **Skill & guidance** — the `github-router` skill 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.
|
|
109
|
+
|
|
110
|
+
## Project layout
|
|
111
|
+
|
|
112
|
+
| Path | Purpose |
|
|
113
|
+
| ---- | ------- |
|
|
114
|
+
| `cordis.patch.yml` | Profile patch layer inserting the `dsh-github-router` row |
|
|
115
|
+
| `lib/index.js` | Host plugin: settings section, five tools, skill, guidance |
|
|
116
|
+
| `lib/client.js` | Browser half: the independent Settings page (hand-written factory bundle, no build step) |
|
|
117
|
+
| `lib/config.js` | Settings schema, defaults, runtime option resolution |
|
|
118
|
+
| `lib/net.js`, `lib/tunnel.js` | Route-aware HTTP layer; zero-dependency CONNECT proxy tunnel |
|
|
119
|
+
| `lib/routes/` | One module per route: `api` (GET-only REST), `gh` (CLI), `git` (protocol), `html` (page parse), `mirror` (raw mirrors) |
|
|
120
|
+
| `lib/core/` | Per-call runtime assembly and the `pr`/`issue`/`file`/`probe` aggregators |
|
|
121
|
+
| `lib/tools/` | The five model tools |
|
|
122
|
+
| `lib/cache.js`, `lib/render.js`, `lib/util.js` | TTL cache, text renderers, guards and shaping |
|
|
123
|
+
| `lib/skill.js`, `lib/guidance.js` | Skill content and prompt-injection section |
|
|
124
|
+
| `test/` | Runtime-free behavior tests (see Development) |
|
|
125
|
+
| `docs/` | Design and analysis documents |
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
No build step: the plugin is plain ESM and the tests run with Node directly
|
|
130
|
+
(a mock ctx stands in for the DSH services; the real `defineTool` validates
|
|
131
|
+
every schema):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm test
|
|
135
|
+
# or: node --test --test-isolation=none "test/*.test.js"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The tests are fully offline — they cover JSON-island extraction, input
|
|
139
|
+
guards, URL builders, the TTL cache, commit-log parsing, the tunnel request
|
|
140
|
+
head, and the apply() wiring. See [CONTRIBUTING.md](CONTRIBUTING.md) for the
|
|
141
|
+
development loop, including offline peer resolution.
|
|
142
|
+
|
|
143
|
+
## Security
|
|
144
|
+
|
|
145
|
+
Read-only by construction: no write verb exists, tokens attach only to
|
|
146
|
+
`api.github.com` and are redacted on every boundary, page payloads are
|
|
147
|
+
whitelist-extracted, and the only disk writes are the two plugin-owned
|
|
148
|
+
caches under `<DSH_HOME>/storages/dsh-github-router/`. See
|
|
149
|
+
[SECURITY.md](SECURITY.md) for the complete threat model and mitigation
|
|
150
|
+
list.
|
|
151
|
+
|
|
152
|
+
## Documentation
|
|
153
|
+
|
|
154
|
+
- [docs/design.md](docs/design.md) — architecture, route ladder, cache and confinement model, known limitations
|
|
155
|
+
- [SECURITY.md](SECURITY.md) — threat model and compensating controls
|
|
156
|
+
- [CHANGELOG.md](CHANGELOG.md) — release history
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# dsh-github-router
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **中文**
|
|
4
|
+
|
|
5
|
+
> 为 DeepSeek Harness 项目提供只读的 GitHub 访问——通过内部多路由(API、gh CLI、git 协议、页面 HTML、镜像)的工具加载 PR、issue、文件与 API 数据,agent 不再需要在终端里反复对抗 TLS 或代理故障。
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-github-router)
|
|
10
|
+
|
|
11
|
+
一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件捆绑包,为 agent 提供无需终端重试的 GitHub 读取能力:
|
|
12
|
+
|
|
13
|
+
- **五个只读工具** —— `github_probe`、`github_pr`、`github_issue`、`github_file`、`github_api`,外加 `github-router` 技能与系统提示引导段落。整个包内不存在任何写入、推送、评论或变更能力。
|
|
14
|
+
- **工具内路由** —— 每个请求都在宿主进程侧执行(不受沙箱 TLS/代理限制),并按路由阶梯回退:`api.github.com`(直连 → 代理)→ `gh` CLI(只读子命令)→ git 协议(插件自有 fetch 缓存,或对本地克隆仅做 log/diff/show 读取)→ PR/issue 页面 HTML(严格 JSON 岛提取)→ 用户配置的 raw 镜像。
|
|
15
|
+
- **一次调用、结构化结果** —— PR 一次返回元数据、讨论、评审、提交、变更文件与 diff,每个部件标注来源路由;失败时返回路由矩阵,而不是十几条重试过的终端命令。
|
|
16
|
+
- **连通性探针** —— `github_probe` 一次调用报告宿主侧哪些路由存活,附耗时与推荐路由链。
|
|
17
|
+
- **零运行时依赖** —— peer 依赖由 DSH profile 解析;代理请求走插件自带的 CONNECT 隧道(不依赖第三方 HTTP 栈),因此可以完全离线安装。
|
|
18
|
+
- **独立设置页** —— 设置中出现独立的「GitHub 路由」入口(与「通知」同机制):`dsh-github-router` 设置命名空间(secret token、代理、路由开关、缓存 TTL、字节上限),编辑暂存、保存/放弃、覆盖徽标。
|
|
19
|
+
- **上下文效率** —— 按类别 TTL 的响应缓存、全局字节上限、带截断注记的列表上限,以及限流余量提示。
|
|
20
|
+
|
|
21
|
+
## 环境要求
|
|
22
|
+
|
|
23
|
+
- Node.js >= 20.18
|
|
24
|
+
- 由 `@deepseek-ai/dsh-base` 组合的 DSH profile(提供插件使用的 `tools`、`subprocess`、`skills`、`settings`、`credentials` 服务)
|
|
25
|
+
- 可选:gh 路由需要已安装且认证的 `gh` CLI;git 路由需要 `git`
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
从 npm:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dsh plugin --profile web add dsh-github-router
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
从本地检出(开发):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dsh plugin --profile web add link:<本仓库的绝对路径>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
从 git 仓库:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
dsh plugin --profile web add github:<owner>/dsh-github-router
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
然后**重启 DSH 后端**——宿主组合在进程启动时装载。新会话中即可使用
|
|
48
|
+
`github_probe`、`github_pr`、`github_issue`、`github_file`、`github_api`,
|
|
49
|
+
以及 `github-router` 技能。
|
|
50
|
+
|
|
51
|
+
## 使用
|
|
52
|
+
|
|
53
|
+
Agent 侧:
|
|
54
|
+
|
|
55
|
+
| 工具 | 作用 |
|
|
56
|
+
| ---- | ---- |
|
|
57
|
+
| `github_probe` | 一次性连通性矩阵(api 直连/代理、gh 安装/认证、git ls-remote、页面直连/代理、镜像、token 有无),附耗时与推荐路由链。访问失败或变慢时先调用它。 |
|
|
58
|
+
| `github_pr` | PR 全貌:元数据、描述、讨论(issue 评论 + 行内评审评论)、评审、提交、变更文件与统一 diff,各部分标注来源路由。可用 `includeDiscussion`/`includeReviews`/`includeCommits`/`includeFiles`/`includeDiff` 开关部件,用 `maxDiffBytes`/`maxItems` 限幅。传 `localRepo`(或自动探测会话 cwd)可从本地克隆零网络读取提交与 diff。 |
|
|
59
|
+
| `github_issue` | issue 元数据、正文、标签与评论,附路由归属。 |
|
|
60
|
+
| `github_file` | 指定分支/标签/commit 的文件内容(或目录列表),经 api contents → raw → 镜像 → git 依次路由;返回大小、截断状态与 serving 路由。 |
|
|
61
|
+
| `github_api` | 校验后的 **GET-only** 逃生舱,可访问任意 `api.github.com` 端点;查询值净化、响应缓存、限流头提示、错误带稳定代码。 |
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
github_probe # 当前哪些路由存活
|
|
65
|
+
github_pr { owner: "o", repo: "r", number: 12 } # PR 全貌
|
|
66
|
+
github_pr { owner: "o", repo: "r", number: 12, localRepo: "C:/src/r" } # 从本地克隆读提交与 diff
|
|
67
|
+
github_issue { owner: "o", repo: "r", number: 34 }
|
|
68
|
+
github_file { owner: "o", repo: "r", path: "src/index.js", ref: "main" }
|
|
69
|
+
github_api { path: "/repos/o/r/commits", query: { per_page: 5 } }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
行为说明:
|
|
73
|
+
|
|
74
|
+
- 路由顺序按部件类型固定:先 API(直连后代理),再 gh,再页面 HTML(代理优先——直连 TLS 被重置的机器通常能经代理取到页面),再 git,最后镜像。每个部件记录其 serving 路由。
|
|
75
|
+
- 匿名 API 使用受限流(每 IP 60 次/小时);配置 token(设置项或 `GITHUB_TOKEN`)后为 5000 次/小时。响应缓存可省配额;`forceRefresh` 绕过缓存。
|
|
76
|
+
- 镜像**默认关闭**——它们是会看到请求路径的第三方;只有在接受这一点时才在设置中启用。
|
|
77
|
+
- git 路由绝不写入用户仓库:本地克隆只做 `git log`/`diff`/`show` 读取,fetch 仅发生在插件自有缓存目录 `<DSH_HOME>/storages/dsh-github-router/` 下。
|
|
78
|
+
|
|
79
|
+
## 配置
|
|
80
|
+
|
|
81
|
+
设置 → **GitHub 路由** 打开独立设置页(注册进 `settings.section` 的导航
|
|
82
|
+
入口,与「通知」区块同机制):编辑内容先本地暂存、点保存才落盘,被用户
|
|
83
|
+
覆盖的字段带标记,留空字段回退到下方默认值。同样的值也可以在组合
|
|
84
|
+
(profile 的 `cordis.patch.yml`)中作为插件基础配置写入;设置 UI 按用户
|
|
85
|
+
覆盖。
|
|
86
|
+
|
|
87
|
+
| 字段 | 默认 | 含义 |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `token` | — | 字面 GitHub token(secret;线上脱敏、只写输入)。优先用 `tokenEnv`。 |
|
|
90
|
+
| `tokenEnv` | `GITHUB_TOKEN` | 指向 token 的环境变量 / 凭据引用。 |
|
|
91
|
+
| `proxy` | `''` | 代理地址。`''` 继承环境变量 `HTTP(S)_PROXY`;`direct` 永不代理。 |
|
|
92
|
+
| `directTimeoutMs` / `proxyTimeoutMs` | 8000 / 15000 | 单次尝试超时。 |
|
|
93
|
+
| `retries` | 1 | 幂等 GET 在 429/5xx 上的重试次数(尊重 `Retry-After`)。 |
|
|
94
|
+
| `routesApi` / `routesGh` / `routesGit` / `routesHtml` / `routesMirror` | 开 / 开 / 开 / 开 / **关** | 各路由开关(卡片中以复选框展示)。 |
|
|
95
|
+
| `mirrors` | `[]` | raw 镜像基址,如 `["https://ghproxy.net"]`。 |
|
|
96
|
+
| `cacheTtlMeta` / `cacheTtlContent` | 300 / 86400 | 响应缓存 TTL(PR/issue 元数据 / 近似不可变内容),单位秒。 |
|
|
97
|
+
| `maxBytes` | 1048576 | 插件读取的每个响应体的字节上限。 |
|
|
98
|
+
| `repos` | `[]` | 授权只读 git 路由使用的本地仓库路径。 |
|
|
99
|
+
| `gitCacheDir` | `''` | 插件 fetch 缓存目录;`''` = `<DSH_HOME>/storages/dsh-github-router/git`。 |
|
|
100
|
+
|
|
101
|
+
## 工作原理
|
|
102
|
+
|
|
103
|
+
- **宿主侧执行** —— 插件代码运行在宿主进程,沙箱的 TLS 凭据重置与代理错乱完全不适用。这一不受限的令牌由 [SECURITY.md](SECURITY.md) 中的约束模型补偿,而不是削弱沙箱。
|
|
104
|
+
- **显式代理决策** —— 全局 `fetch` 不继承环境代理;每次尝试显式选择直连或代理,代理请求走零依赖 CONNECT 隧道(`node:http`/`node:tls`),TLS 对目标主机名校验,`accept-encoding: identity`。
|
|
105
|
+
- **严格解析** —— 页面 HTML 路由只提取 `react-app.embeddedData` JSON 岛并执行 `JSON.parse`(绝不求值);有界 BFS 只拷贝白名单字段,CSRF token 与原始载荷绝不进入模型。
|
|
106
|
+
- **纯 argv 子进程** —— 每次 `gh`/`git` 调用都是固定参数列表的 argv 数组;用户输入经正则校验后才进入 argv,全插件无 shell 插值。
|
|
107
|
+
- **工具契约** —— 规范值为无损失 JSON(数组不挂侧属性)、字节受限,并渲染为带路由归属的紧凑文本。
|
|
108
|
+
- **技能与引导** —— `github-router` 技能教授工具优先用法与"GitHub 读取不升级沙箱权限"规则;一条系统提示段落(`dsh-github-router:guidance`,order 118)提醒每个会话 github_* 工具是合规路径。
|
|
109
|
+
|
|
110
|
+
## 项目结构
|
|
111
|
+
|
|
112
|
+
| 路径 | 用途 |
|
|
113
|
+
| ---- | ---- |
|
|
114
|
+
| `cordis.patch.yml` | 插入 `dsh-github-router` 行的 profile patch 层 |
|
|
115
|
+
| `lib/index.js` | 宿主插件:设置段、五个工具、技能、引导 |
|
|
116
|
+
| `lib/client.js` | 浏览器半边:独立设置页(手写工厂包,无构建步骤) |
|
|
117
|
+
| `lib/config.js` | 设置 schema、默认值、运行时选项解析 |
|
|
118
|
+
| `lib/net.js`、`lib/tunnel.js` | 路由感知 HTTP 层;零依赖 CONNECT 代理隧道 |
|
|
119
|
+
| `lib/routes/` | 每条路由一个模块:`api`(GET-only REST)、`gh`(CLI)、`git`(协议)、`html`(页面解析)、`mirror`(raw 镜像) |
|
|
120
|
+
| `lib/core/` | 每次调用的运行时装配与 `pr`/`issue`/`file`/`probe` 聚合器 |
|
|
121
|
+
| `lib/tools/` | 五个模型工具 |
|
|
122
|
+
| `lib/cache.js`、`lib/render.js`、`lib/util.js` | TTL 缓存、文本渲染、守卫与整形 |
|
|
123
|
+
| `lib/skill.js`、`lib/guidance.js` | 技能内容与提示注入段落 |
|
|
124
|
+
| `test/` | 无运行时依赖的行为测试(见 开发) |
|
|
125
|
+
| `docs/` | 设计与分析文档 |
|
|
126
|
+
|
|
127
|
+
## 开发
|
|
128
|
+
|
|
129
|
+
无构建步骤:插件是纯 ESM,测试直接用 Node 运行(mock ctx 代替 DSH
|
|
130
|
+
服务;真实的 `defineTool` 校验每个 schema):
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm test
|
|
134
|
+
# 或:node --test --test-isolation=none "test/*.test.js"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
测试完全离线——覆盖 JSON 岛提取、入参守卫、URL 构建、TTL 缓存、提交日志
|
|
138
|
+
解析、隧道请求头与 apply() 装配。开发循环与离线 peer 解析见
|
|
139
|
+
[CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
140
|
+
|
|
141
|
+
## 安全
|
|
142
|
+
|
|
143
|
+
构造上只读:不存在写入动词;token 只附加到 `api.github.com` 且在每条
|
|
144
|
+
边界脱敏;页面载荷按白名单提取;唯一的磁盘写入是
|
|
145
|
+
`<DSH_HOME>/storages/dsh-github-router/` 下的两个插件自有缓存。完整威胁
|
|
146
|
+
模型与缓解清单见 [SECURITY.md](SECURITY.md)。
|
|
147
|
+
|
|
148
|
+
## 文档
|
|
149
|
+
|
|
150
|
+
- [docs/design.md](docs/design.md) — 架构、路由阶梯、缓存与约束模型、已知局限
|
|
151
|
+
- [SECURITY.md](SECURITY.md) — 威胁模型与补偿控制
|
|
152
|
+
- [CHANGELOG.md](CHANGELOG.md) — 版本历史
|
|
153
|
+
|
|
154
|
+
## 许可证
|
|
155
|
+
|
|
156
|
+
MIT
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# SECURITY.md
|
|
2
|
+
|
|
3
|
+
`dsh-github-router` runs **host-side with the host token** — unrestricted by
|
|
4
|
+
the agent's sandbox. That freedom is compensated by construction-level
|
|
5
|
+
constraints documented here.
|
|
6
|
+
|
|
7
|
+
## Threat model
|
|
8
|
+
|
|
9
|
+
The hostile inputs are: (1) agent-supplied tool arguments, (2) remote
|
|
10
|
+
GitHub responses (API JSON, git output, HTML pages, file bytes), and (3)
|
|
11
|
+
the configured proxy/mirrors (which see request URLs). The agent is assumed
|
|
12
|
+
untrusted for argument content; GitHub itself is assumed untrusted for
|
|
13
|
+
response content.
|
|
14
|
+
|
|
15
|
+
## Hard constraints
|
|
16
|
+
|
|
17
|
+
### 1. Read-only by construction
|
|
18
|
+
|
|
19
|
+
- The plugin contains **no write verb**. There is no POST/PATCH/PUT/DELETE,
|
|
20
|
+
no body, no push/checkout/reset/merge into user repositories, no comment
|
|
21
|
+
or reaction endpoints. `github_api` accepts only a validated relative
|
|
22
|
+
path and always performs GET.
|
|
23
|
+
- The only disk writes are: the TTL response cache and the git fetch cache,
|
|
24
|
+
both under `<DSH_HOME>/storages/dsh-github-router`. A user repository is
|
|
25
|
+
never fetched into — local clones are read with `git log`/`diff`/`show`/
|
|
26
|
+
`rev-parse`/`config --get` only, and only when the user granted the path
|
|
27
|
+
(`repos` setting), passed `localRepo`, or the session cwd's origin
|
|
28
|
+
matches the requested owner/repo.
|
|
29
|
+
- Code review note: if a future change adds any mutating capability, it
|
|
30
|
+
must be its own tool behind an explicit user-facing consent surface.
|
|
31
|
+
|
|
32
|
+
### 2. No shell interpolation
|
|
33
|
+
|
|
34
|
+
Every subprocess (`gh`, `git`) is spawned as an **argv array** through the
|
|
35
|
+
platform subprocess service. Nothing is concatenated into a shell command
|
|
36
|
+
string, so argument injection (spaces, `;`, `|`, `--flags`) cannot escape
|
|
37
|
+
the argv element. User-derived values reach argv only after validation:
|
|
38
|
+
|
|
39
|
+
- `owner`/`repo`: `/^[A-Za-z0-9][A-Za-z0-9._-]*$/`
|
|
40
|
+
- `number`: positive integer
|
|
41
|
+
- api path: starts `/`, no `..`, no spaces/control chars, ≤ 2048 chars
|
|
42
|
+
- file path: relative, no `..`, no control chars, ≤ 1024 chars
|
|
43
|
+
- ref: plain name/sha, rejects leading `-`, `..`, `~^:*?[]`, ≤ 512 chars
|
|
44
|
+
- query values: short strings without control chars, finite numbers,
|
|
45
|
+
booleans; ≤ 20 entries
|
|
46
|
+
|
|
47
|
+
### 3. Token handling
|
|
48
|
+
|
|
49
|
+
- The token is attached **only** to `api.github.com` requests (never to
|
|
50
|
+
proxies, mirrors, raw hosts, or the gh CLI — gh uses its own stored
|
|
51
|
+
auth).
|
|
52
|
+
- Settings declare the token with `role('secret')`: it is redacted at every
|
|
53
|
+
wire boundary, rendered as a write-only input, and never returned in tool
|
|
54
|
+
output. No output path carries it: tool results contain only parsed
|
|
55
|
+
payload fields, and failure notes carry error messages that never echo
|
|
56
|
+
request headers; proxy URLs in error text are credential-stripped.
|
|
57
|
+
- Resolution order: literal setting → credentials service
|
|
58
|
+
(`credentialRef(tokenEnv)`) → environment variable. Nothing is logged.
|
|
59
|
+
|
|
60
|
+
### 4. Page HTML parsing is JSON-only
|
|
61
|
+
|
|
62
|
+
The PR/issue page route extracts only
|
|
63
|
+
`<script type="application/json" data-target="react-app.embeddedData">`
|
|
64
|
+
islands and runs **`JSON.parse`** on them — never `eval`, `Function`, or
|
|
65
|
+
any executable interpretation. Extraction is a bounded BFS (depth 10, ≤
|
|
66
|
+
40k nodes) that copies a whitelist of fields (title, body text, state,
|
|
67
|
+
author, dates, review/discussion items) and drops everything else — CSRF
|
|
68
|
+
tokens, session data, and the raw payload never reach the model. Bodies
|
|
69
|
+
are HTML-tag-stripped for display and byte-capped.
|
|
70
|
+
|
|
71
|
+
### 5. Proxy and mirrors
|
|
72
|
+
|
|
73
|
+
- Global `fetch` does **not** inherit ambient proxy env automatically; each
|
|
74
|
+
attempt gets an explicit proxy decision (`direct` first for API/raw,
|
|
75
|
+
`proxy` first for page HTML), so a broken ambient proxy cannot silently
|
|
76
|
+
hijack traffic.
|
|
77
|
+
- Proxied requests travel through the plugin's own zero-dependency CONNECT
|
|
78
|
+
tunnel (node:http/tls): TLS is validated against the target hostname
|
|
79
|
+
exactly as direct requests validate it, the request carries
|
|
80
|
+
`accept-encoding: identity`, and every body passes the same byte cap.
|
|
81
|
+
No third-party HTTP stack is involved.
|
|
82
|
+
- Mirrors are **off by default**: they are third parties that see the
|
|
83
|
+
requested repo/path and can substitute bytes. Enabling them in settings
|
|
84
|
+
is an explicit user decision. Mirror responses pass through the same
|
|
85
|
+
byte caps as every other route.
|
|
86
|
+
|
|
87
|
+
### 6. Response containment
|
|
88
|
+
|
|
89
|
+
- Every response body (API JSON, HTML, raw files, git stdout) is
|
|
90
|
+
byte-capped (`maxBytes`, default 1 MiB); oversized bodies truncate with
|
|
91
|
+
an explicit marker.
|
|
92
|
+
- Non-2xx API responses are classified into stable codes
|
|
93
|
+
(`AUTH_REQUIRED`, `RATE_LIMITED`, `NOT_FOUND`, `HTTP`, `TRANSPORT`,
|
|
94
|
+
`TIMEOUT`, `ABORTED`) so failures are cheap to reason about.
|
|
95
|
+
- Cache files are parsed defensively: corrupt or expired entries are
|
|
96
|
+
dropped as misses; writes are tmp+rename (torn files degrade to a miss).
|
|
97
|
+
|
|
98
|
+
## Residual risks
|
|
99
|
+
|
|
100
|
+
- **Rate limits**: anonymous API use shares the host IP's 60 req/h quota.
|
|
101
|
+
Configure a token (Settings) to raise it to 5000 req/h; the cache is the
|
|
102
|
+
second line of defense.
|
|
103
|
+
- **git fetch cache growth**: repeated fetches grow the cache dir; it is
|
|
104
|
+
shallow (`--depth`), and users may delete it at any time — it rebuilds
|
|
105
|
+
on demand.
|
|
106
|
+
- **Mirrors/proxies see URLs**: enabling mirrors or a proxy reveals which
|
|
107
|
+
repos/files are requested. Off by default; opt-in.
|
|
108
|
+
- **gh CLI surface**: only fixed subcommands are constructed, but `gh api
|
|
109
|
+
<path>` inherits gh's own scoped token permissions; paths are validated
|
|
110
|
+
and the method is GET, yet an account-scoped token with broad grants
|
|
111
|
+
could still read beyond the public surface. Prefer the API route with
|
|
112
|
+
the plugin token when least-privilege matters.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# dsh-github-router bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# Applied as a profile patch layer after every in-box bundle. Inserts the
|
|
4
|
+
# plugin row at the profile root; later layers (or the user's own
|
|
5
|
+
# cordis.patch.yml) can address it by id with the last write winning.
|
|
6
|
+
- insert:
|
|
7
|
+
- id: dsh-github-router
|
|
8
|
+
name: 'dsh-github-router'
|