Skip to content

Releases

v0.1.5

Choose a tag to compare

@tangyixiao tangyixiao released this 10 Nov 13:22
294ff60

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

Choose a tag to compare

@tangyixiao tangyixiao released this 08 Nov 22:22
e7d1808

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 for requestRejection) 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 clientModules service
    (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.dsh is 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-0 rather than
    <0.3.0 because DSH evaluates the range with includePrerelease: true, and under that rule
    <0.3.0 still admits 0.3.0-0.
  • version is 0.1.4. 0.1.3 was 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.2 has no native diff or review panel is
    gone. It was wrong twice over: 0.2.2 ships a native review surface (the Change requests &
    worktrees
    experimental plugin), and DiffBlock was 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 of app.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.4

Nothing 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

Choose a tag to compare

@tangyixiao tangyixiao released this 07 Nov 13:48
2ce39e7

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.3

Restart 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

Choose a tag to compare

@tangyixiao tangyixiao released this 07 Nov 12:10
15fc0f1

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:

  1. connection fence — 401/403 passed through verbatim
  2. method — 405
  3. local check — 403
  4. 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.2

Then 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 files stops 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

Choose a tag to compare

@tangyixiao tangyixiao released this 07 Nov 11:47
7a3dd30

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-large before the generic branches. It is a recoverable
    code, not unknown.
  • The per-file fallback actually runs, and pages up to 20 pages (2000 files) instead of 8.
  • The files tab distinguishes two different claims. truncated means GitHub would not render
    the change as one diff. complete means 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

Choose a tag to compare

@tangyixiao tangyixiao released this 07 Nov 11:03
e0e15f1

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 in engines.dsh). That release is the validated
    baseline: it provides the webServer route service the panel's data channel uses.
  • Node.js >= 22 (engines.node).
  • GitHub CLI installed and authenticated: run gh auth login once. Without it the panel
    shows "The gh command 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 committed lib/client.js differs, so the source and the artifact cannot drift.
  • node --check clean on lib/index.js, lib/client.js, lib/host/routes.js,
    lib/host/release.js.
  • Tarball contents inspected with npm pack --dry-run before 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. No src/, no test/, no
    node_modules/.
  • The read-only guarantee is enforced by construction, not by policy: lib/index.js registers
    one prefix route and no tool, and every endpoint in lib/host/routes.js is a read or a local
    note write. A source scan in test/security.test.js asserts no production file performs a
    GitHub write.
  • Exercised end to end inside a real DSH process (isolated DSH_HOME, profile rescue-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-requested unconfirmed for 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.