Repository navigation
Releases
v0.1.5
0.1.5
Ships the enlarged project logo, linked bilingual banners and contribution guidance with the reviewed GitHub Actions updates.
The published package includes the logo, artwork notice, both README entry pages and every referenced local banner/feature asset. Package version and installation examples are aligned. Runtime host requirements and profile/account formats are unchanged.
Release evidence records source CI, exact npm metadata and tarball hashes, GitHub release assets and market deployment separately. Installing and activating the final release in the intended local profile is verified after publication.
PR Release Workspace 0.1.4
0.1.4 — Verified against DSH 0.2.2-rc.1
This release fixes a wrong statement, and one real bug the statement was hiding.
0.1.3 works. It shipped a connection fence and it passed its tests. What was wrong was the
claim underneath it: the baseline said the host was DSH 0.1.6-alpha.2, and on that basis the
package declared engines.dsh: ">=0.1.6-alpha.2". The host was actually 0.2.2-rc.1.
Why the baseline was wrong
~/.dsh/profiles/node_modules/@deepseek-ai/dsh does report 0.1.6-alpha.2. That tree belongs
to the CLI profile. The Desktop application does not load it — Desktop runs its own runtime
from app.asar, and that runtime is @deepseek-ai/dsh-desktop-runtime 0.2.2-rc.1. Reading the
profile was reading the wrong tree, and every downstream document inherited the mistake.
The honest value comes out of the asar: 12 bytes of header, then a uint32 header size, then a
JSON directory, then the file data. Every forge package, the sidebar, the layout, the locale
packages and the client connection package inside it are 0.2.2-rc.1.
The bug under the claim
Running the panel on the real host surfaced one: pasting a pull request link searched for it
instead of opening it. The hint under the box has always said owner/repo#123 or a github.com
pull URL works, and the host has always had a resolve endpoint that parses exactly those — but
no client code ever called it. The submitted text went straight to GitHub's search API, which
returns nothing for a URL, so a paste came back as "no matches" for every PR including the one
two inches away in the list.
The box now routes on the shape of the input. A github.com pull URL, owner/repo/pull/123,
owner/repo#123, or a bare #123 / 123 goes to resolve and opens the detail view directly.
Anything else — and any reference the host refuses, such as a bare number with no repository in
view — falls back to search, so free text still answers honestly. looksLikeReference is only a
routing hint; the host still owns the real parsing and refuses non-references with
bad-request.
Verified on a running 0.2.2-rc.1 host: both the URL and the shorthand open the PR, free text
still searches, and Back returns to a clean list.
What changed in the code
Nothing had to be renamed. Three contracts were checked on both hosts and they are identical:
| Contract | 0.1.6-alpha.2 |
0.2.2-rc.1 |
|---|---|---|
sidebar.panellist |
list / root |
same |
main |
keyed / root, addressed by options.key |
same |
connection.requestRejection(request) |
403 / 401 / undefined |
same |
So there is no version branch anywhere. What did change is how the plugin finds things:
- Host services are looked up by
serviceOf(inner, name): the plain property first, then
inner.get(name, false). A Cordis proxy throws on a name the fiber did not declare, so the
property read is wrapped; the reflective read is the fallback. Capability checks
(typeof register === 'function', same forrequestRejection) still decide whether a route
is published — a service that exists but cannot fence still gets no route. - Client capabilities are probed once at load (
PRW.compat). Missing primitives are
reported instead of rendering blanks; a host without a slot service or a locale service still
gets a working panel with a local dictionary fallback. - The host version is read at runtime from the
clientModulesservice
(release.version) and logged. On a host that does not publish it, the log says
version not reported— it never guesses.
What changed in the metadata
engines.dshis now>=0.1.6-alpha.2 <0.3.0-0. The lower bound is the oldest host this
panel was tested against; the upper bound excludes the next major.<0.3.0-0rather than
<0.3.0because DSH evaluates the range withincludePrerelease: true, and under that rule
<0.3.0still admits0.3.0-0.versionis0.1.4.0.1.3was taken by the security release and is not being reused.
Documentation corrected
README.md/README.zh.md: the Requirements row now names the real range and the host it
was verified on.- The "Known limits" line that said DSH
0.1.6-alpha.2has no native diff or review panel is
gone. It was wrong twice over:0.2.2ships a native review surface (the Change requests &
worktrees experimental plugin), andDiffBlockwas never a dependency of this package —
it is part of the host. The README now describes the division of labour instead: the official
panel owns the right sidebar and shows the PR bound to the current session; this panel owns
the left rail and does discovery, search and the release chain. docs/baseline.md: the baseline table now reads the version out ofapp.asar, records the
three-contract comparison, and explains why a plugin should not re-implement the host's own
compatibility check.
Upgrading
npm install dsh-pr-release-workspace@0.1.4Nothing to migrate. No stored data changed shape; local notes are untouched.
Tests
166 cases, all passing. Three new files cover this release:
test/host-compat.test.js (service lookup order, a proxy that throws on read, a connection
without an admission method, version reporting), test/client-compat.test.js (degraded
primitives, a missing slot service, a missing locale service) and test/client-search.test.js
(which input goes to resolve and which goes to search).
0.1.3 — Require host authentication
dsh-pr-release-workspace 0.1.3
A security fix for compositions that lack DSH connection authentication.
What changed
In 0.1.2, a composition with a web server but no connection service or no
connection.requestRejection method still exposed the plugin's data route. It
trusted loopback, Host and Origin checks alone. A non-browser client can supply
those headers, so they cannot authenticate access to private GitHub data or local
review notes. A loopback binding limits reachability but does not fix that gap.
The plugin now registers no data route when connection authentication is
unavailable. The request admission function independently returns 403 when the
service or method is missing, including when the method disappears after route
registration. Authentication remains ahead of method and content-type checks.
The existing 401/403 passthrough and throw-refuses behavior remain in place.
Only the connection service's documented undefined result allows a request;
unexpected return values, including a Promise, are refused with 403.
Compositions without connection authentication have no panel data. They must
provide the supported DSH connection service to use the panel.
Verification
Two regression tests first failed against 0.1.2: a local JSON request was admitted
without an authentication service, and the host registered the data route anyway.
The tests now require 403 for missing or malformed services and no route
registration without the admission method. A further regression first failed
when the connection service returned null; it now covers unexpected return
values as refusals. Existing tests cover authenticated admission, 401/403
passthrough, exceptions and method/content-type checks.
The release preparation runs the full test suite, build and package dry-run.
Real DSH host validation and publication are tracked separately from those local
checks.
Upgrade
dsh plugin --profile <profile> add dsh-pr-release-workspace@0.1.3Restart the host after installation. The panel continues to use the current
gh session and never posts to GitHub.
Final package acceptance
The final npm tarball was installed through the official CLI into a fresh isolated DSH 0.2.2-rc.1 Web profile. Unauthenticated requests returned 401; after the official launch-token session flow, the identity endpoint returned 200 with viewer, teams and writeGuard. No model generation or GitHub write was performed. This is host/API acceptance; a new visual Desktop interaction test was not performed.
Source: 2ce39e7. npm tarball SHA-1: 4034918431614eb1b19eb52c38963556b7666a89.
v0.1.2 — the route now asks DSH who is allowed in
v0.1.2 — the route now asks DSH who is allowed in
A security fix. Everything the panel does is unchanged; what changed is who can
reach the route behind it.
The defect
The route was guarded only by isTrustedLocalRequest: it checked the Host
header, Origin, and sec-fetch-site. Those rules stop DNS rebinding — a page
cannot make the browser believe it is talking to 127.0.0.1 when it is not — but
they are not authentication. A page served from any origin can send a
loopback request without an Origin header at all, and the route answered it.
So: any web page the user happened to have open could read their pull request
list, titles, diffs and review comments through this panel.
The fix
DSH already provides the fence. connection.requestRejection(request) applies
the Host/Origin rules and then requires the browser session cookie that the
launch token established, answering 401 or 403. Every first-party route asks
it first (packages/host/open-in-app/src/index.ts:184-191), and its own comment
explains why: the fence "defeats DNS rebinding and cross-site calls, and its
browser authentication (the login-token cookie) gates every caller".
@deepseek-ai/dsh-host-webserver does no checking of its own, so this was
entirely the plugin's responsibility — and 0.1.0/0.1.1 skipped it.
apply now injects ['webServer', 'connection']. The whole admission decision
moved into admitRequest() in lib/host/routes.js, ordered as:
- connection fence —
401/403passed through verbatim - method —
405 - local check —
403 - content type —
415
The fence runs before the method check so a refused caller cannot probe which
endpoints exist. A connection service that throws refuses: "we could not
tell" is not "yes". A composition with no connection service at all (headless,
tests) still runs the local check and logs that only that check applies.
Verified in a real DSH host
POST /dsh-pr-release-workspace/identity, no cookie → 401
same call, with the launch-token session cookie → 200 + real pull requests
And the panel still lists, opens and diffs pull requests in the browser, so the
intended path is intact.
Upgrade
npm install -g dsh-pr-release-workspace@0.1.2
# or, in a profile you control:
dsh plugin --profile <profile> add dsh-pr-release-workspace@0.1.2Then restart the host. If you run 0.1.0 or 0.1.1, upgrade: the route in those
versions is reachable by any page the browser loads.
Tests
144 tests, all passing. Five are new and each names the attack it closes: the
401/403 passthrough, the throw-refuses case, the no-connection fallback, the
no-oracle ordering, and post-fence method/content-type checks.
Unchanged
- Nothing is ever written to GitHub: no comments, reviews, approvals, merges.
- No cookie, token or private pull-request content is logged.
- Local notes stay in
~/.dsh/storages/dsh-pr-release-workspace/drafts.json. - The six release facts remain six separate verdicts.
- Known limits still apply:
gh pr view --json filesstops at 100 files, diffs
over 300 files are reassembled page by page (a 1,680-file pull request takes
about 20 seconds and returns a large payload), and binary files have no text
sides.
v0.1.1 — large pull requests open again
dsh-pr-release-workspace 0.1.1
A patch release. One real defect, found by using the panel against a genuinely large pull
request rather than against fixtures.
Package: dsh-pr-release-workspace
License: MIT
Previous release: 0.1.0
What changed
Large pull requests open again
gh pr diff refuses any pull request with more than 300 changed files:
HTTP 406: Sorry, the diff exceeded the maximum number of files (300).
PullRequest.diff too_large
The panel has always had a fallback for that — the per-file endpoint still answers — but the
fallback could never run. runGh classifies a failure and returns a content-free message, so
the original stderr, which is the only place 406 and too_large appear, was already gone by
the time readPullDiff caught the error. The check that should have recognised the refusal
matched against that erased message and therefore always said no.
The effect in the real host: opening a 1680-file pull request and clicking 文件 rendered
「diff 加载失败。」 with 「GitHub 拒绝了这次请求。」 — a size limit reported as a refusal.
Now:
- The classifier assigns
diff-too-largebefore the generic branches. It is a recoverable
code, notunknown. - The per-file fallback actually runs, and pages up to 20 pages (2000 files) instead of 8.
- The files tab distinguishes two different claims.
truncatedmeans GitHub would not render
the change as one diff.completemeans the per-file read reached the end. When it did not,
the panel says the list below is incomplete instead of presenting a slice that looks whole.
Measured on tangyixiao/CSP-2026#6 (1680 changed files): 1680 files, 1667 with text, 13 named
as binary with no text on either side, complete: true. A 21-file pull request still takes the
fast gh pr diff path unchanged.
Tests
A new test pins the case that was missing:
the "more than 300 files" refusal is its own recoverable code, not unknown. It also asserts
the surfaced message stays content-free — no URL, no owner name.
Full suite: 139 tests, 139 pass.
Install
dsh plugin --profile <your-profile> add dsh-pr-release-workspace
Requires DSH 0.1.6-alpha.2 or newer, Node 22 or newer, and a gh on PATH that is already
signed in (gh auth login). The panel reads through gh and never handles a token itself.
Known limits, unchanged
- A 2000-file pull request takes roughly 25 seconds per diff open and produces a payload of
several megabytes. Reading every file eagerly has that cost; capping the payload or loading
files lazily is a separate change. - Files that GitHub will not patch (binaries, very large files) are listed by name with no
text on either side. They are not silently dropped. - The team tab needs
read:org. Without it the tab says membership could not be confirmed
rather than showing an empty list. - The panel is read-only. It does not comment, review, approve, request changes, or merge.
Those stay a deliberate action on GitHub.
v0.1.0 — first stable release
dsh-pr-release-workspace 0.1.0
First formal release. A DSH panel for reviewing pull requests and for checking whether a
release actually happened.
Package: dsh-pr-release-workspace
License: MIT
Repository: https://github.com/tangyixiao/dsh-pr-release-workspace
What it does
The plugin adds one sidebar entry, Pull requests, with two top-level views: the PR
workspace and Release state.
The PR workspace has three group tabs.
Created by me — pull requests you opened, with state, draft marker, the repository, the
head branch, and the relative time of the last update.
Awaiting my review — pull requests where your review is requested.
Awaiting my team — pull requests where a team you belong to is requested. This tab needs
the read:org scope on your gh token. Without it the tab says
"Team membership could not be confirmed, so this list may be incomplete." rather than showing
an empty list, because an empty list would read as "nothing to review".
Clicking a pull request opens a detail view with four tabs:
- Overview — base and head branches, head commit, mergeable or conflicting, the
description as text, and the CI rollup. - Files — the changed file list and, per file, the diff hunks.
- Comments — the reviews and the inline review comments, with file and line.
- My notes — local review notes you write per file and line.
A search box accepts owner/repo#123 or a pasted github.com pull request URL.
Release state
The second half of the panel answers a question that a merged pull request does not answer:
did this version actually ship? It takes a target — owner, repo, tag, commit SHA, npm package
name, version, and the market repository plus pull request number — and reports six
separate facts:
| Fact | Reads |
|---|---|
| Commit checks | GET /repos/{owner}/{repo}/commits/{sha}/check-runs |
| GitHub Release | the release for the tag |
| npm | the published version and the latest dist-tag |
| Market pull request | gh pr view on the market repository |
| Market manifest | market.json on the market's default branch |
| Live index | the published index URL |
Each fact renders with its own verdict — Yes, No, Pending, Unverified, Not configured, or
Stale — its reason, and a link to the evidence. The summary line reads "Every fact checks out"
only when all six pass. A pending CI run is not a pass. An npm version that exists but is not
latest is not a pass. A merged PR with no published package is not a pass.
This is deliberate. The panel is built to refuse to call a release done on the strength of one
green check.
Local review notes
You can attach notes to a file and line in the diff. They stay on this machine, under the DSH
home directory (~/.dsh by default), in a JSON store. They are never posted anywhere. A note
written against an earlier commit is marked "Written against an earlier commit" rather than
silently moved to whatever line now occupies that position.
What it does not do
It does not write to GitHub. No comments, no reviews, no approvals, no change requests, no
merges, no labels, no branch updates, no closing. The panel reads; you act on GitHub. The
message it shows when you try to leave a note says exactly this: "This panel is read-only: it
never posts to GitHub."
It does not register any agent tool, model provider, prompt fragment, or hook. It adds a
panel and nothing else. Your agent's tool list is unchanged by installing it.
It does not store or handle GitHub credentials. Every GitHub call goes through the gh
CLI and your existing gh auth login session. The plugin never sees a token.
It does not cache GitHub responses on disk. Each view is fetched when you open it.
Requirements
- DSH
>= 0.1.6-alpha.2(declared inengines.dsh). That release is the validated
baseline: it provides thewebServerroute service the panel's data channel uses. - Node.js
>= 22(engines.node). - GitHub CLI installed and authenticated: run
gh auth loginonce. Without it the panel
shows "Theghcommand is not available" or "GitHub is not signed in" and does nothing
else. - A DSH host that exposes
webServer. If it does not, the plugin logs a warning at start
instead of throwing, and the panel has no data.
Scopes: repo covers everything except the "Awaiting my team" tab and team review requests,
which need read:org.
Platforms: DSH web UI and the desktop app's web surface. This has been exercised on Windows;
macOS and Linux have not been run by the maintainer. The panel itself is
platform-independent, so the risk there is the gh CLI's behaviour, not the plugin's.
Install
From npm:
dsh plugin --profile web add dsh-pr-release-workspace
Or without naming the profile, into the default one:
dsh plugin add dsh-pr-release-workspace
From a downloaded tarball:
dsh plugin --profile web add ./dsh-pr-release-workspace-0.1.0.tgz
Then reload DSH. The sidebar entry is Pull requests. If it does not appear, check that
gh auth status succeeds in a terminal on the same machine.
To remove:
dsh plugin --profile web remove dsh-pr-release-workspace
Removal does not delete your local notes; they remain under ~/.dsh until you delete them.
Known limits
These are real and measured in the source, not hedging. Each one is a boundary set by the
GitHub API or a deliberate scope decision.
Diffs over 300 changed files. gh pr diff refuses a pull request with more than 300
changed files (HTTP 406, "too_large"). The plugin catches that and reassembles the diff from
the per-file patches endpoint instead, which is capped at 800 files (8 pages of 100). Beyond
that, the diff is marked truncated. The panel says so: "This pull request is larger than one
diff; files were read individually."
File lists over 100 files. gh pr view --json files returns at most 100 entries and
truncates silently. When the returned list is shorter than the PR's stated changed-file count,
the panel shows "showing 100 of 472" rather than pretending the list is complete.
Diffs over 1.5 MB. Truncated and labelled.
Inline review comments are one page of 100, and they are flat. Review comments are read
from a single REST page. More than 100 inline comments will be cut off. They render as a flat
list per file, ordered by file and line — there is no thread model, no reply nesting, and no
resolved/unresolved state. If you need to know whether a thread is resolved, use GitHub.
Search is capped at 1,000 results. GitHub's search API returns at most 1,000 items per
query. When the total count exceeds that, the panel reports it rather than implying it has
everything.
Team review requests are best-effort. Only the first six teams you belong to get their own
team-review-requested: query. Without read:org, the "Awaiting my team" tab shows
"unconfirmed" instead of an empty list — an empty list would be a lie.
Rate limits are surfaced, not retried. When GitHub asks the panel to slow down, it shows
the wait time GitHub gave it and does not retry in the background.
Notes are local only. There is no sync, no export yet, and no way to turn a note into a
GitHub review comment. The 20,000-character cap per note body is a request-body limit, not a
suggestion.
No multi-account support. The panel uses whichever account gh is currently authenticated
as.
What changed
Initial release. No prior versions.
Verification for this release
- 136 tests, 0 failures, 0 skipped (
npm test). - Build is reproducible from 13 source fragments (
npm run build). CI rebuilds the bundle and
fails if the committedlib/client.jsdiffers, so the source and the artifact cannot drift. node --checkclean onlib/index.js,lib/client.js,lib/host/routes.js,
lib/host/release.js.- Tarball contents inspected with
npm pack --dry-runbefore publishing: 16 files —
package.json,cordis.patch.yml,LICENSE,README.md,README.zh.md,icon.svg,
lib/(7 files),locale/en.json,locale/zh.json. Nosrc/, notest/, no
node_modules/. - The read-only guarantee is enforced by construction, not by policy:
lib/index.jsregisters
one prefix route and no tool, and every endpoint inlib/host/routes.jsis a read or a local
note write. A source scan intest/security.test.jsasserts no production file performs a
GitHub write. - Exercised end to end inside a real DSH process (isolated
DSH_HOME, profilerescue-prw,
port 13787) against real GitHub: the sidebar entry renders, the three group queries return
real data (authored 116 total, review-requested 0, team-review-requestedunconfirmedfor an
account with no teams), a real PR opens with its YAML diff and its two passing checks, the
notes tab saves and marks a note stale when the SHA changes, and the release panel returns six
separate rows for an unpublished version (ready: false).
What has not been done: a full release chain observed from the panel itself for a version
this plugin published — that is what 0.1.0 establishes, and the panel is used to verify it. And
the panel has not been run against a pull request with more than 300 changed files, so the
reassembly path is covered by unit tests rather than by a live observation.