Skip to content

Commit 4e820bb

Browse files
authored
fix(dsh): qualify 0.2 installs and require the Windows-safe CLI (#5589)
Qualify the independently distributed DSH provider on the supported host contracts and require the released Windows-safe LoopX CLI. Public beta.6 publication and marketplace adoption remain separately tracked. Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
1 parent 095659a commit 4e820bb

22 files changed

Lines changed: 7598 additions & 4012 deletions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Publish DSH Plugin to npm
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
release_tag:
7+
description: "Existing published DSH plugin release tag"
8+
required: true
9+
type: string
10+
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: dsh-plugin-npm-publication
16+
cancel-in-progress: false
17+
18+
jobs:
19+
publish:
20+
if: github.repository == 'loopx-project/loopx' && github.ref_type == 'tag' && github.ref_name == inputs.release_tag
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 10
23+
permissions:
24+
contents: read
25+
id-token: write
26+
defaults:
27+
run:
28+
working-directory: packages/dsh-loopx-plugin
29+
env:
30+
RELEASE_TAG: ${{ inputs.release_tag }}
31+
GH_TOKEN: ${{ github.token }}
32+
steps:
33+
- uses: actions/checkout@v7
34+
with:
35+
ref: ${{ github.sha }}
36+
fetch-depth: 0
37+
- uses: actions/setup-node@v6
38+
with:
39+
node-version: "24"
40+
- name: Require a merged, published release with matching package identity
41+
run: |
42+
git fetch origin main
43+
git merge-base --is-ancestor HEAD origin/main
44+
node --input-type=module <<'NODE'
45+
import assert from 'node:assert/strict'
46+
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs'
47+
const manifest = JSON.parse(readFileSync('package.json', 'utf8'))
48+
assert.equal(process.env.RELEASE_TAG, `dsh-loopx-plugin-v${manifest.version}`)
49+
mkdirSync('output', { recursive: true })
50+
appendFileSync(process.env.GITHUB_ENV, `DSH_ARTIFACT=output/${manifest.name}-${manifest.version}.tgz\n`)
51+
NODE
52+
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json tagName,isDraft > output/release.json
53+
node -e 'const r=require("./output/release.json"); require("node:assert/strict").equal(r.isDraft, false)'
54+
- name: Download the immutable GitHub package
55+
run: |
56+
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern "$(basename "$DSH_ARTIFACT")" --dir output
57+
tar -xOf "$DSH_ARTIFACT" package/package.json > output/manifest.json
58+
node --input-type=module <<'NODE'
59+
import assert from 'node:assert/strict'
60+
import { appendFileSync, readFileSync } from 'node:fs'
61+
const source = JSON.parse(readFileSync('package.json', 'utf8'))
62+
const packed = JSON.parse(readFileSync('output/manifest.json', 'utf8'))
63+
for (const field of ['name', 'version', 'main', 'exports', 'dsh', 'peerDependencies', 'repository', 'keywords']) {
64+
assert.deepEqual(packed[field], source[field], `release artifact ${field} differs from its tag`)
65+
}
66+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(source.name)}`, {
67+
signal: AbortSignal.timeout(15_000),
68+
})
69+
assert(response.ok || response.status === 404, `registry lookup failed: HTTP ${response.status}`)
70+
const exists = response.ok && !!(await response.json()).versions?.[source.version]
71+
appendFileSync(process.env.GITHUB_ENV, `DSH_ALREADY_PUBLISHED=${exists}\n`)
72+
NODE
73+
- name: Publish the same bytes with npm trusted publishing
74+
if: env.DSH_ALREADY_PUBLISHED != 'true'
75+
run: npm publish "$DSH_ARTIFACT" --access public --tag latest --ignore-scripts --provenance
76+
- name: Read back npm bytes, latest, and marketplace repository discovery
77+
run: node scripts/verify-distribution.mjs --tarball "$DSH_ARTIFACT"

‎.github/workflows/dsh-plugin.yml‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: DSH Plugin Distribution
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "packages/dsh-loopx-plugin/**"
7+
- ".github/workflows/dsh-plugin.yml"
8+
- ".github/workflows/dsh-plugin-publish.yml"
9+
push:
10+
branches: [main]
11+
paths:
12+
- "packages/dsh-loopx-plugin/**"
13+
- ".github/workflows/dsh-plugin.yml"
14+
- ".github/workflows/dsh-plugin-publish.yml"
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
20+
concurrency:
21+
group: dsh-plugin-${{ github.ref }}
22+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
23+
24+
jobs:
25+
distribution:
26+
name: dsh-plugin (${{ matrix.os }})
27+
runs-on: ${{ matrix.os }}
28+
timeout-minutes: 20
29+
strategy:
30+
fail-fast: false
31+
matrix:
32+
os: [ubuntu-latest, windows-latest]
33+
defaults:
34+
run:
35+
working-directory: packages/dsh-loopx-plugin
36+
shell: bash
37+
env:
38+
LOOPX_USAGE_PING: "0"
39+
steps:
40+
- uses: actions/checkout@v7
41+
- uses: actions/setup-node@v6
42+
with:
43+
node-version: "24"
44+
- uses: pnpm/action-setup@v4
45+
with:
46+
version: "10.33.0"
47+
- uses: astral-sh/setup-uv@v7
48+
- name: Prepare source-checkout integration interpreter
49+
if: runner.os == 'Linux'
50+
working-directory: .
51+
run: uv sync --extra test
52+
- name: Install frozen plugin dependencies
53+
run: pnpm install --frozen-lockfile --ignore-scripts
54+
- name: Validate typed host and client contracts
55+
run: pnpm typecheck && pnpm smoke:peer-range
56+
- name: Validate unit and real LoopX admission contracts
57+
if: runner.os == 'Linux'
58+
run: PATH="$GITHUB_WORKSPACE/.venv/bin:$PATH" pnpm test
59+
- name: Build the npm publication artifact
60+
run: |
61+
node -e 'require("node:fs").mkdirSync("output", { recursive: true })'
62+
npm pack --pack-destination output
63+
node -e 'require("node:fs").appendFileSync(process.env.GITHUB_ENV, "DSH_ARTIFACT=output/dsh-loopx-plugin-" + require("./package.json").version + ".tgz\n")'
64+
- name: Qualify the real package-name install and uninstall
65+
run: node smoke/dsh-registry-smoke.mjs --tarball "$DSH_ARTIFACT"
66+
- name: Qualify packed Client and real web runtime
67+
if: runner.os == 'Linux'
68+
run: |
69+
node smoke/dsh-client-artifact-smoke.mjs --tarball "$DSH_ARTIFACT"
70+
node smoke/dsh-profile-smoke.mjs --tarball "$DSH_ARTIFACT"
71+
node smoke/dsh-goalbar-runtime-smoke.mjs --tarball "$DSH_ARTIFACT"
72+
- name: Preserve the qualified package for review and local recovery
73+
uses: actions/upload-artifact@v7
74+
with:
75+
name: dsh-loopx-plugin-${{ matrix.os }}
76+
path: packages/dsh-loopx-plugin/${{ env.DSH_ARTIFACT }}
77+
if-no-files-found: error

‎docs/architecture/rfcs/harness-selection-dsh-pi-v0.md‎

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -320,24 +320,31 @@ These are integration-cost and contract observations, not claims that Pi lacks
320320
events or DSH cannot support other models. Both expose control-capable APIs;
321321
passivity is a property of the selected adapter and its loaded dependencies.
322322

323-
The two LoopX surfaces that depend on dsh do not move together. The bounded Turn
324-
host uses the Python SDK/runtime pin recorded above (`0.1.5rc1`, the released
325-
channel). The dsh-side plugin (`packages/dsh-loopx-plugin`) now builds its
326-
development, host, and client surfaces on the same released `0.1.5-rc.2` line
327-
instead of the retired `0.1.1-rc.2` one, and its npm peer ranges admit only
328-
`>=0.1.5-rc.1`. Three upstream moves forced that, so it is a new release line
329-
rather than a patch: the 0.1.5 line no longer publishes
330-
`@deepseek-ai/dsh-client-runtime` (last released 0.1.1-rc.2), which moves the
331-
`slots` service seat to `@deepseek-ai/dsh-client-ui-renderer` — the package this
332-
manifest now names in `dsh.client.inject`; `Session.events` became
333-
`Session.snapshotEvents()` and `Inbox.hasPending` became the two pending queues;
334-
and the shared `/api` bridge addresses Remote methods as `<namespace>/<method>`
335-
with a single `args` payload field. One `dsh.client.inject` list cannot order
336-
boot rows for both generations at once, so the plugin cannot claim both. The L1
337-
observer contract above is unchanged: the observer still consumes only
338-
`session/created`, `session/event`, and `session/disposed`, and now treats
339-
token-level `assistant/chunk` rows as retired input replayed from older durable
340-
logs instead of a live event type.
323+
The bounded Turn host's Python SDK/runtime pin remains separate from the
324+
independently versioned `packages/dsh-loopx-plugin`. The plugin source now pins
325+
its development, host, and Client packages to `0.2.0-rc.2`, retaining the supported
326+
0.1.5 and explicit 0.1.7 prerelease peer ranges. It registers its initialization
327+
message source, resets Session state at `agent/created`, and keeps Connection
328+
Peer admission in the upstream transport. Client revisions remain opaque.
329+
Bootstrap and runtime consumers require LoopX 1.2.4 or newer, including the
330+
released Windows peer-file fix; an explicit outdated CLI fails before install.
331+
The retired 0.1.1 Client runtime is still unsupported; the renderer owns slots,
332+
and the shared `/api` carrier retains `<namespace>/<method>` and `args`.
333+
334+
The npm distribution channel requires the exact GitHub release artifact,
335+
the qualified `latest` tag, and repository search selecting `dsh-loopx-plugin`
336+
instead of the monorepo root. The registry smoke qualifies package-name
337+
installation and removal locally; CI covers Linux and Windows. Direct release
338+
installation reported in [Hub PR #93](https://github.com/dshplugin/dsh-plugin-hub/pull/93)
339+
was shipped independently in [Hub v1.4.14](https://github.com/dshplugin/dsh-plugin-hub/releases/tag/v1.4.14).
340+
It uses the authoritative catalog command and does not require publishing this
341+
provider to npm. The live catalog still selects beta.5; beta.6 publication and
342+
selection remain open. Released-Hub installation passed, but its Client
343+
installed-state identity does not yet recognize the release URL, leaving the
344+
marketplace update/removal journey incomplete. Public distribution, marketplace adoption, and browser-mounted
345+
Start/Pause remain independent release evidence. The L1 observer still consumes only
346+
`session/created`, `session/event`, and `session/disposed`; this compatibility
347+
repair does not close its separately budgeted C0/C1 or overhead qualification.
341348

342349
## Data and Authority Flow
343350

‎docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md‎

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,25 @@ tag:PyPI 上的 `deepseek-harness-sdk==0.1.5rc1` /
255255
这是接入成本和合同差异,不是说 Pi 没有事件,或 DSH 不能使用其他模型。
256256
两个 harness 都有控制 API;“被动”是具体 adapter 和实际加载依赖的性质。
257257

258-
依赖 dsh 的两个 LoopX 面并不一起移动:有界 Turn 宿主使用上文记录的 Python
259-
SDK/runtime 固定版本(`0.1.5rc1`,已发布通道);而 dsh 侧插件
260-
(`packages/dsh-loopx-plugin`)的开发、宿主与客户端面现已统一构建在同一已发布的
261-
`0.1.5-rc.2` 线上,不再停留在 `0.1.1-rc.2`,其 npm peer 范围只接受
262-
`>=0.1.5-rc.1`。这是上游三处变化逼出来的,因此它是一条新的发布线而不是原地补丁:
263-
0.1.5 线不再发布 `@deepseek-ai/dsh-client-runtime`(最后发布版本为 `0.1.1-rc.2`),
264-
`slots` service 座位随之移到 `@deepseek-ai/dsh-client-ui-renderer`,也就是本 manifest
265-
现在写入 `dsh.client.inject` 的包;`Session.events` 变为 `Session.snapshotEvents()`,
266-
`Inbox.hasPending` 变为两个 pending 队列;共享 `/api` bridge 用
267-
`<namespace>/<method>` 寻址 Remote 方法,并只接受一个 `args` payload 字段。一份
268-
`dsh.client.inject` 无法同时为两代排序 boot row,所以插件不能同时声明两代。上文的
269-
L1 observer 契约不变:observer 仍只消费 `session/created`、`session/event`、
270-
`session/disposed`,只是把 token 级 `assistant/chunk` 行视为旧 durable 日志重放出来的
271-
已退场输入,而不是现存事件类型。
258+
有界 Turn 宿主的 Python SDK/runtime 固定版本,与独立版本化的
259+
`packages/dsh-loopx-plugin` 分开维护。插件源码现将开发、Host、Client 包固定为
260+
`0.2.0-rc.2`,保留已支持的 0.1.5 和显式 0.1.7 预发布 peer 范围。插件注册自己的
261+
初始化消息来源,在 `agent/created` 重置 Session 状态,并由上游 Connection transport
262+
继续负责 Peer 准入;Client revision 保持不透明。
263+
初始化与运行时统一要求包含 Windows peer-file 修复的 LoopX 1.2.4 或更新版本;
264+
显式指定的旧 CLI 在安装前失败。已退场的 0.1.1 Client runtime 仍不支持,slots 由
265+
renderer 提供,共享 `/api` 保留 `<namespace>/<method>` 和 `args` 契约。
266+
267+
npm 分发通道要求包与 GitHub release artifact 完全一致,`latest` 指向已验证版本,
268+
仓库搜索选中 `dsh-loopx-plugin` 而不是 monorepo 根目录。registry smoke 在本地验证
269+
包名安装与卸载,CI 覆盖 Linux 和 Windows。
270+
[Hub PR #93](https://github.com/dshplugin/dsh-plugin-hub/pull/93) 报告的直接 release 安装已由
271+
上游独立实现并发布到 [Hub v1.4.14](https://github.com/dshplugin/dsh-plugin-hub/releases/tag/v1.4.14),
272+
按现有权威目录命令安装,不要求本 provider 先发布 npm。在线目录仍选择 beta.5;beta.6
273+
发布与目录采用仍未完成。发布版 Hub 的安装已通过,但其 Client 的已安装状态仍不能
274+
识别 release URL,因此市场升级、卸载交互尚未闭环。
275+
公开分发、市场目录采用,以及浏览器挂载后的 Start/Pause 仍各自需要发布证据。L1 observer 仍只消费 `session/created`、
276+
`session/event`、`session/disposed`;兼容性修复不关闭另行预算的 C0/C1 或开销验收。
272277

273278
## 数据流与权限
274279

0 commit comments

Comments
 (0)