dsh-packycode-api 1.3.8 → 1.3.9
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 +6 -0
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/lib/client.js +28 -0
- package/lib/index.js +71 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.9
|
|
4
|
+
|
|
5
|
+
- **The update button tells an install source that has not synced the new version, and one it cannot reach, from a plain failure.** The card reads the latest version from npm itself, while DSH's pnpm downloads it from the profile's own install source, and right after a release that source can lag: npmmirror, which listed 1.3.7 15 seconds after npm, still answered 1.3.7 36 minutes after 1.3.8 was published, and npm's CDN can serve a 404 for a new tarball for a few minutes. An update on another machine then failed with only "更新失败,请重试,或在插件页重新安装。" The plugin now reads pnpm's output with the codes DSH's own plugin manager reads. When the source has no such version or tarball of this package yet (`ERR_PNPM_NO_MATCHING_VERSION`, `ERR_PNPM_FETCH_404`, `ETARGET`, `E404`), the card shows a note to update again in a few minutes, adding that a regional mirror can take longer. When pnpm could not reach the source (`ERR_PNPM_META_FETCH_FAIL`, `ERR_PNPM_FETCH_5xx`, or Node's `ENOTFOUND`, `ECONNREFUSED`, `ECONNRESET`, `ETIMEDOUT`, `EAI_AGAIN`), or the install hung past its 3-minute bound, the card shows an alert to check the network or proxy. Both name the source's host when pnpm names it; its path, credentials and pnpm's output stay host-side. Another package's 404 and any other failure keep the old message. The install source is not switched.
|
|
6
|
+
- **An install stopped by its bound or by a cancel reports why.** Node reports a spawn's abort as its own `AbortError` before the process closes, so the bound's "timed out" never reached the update; it now does.
|
|
7
|
+
- Validation, 2026-10-22: 515 offline tests (new: real pnpm 11.28.5 output read as syncing or unreachable with only the host, another package's 404 and unknown output as neither; the update's `failed` outcomes; a pnpm that hangs, started through the real spawn, ending at the bound as unreachable while a cancel stays a cancel; the card's note and alert), both rebuilt bundles and the versioned tarball. With DSH's bundled pnpm 11.28.5 and the plugin's `update()` against scratch profiles holding 1.3.7: on npmjs it installed 1.3.8 (4.7 s); on npmmirror, still without 1.3.8, it returned `syncing` with `registry.npmmirror.com` in 0.9 s; on a closed local port it returned `unreachable` once pnpm's own retries were spent (70 s); on an address that never answers it returned `unreachable` at a 20-second bound (the default is 180 s), with `package.json` and the lockfile unchanged and no pnpm left running. Every failed profile kept 1.3.7. The card was checked through the client tests, not in DeepSeek Harness.
|
|
8
|
+
|
|
3
9
|
## 1.3.8
|
|
4
10
|
|
|
5
11
|
- **DSH's official 自动审查 can review with jev-latest or DeepSeek Flash.** DSH ships an experimental "自动授权审查" plugin (`@deepseek-ai/dsh-experimental-auto-review`, row `auto-review`, off by default). Switched on, it adds 自动审查 to the composer's permission menu and reviews every tool call of a session in that mode with one hand-built `llm.stream` request on the session's own model. The plugin now listens on DSH's `llm/stream` waterfall, which every model request passes. It recognizes that request by its fixed `REVIEW_POLICY` system prompt and four-section message, and answers it with the reviewer chosen under Settings → PackyCode → 自动审查 (`autoReviewModel`: `jev-latest` by default, `deepseek-flash`, or `session` to leave it alone). What is reviewed, the policy, asking the user, final denials in children, and refusing a call whose review failed all stay the official plugin's. Every other request goes on untouched. The plugin registers no permission preset and no tool gate of its own.
|
package/README.en.md
CHANGED
|
@@ -48,10 +48,10 @@ You need **DeepSeek Harness** and a **PackyCode (PackyAPI) API key**. For DSH se
|
|
|
48
48
|
Open the desktop application's **Plugins** page, click **Add plugin**, and enter the package name **with a version** as the **package name or address**:
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
|
-
dsh-packycode-api@1.3.
|
|
51
|
+
dsh-packycode-api@1.3.9
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Choose the **official** install source, then click **Install**. `1.3.
|
|
54
|
+
Choose the **official** install source, then click **Install**. `1.3.9` was the latest release when this was written; the [npm page](https://www.npmjs.com/package/dsh-packycode-api) shows the current one.
|
|
55
55
|
|
|
56
56
|
> **Always include the version.** With a bare `dsh-packycode-api`, the install prompt shows the newest version on npm, but DSH's bundled pnpm by default installs only versions published at least 24 hours ago, so you can end up several releases behind with no sign of it in the prompt. With an exact version, pnpm installs that version and adds it to the profile's `minimumReleaseAgeExclude` itself. Afterwards, check **Current version** under **Settings → PackyCode → Plugin version**.
|
|
57
57
|
|
|
@@ -72,7 +72,7 @@ Choose a model from the `PackyCode` group. All ten models use the plugin’s HTT
|
|
|
72
72
|
|
|
73
73
|
The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
|
|
74
74
|
|
|
75
|
-
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.3.
|
|
75
|
+
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.3.9`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
|
|
76
76
|
|
|
77
77
|
For a local checkout, keep the `link:` prefix: `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`. **Do not pass a bare path or `file:`** — that install copies the whole checkout, `node_modules` included, into the profile, where it then shadows the packages the DSH installation itself provides and the plugin looks uninstalled; see the FAQ. If you already installed that way, uninstall first and reinstall as above.
|
|
78
78
|
|
|
@@ -266,9 +266,9 @@ DeepSeek Flash uses the gateway's Responses endpoint (`/api/pricing` lists `open
|
|
|
266
266
|
|
|
267
267
|
## Update and uninstall
|
|
268
268
|
|
|
269
|
-
> **Upgrading from 1.3.2 or earlier:** install `dsh-packycode-api@1.3.
|
|
269
|
+
> **Upgrading from 1.3.2 or earlier:** install `dsh-packycode-api@1.3.9` on the Plugins page instead of clicking **Update plugin** in the old settings card. The old button bypasses pnpm's release-age policy: if the version it installs is less than 24 hours old, the profile refuses every other plugin install until that version is 24 hours old. After the install and a restart, the button is the fixed one, and later updates can use it.
|
|
270
270
|
|
|
271
|
-
**Update:** open **Settings → PackyCode** and, when a newer version is listed, click **Update plugin**. It installs that exact version into the owning profile with DSH's bundled pnpm and reads `package.json` back before reporting success. The install keeps pnpm's release-age policy instead of overriding it: by default pnpm installs the version at once and records it in the profile's `minimumReleaseAgeExclude`; under a stricter pnpm policy the button says when the update can run. A local `link:` checkout does not get the button. Then quit and reopen the desktop application. A page refresh does not reload the Host. Reinstalling from the **Plugins** page works too, again with the version in the package name (for example `dsh-packycode-api@1.3.
|
|
271
|
+
**Update:** open **Settings → PackyCode** and, when a newer version is listed, click **Update plugin**. It installs that exact version into the owning profile with DSH's bundled pnpm and reads `package.json` back before reporting success. The install keeps pnpm's release-age policy instead of overriding it: by default pnpm installs the version at once and records it in the profile's `minimumReleaseAgeExclude`; under a stricter pnpm policy the button says when the update can run. The newer version is read from npm, while pnpm downloads it from the profile's own install source: right after a release that source (a regional mirror in particular) may not have it yet, and the button asks you to update again in a few minutes; when the source cannot be reached, it asks you to check the network or proxy. A local `link:` checkout does not get the button. Then quit and reopen the desktop application. A page refresh does not reload the Host. Reinstalling from the **Plugins** page works too, again with the version in the package name (for example `dsh-packycode-api@1.3.9`); without it you may get a version that is merely the newest one published at least 24 hours ago.
|
|
272
272
|
|
|
273
273
|
**Coming back from a local-path install, uninstall first.** An update is a `pnpm add`: it rewrites the files inside the package directory and leaves a `node_modules` that an earlier local-path install left there. That older tree keeps shadowing the packages the DSH installation provides, so the plugin row fails to load with no visible reason. **Uninstall** runs `pnpm remove`, which deletes the package directory outright — only a reinstall after that is clean. `node scripts/install-check.mjs` reports whether a profile is in that state.
|
|
274
274
|
|
|
@@ -284,7 +284,7 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
284
284
|
|
|
285
285
|
Output means a leftover tree: a registry install ships no `node_modules`, so this one usually comes from an earlier local-path install (`file:`, or a bare path instead of `link:`). It sits inside the profile directory, and DSH resolves a plugin's imports from inside the profile first, so the plugin gets those old copies — for example `@deepseek-ai/schemastery` 3.18.2, which has no `Schema.volatile()`, so the module throws while it is imported. When an import fails the Loader creates no fiber for that row and no client bundle is composed either, so the model route, the RPC channel, and the settings panel all stay absent.
|
|
286
286
|
|
|
287
|
-
Recovery: **uninstall** the plugin on the **Plugins** page, **install** it again with the version in the package name (for example `dsh-packycode-api@1.3.
|
|
287
|
+
Recovery: **uninstall** the plugin on the **Plugins** page, **install** it again with the version in the package name (for example `dsh-packycode-api@1.3.9`), then quit and reopen the desktop application. Uninstall runs `pnpm remove` and deletes the whole package directory; reinstalling or clicking **Update** only rewrites files and keeps the leftover tree. Deleting that `node_modules` by hand and restarting works too.
|
|
288
288
|
|
|
289
289
|
**Right after a first install, every card in Settings shows `transport failure for /packycode-api/…: HTTP 405`?** The installed version is 1.1.6 or earlier, usually because only the bare package name was entered: the install prompt shows the newest version, but pnpm installs the newest one published at least 24 hours ago (see **Install the plugin**). Those older versions can register their settings channel only after DSH restarts and rebuilds its `connection` service. So right after a first install `/packycode-api` does not exist yet, requests fall through to the static page, and it answers 405; the model route and the settings panel itself load normally. From 1.1.7 on, a first install needs no restart. Check which version was installed:
|
|
290
290
|
|
package/README.md
CHANGED
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
打开桌面应用的**插件**页,点击**添加插件**,在**包名或地址**中填入**带版本号**的包名:
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
|
-
dsh-packycode-api@1.3.
|
|
51
|
+
dsh-packycode-api@1.3.9
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
在**安装源**中选择**官方源**,然后点击**安装**。`1.3.
|
|
54
|
+
在**安装源**中选择**官方源**,然后点击**安装**。`1.3.9` 是写这份说明时的最新版,当前最新版本号以 [npm 页面](https://www.npmjs.com/package/dsh-packycode-api) 为准。
|
|
55
55
|
|
|
56
56
|
> **一定要带版本号。** 只填 `dsh-packycode-api` 时,安装前的提示显示的是 npm 上的最新版,但 DSH 自带的 pnpm 默认只安装发布满 24 小时的版本,实际装上的可能是好几个版本之前的旧版,而安装提示里看不出这一点。填写精确版本时,pnpm 会直接安装这个版本,并自动把它加入 profile 的 `minimumReleaseAgeExclude`。装好后可以在 **设置 → PackyCode → 插件版本** 核对「当前版本」。
|
|
57
57
|
|
|
@@ -72,7 +72,7 @@ dsh-packycode-api@1.3.8
|
|
|
72
72
|
|
|
73
73
|
桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
|
|
74
74
|
|
|
75
|
-
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.3.
|
|
75
|
+
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.3.9`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
|
|
76
76
|
|
|
77
77
|
本地开发要加 `link:` 前缀:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。**不要直接写路径(或 `file:`)** —— 那样的安装会把整个仓库目录(连同 `node_modules`)复制进 profile,之后它会在 profile 内部挡住 DSH 自带的包,插件看起来像没装,见「常见问题」。已经那样装过的话,先卸载再按上面的方式重装。
|
|
78
78
|
|
|
@@ -266,9 +266,9 @@ DeepSeek Flash 走网关的 Responses 接口(`/api/pricing` 列出 `openai-res
|
|
|
266
266
|
|
|
267
267
|
## 更新与卸载
|
|
268
268
|
|
|
269
|
-
> **从 1.3.2 或更早版本升级:** 请在插件页填写 `dsh-packycode-api@1.3.
|
|
269
|
+
> **从 1.3.2 或更早版本升级:** 请在插件页填写 `dsh-packycode-api@1.3.9` 安装,不要点旧版设置页里的「更新插件」。旧按钮安装时会绕过 pnpm 的发布冷却期:装到的版本如果发布还不满 24 小时,这个 profile 在它满 24 小时之前就装不了其他插件。装好并重启后,按钮就换成了修好的版本,以后直接点即可。
|
|
270
270
|
|
|
271
|
-
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。安装遵守 pnpm 的发布冷却策略,不会绕过它:默认情况下 pnpm 会直接装上这个版本,并把它记进 profile 的 `minimumReleaseAgeExclude`;如果你给 pnpm
|
|
271
|
+
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。安装遵守 pnpm 的发布冷却策略,不会绕过它:默认情况下 pnpm 会直接装上这个版本,并把它记进 profile 的 `minimumReleaseAgeExclude`;如果你给 pnpm 配了更严格的策略,按钮会提示什么时候可以更新。新版本号是从 npm 读到的,pnpm 却从 profile 配置的安装源下载:刚发布的版本,安装源(尤其是国内镜像)可能还没同步到,这时按钮会提示过几分钟再更新;连不上安装源时,会提示检查网络或代理。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装,同样要填写带版本号的包名(例如 `dsh-packycode-api@1.3.9`),否则装上的可能是发布满 24 小时的旧版。
|
|
272
272
|
|
|
273
273
|
**从本地路径安装切回官方源时,必须先卸载。** 「更新」是 `pnpm add`,它只重写包目录里的文件,不会删除更早一次本地路径安装留在包目录内的 `node_modules`;那份旧依赖会继续挡住 DSH 自带的包,插件行加载失败且看不出原因。「卸载」执行的是 `pnpm remove`,会删掉整个包目录,重装才是干净的。用 `node scripts/install-check.mjs` 可以确认当前 profile 是否处于这个状态。
|
|
274
274
|
|
|
@@ -284,7 +284,7 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
284
284
|
|
|
285
285
|
有输出就是残留:从 npm 安装的包不会自带 `node_modules`,这一份通常来自更早一次「本地路径」安装(`file:` 或直接写路径,而不是 `link:`)。它位于 profile 目录内部,而 DSH 解析插件导入时优先使用 profile 内的副本,所以插件拿到的仍是那份旧依赖 —— 例如 `@deepseek-ai/schemastery` 3.18.2 没有 `Schema.volatile()`,模块在导入阶段就抛错。导入失败时 Loader 不会为这一行创建 fiber,客户端 bundle 也不会被组合,于是模型路由、RPC 通道和设置页全都不会出现。
|
|
286
286
|
|
|
287
|
-
修复:到**插件**页**卸载**本插件,再用带版本号的包名重新**安装**(例如 `dsh-packycode-api@1.3.
|
|
287
|
+
修复:到**插件**页**卸载**本插件,再用带版本号的包名重新**安装**(例如 `dsh-packycode-api@1.3.9`),然后完全退出并重新打开桌面应用。卸载执行的是 `pnpm remove`,会删掉整个包目录;直接重装或点「更新」只重写文件,不会清掉这份残留。也可以手动删掉上面那个 `node_modules` 再重启。
|
|
288
288
|
|
|
289
289
|
**首次安装后,设置页每张卡片都显示 `transport failure for /packycode-api/…: HTTP 405`?** 实际装上的是 1.1.6 或更早的版本,通常是因为安装时只填了包名:安装提示显示的是最新版,pnpm 实际装的却是发布满 24 小时的旧版(见「安装插件」)。这些旧版要等 DSH 重启、重建它的 `connection` 服务之后,才能注册设置通道。所以首次安装后 `/packycode-api` 通道还不存在,请求落到静态页面,返回 405;模型路由和设置页本身照常加载。1.1.7 起首次安装不需要重启。先确认装上的版本:
|
|
290
290
|
|
package/lib/client.js
CHANGED
|
@@ -194,6 +194,10 @@ window.__ModuleLoader__.load({
|
|
|
194
194
|
versionUpdate: "更新插件",
|
|
195
195
|
versionUpdating: "正在更新…",
|
|
196
196
|
versionUpdateFailed: "更新失败,请重试,或在插件页重新安装。",
|
|
197
|
+
versionUpdateSyncing: "v{version} 刚发布不久,安装源还没有同步到这个版本。请过几分钟再更新;国内镜像可能要等更久。",
|
|
198
|
+
versionUpdateSyncingFrom: "v{version} 刚发布不久,安装源 {registry} 还没有同步到这个版本。请过几分钟再更新;国内镜像可能要等更久。",
|
|
199
|
+
versionUpdateUnreachable: "连不上安装源,没能下载 v{version}。请检查网络或代理后重试。",
|
|
200
|
+
versionUpdateUnreachableFrom: "连不上安装源 {registry},没能下载 v{version}。请检查网络或代理后重试。",
|
|
197
201
|
versionCooling: "v{version} 发布不久,DSH 的 pnpm 要等新版本过了发布冷却期才会安装,请稍后再更新。",
|
|
198
202
|
versionCoolingUntil: "v{version} 发布不久,DSH 的 pnpm 要等新版本过了发布冷却期才会安装,{time} 之后可以更新。",
|
|
199
203
|
versionUpdated: "已更新到 v{version}。",
|
|
@@ -422,6 +426,10 @@ window.__ModuleLoader__.load({
|
|
|
422
426
|
versionUpdate: "Update plugin",
|
|
423
427
|
versionUpdating: "Updating…",
|
|
424
428
|
versionUpdateFailed: "The update failed. Try again, or reinstall from the Plugins page.",
|
|
429
|
+
versionUpdateSyncing: "v{version} was published only recently and the install source has not synced it yet. Update again in a few minutes; a regional mirror can take longer.",
|
|
430
|
+
versionUpdateSyncingFrom: "v{version} was published only recently and the install source {registry} has not synced it yet. Update again in a few minutes; a regional mirror can take longer.",
|
|
431
|
+
versionUpdateUnreachable: "Could not reach the install source to download v{version}. Check the network or proxy, then try again.",
|
|
432
|
+
versionUpdateUnreachableFrom: "Could not reach the install source {registry} to download v{version}. Check the network or proxy, then try again.",
|
|
425
433
|
versionCooling: "v{version} was published recently. DSH’s pnpm installs a new version only after its release cooldown; update again later.",
|
|
426
434
|
versionCoolingUntil: "v{version} was published recently. DSH’s pnpm installs a new version only after its release cooldown; update after {time}.",
|
|
427
435
|
versionUpdated: "Updated to v{version}.",
|
|
@@ -3036,6 +3044,11 @@ window.__ModuleLoader__.load({
|
|
|
3036
3044
|
return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
|
|
3037
3045
|
}
|
|
3038
3046
|
}
|
|
3047
|
+
/** Copy for an update pnpm could not fetch: the source still syncing, or unreachable; with and without its host. */
|
|
3048
|
+
const UPDATE_BLOCKED = {
|
|
3049
|
+
syncing: ["versionUpdateSyncing", "versionUpdateSyncingFrom"],
|
|
3050
|
+
unreachable: ["versionUpdateUnreachable", "versionUpdateUnreachableFrom"]
|
|
3051
|
+
};
|
|
3039
3052
|
function VersionCard({ call, t }) {
|
|
3040
3053
|
const [info, setInfo] = react.useState();
|
|
3041
3054
|
const [loading, setLoading] = react.useState(true);
|
|
@@ -3044,6 +3057,8 @@ window.__ModuleLoader__.load({
|
|
|
3044
3057
|
const [updateFailed, setUpdateFailed] = react.useState(false);
|
|
3045
3058
|
/** A version pnpm still holds back under the profile's release-age policy. */
|
|
3046
3059
|
const [pending, setPending] = react.useState();
|
|
3060
|
+
/** A version pnpm could not fetch: its install source still syncing, or unreachable. */
|
|
3061
|
+
const [blocked, setBlocked] = react.useState();
|
|
3047
3062
|
const [updated, setUpdated] = react.useState();
|
|
3048
3063
|
const [restartHint, setRestartHint] = react.useState(false);
|
|
3049
3064
|
const generation = react.useRef(0);
|
|
@@ -3069,11 +3084,16 @@ window.__ModuleLoader__.load({
|
|
|
3069
3084
|
setUpdating(true);
|
|
3070
3085
|
setUpdateFailed(false);
|
|
3071
3086
|
setPending(void 0);
|
|
3087
|
+
setBlocked(void 0);
|
|
3072
3088
|
call("plugin/update").then((value) => {
|
|
3073
3089
|
if (value?.pending === true) {
|
|
3074
3090
|
setPending(value);
|
|
3075
3091
|
return;
|
|
3076
3092
|
}
|
|
3093
|
+
if (UPDATE_BLOCKED[value?.failed] !== void 0) {
|
|
3094
|
+
setBlocked(value);
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3077
3097
|
setUpdated(value);
|
|
3078
3098
|
setRestartHint(false);
|
|
3079
3099
|
}).catch(() => setUpdateFailed(true)).finally(() => setUpdating(false));
|
|
@@ -3135,6 +3155,14 @@ window.__ModuleLoader__.load({
|
|
|
3135
3155
|
role: "alert",
|
|
3136
3156
|
children: t("versionUpdateFailed")
|
|
3137
3157
|
}),
|
|
3158
|
+
blocked !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3159
|
+
className: blocked.failed === "syncing" ? "pkc-note" : "pkc-error",
|
|
3160
|
+
role: blocked.failed === "syncing" ? "status" : "alert",
|
|
3161
|
+
children: fill(t(UPDATE_BLOCKED[blocked.failed][typeof blocked.registry === "string" ? 1 : 0]), {
|
|
3162
|
+
version: blocked.version,
|
|
3163
|
+
registry: blocked.registry
|
|
3164
|
+
})
|
|
3165
|
+
}),
|
|
3138
3166
|
pending !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3139
3167
|
className: "pkc-note",
|
|
3140
3168
|
role: "status",
|
package/lib/index.js
CHANGED
|
@@ -270,8 +270,12 @@ function createCachedRead(load, { ttlMs, now = Date.now }) {
|
|
|
270
270
|
* runs under the profile's own `minimumReleaseAge`: a version installed past
|
|
271
271
|
* it would sit in the profile's lockfile, and pnpm then refuses every later
|
|
272
272
|
* plugin install in that profile until the version is old enough. A version
|
|
273
|
-
* pnpm still holds back is reported as pending, with when it clears.
|
|
274
|
-
*
|
|
273
|
+
* pnpm still holds back is reported as pending, with when it clears. The
|
|
274
|
+
* latest version is read from npm itself while pnpm installs from the
|
|
275
|
+
* profile's own install source, which can lag behind a fresh release (a mirror
|
|
276
|
+
* still syncing, npm's CDN for a few minutes) or be unreachable; both are
|
|
277
|
+
* reported as such instead of a bare failure. Registry bodies and
|
|
278
|
+
* package-manager output stay host-side; only a registry host name leaves.
|
|
275
279
|
*
|
|
276
280
|
* @module dsh-packycode-api/plugin-version
|
|
277
281
|
*/
|
|
@@ -280,7 +284,17 @@ const NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/late
|
|
|
280
284
|
const DEFAULT_VERSION_TTL_MS = 5 * 6e4;
|
|
281
285
|
const DEFAULT_VERSION_TIMEOUT_MS = 1e4;
|
|
282
286
|
const DEFAULT_UPDATE_TIMEOUT_MS = 18e4;
|
|
287
|
+
/** The spawn bound's own reason, so a hung install reads as an unreachable source. */
|
|
288
|
+
const UPDATE_TIMED_OUT = "PackyCode plugin update timed out";
|
|
283
289
|
const RELEASE_AGE_ERROR = /ERR_PNPM_(?:NO_MATURE_MATCHING_VERSION|MINIMUM_RELEASE_AGE_VIOLATION)/u;
|
|
290
|
+
/**
|
|
291
|
+
* pnpm's codes for a version its registry does not list yet, or a tarball it
|
|
292
|
+
* does not serve yet; DSH's plugin manager reads the same ones.
|
|
293
|
+
*/
|
|
294
|
+
const NOT_SYNCED = /ERR_PNPM_NO_MATCHING_VERSION|\bETARGET\b|No matching version found|ERR_PNPM_FETCH_404|\bE404\b|404 Not Found|Not Found - 404/u;
|
|
295
|
+
/** pnpm's and Node's codes for a registry pnpm could not reach (DSH's network family). */
|
|
296
|
+
const UNREACHABLE = /ERR_PNPM_META_FETCH_FAIL|ERR_PNPM_FETCH_5\d\d|ERR_PNPM_FETCH_TIMEOUT|\bENOTFOUND\b|\bECONNRESET\b|\bETIMEDOUT\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|\bFETCH_ERROR\b|socket hang up/u;
|
|
297
|
+
const REGISTRY_URL = /(?:while fetching it from|GET)\s+(https?:\/\/\S+)/u;
|
|
284
298
|
const RELEASE_AGE_ENTRY = /(\S+@\S+) was published at (\S+?), within the minimumReleaseAge cutoff \((\S+?)\)/gu;
|
|
285
299
|
/**
|
|
286
300
|
* Recognize pnpm refusing a version that is younger than its
|
|
@@ -302,6 +316,39 @@ function releaseAgeRefusal(output, finishedAt) {
|
|
|
302
316
|
}
|
|
303
317
|
return availableAt === void 0 ? {} : { availableAt };
|
|
304
318
|
}
|
|
319
|
+
/** The host of the registry URL one pnpm error line names; never its path or credentials. */
|
|
320
|
+
function registryHost(line) {
|
|
321
|
+
const match = REGISTRY_URL.exec(line);
|
|
322
|
+
if (match === null) return {};
|
|
323
|
+
try {
|
|
324
|
+
const { host } = new URL(match[1].replace(/[.,:;)\]]+$/u, ""));
|
|
325
|
+
return host === "" ? {} : { registry: host };
|
|
326
|
+
} catch {
|
|
327
|
+
return {};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Recognize why pnpm could not fetch the new version: its install source has
|
|
332
|
+
* not synced the version yet, or pnpm could not reach the install source.
|
|
333
|
+
* Only a line naming this package counts as not synced; another package's 404
|
|
334
|
+
* is not this update's.
|
|
335
|
+
* @param output - the install's stdout and stderr.
|
|
336
|
+
* @returns `{ reason: 'syncing' | 'unreachable', registry? }` with the host
|
|
337
|
+
* pnpm named, or undefined for any other failure.
|
|
338
|
+
*/
|
|
339
|
+
function installFailure(output) {
|
|
340
|
+
const lines = String(output ?? "").replace(/\u001b\[[0-9;]*m/gu, "").split("\n");
|
|
341
|
+
const syncing = lines.find((line) => NOT_SYNCED.test(line) && line.includes("dsh-packycode-api"));
|
|
342
|
+
if (syncing !== void 0) return {
|
|
343
|
+
reason: "syncing",
|
|
344
|
+
...registryHost(syncing)
|
|
345
|
+
};
|
|
346
|
+
const unreachable = lines.find((line) => UNREACHABLE.test(line));
|
|
347
|
+
if (unreachable !== void 0) return {
|
|
348
|
+
reason: "unreachable",
|
|
349
|
+
...registryHost(unreachable)
|
|
350
|
+
};
|
|
351
|
+
}
|
|
305
352
|
/** Parse a strict `x.y.z[-pre][+build]` version; anything else is undefined. */
|
|
306
353
|
function parseSemver(value) {
|
|
307
354
|
if (typeof value !== "string") return void 0;
|
|
@@ -444,7 +491,7 @@ function spawnRunCommand({ command, args, cwd }, { signal, timeoutMs }) {
|
|
|
444
491
|
const controller = new AbortController();
|
|
445
492
|
const onAbort = () => controller.abort(signal.reason);
|
|
446
493
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
447
|
-
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error(
|
|
494
|
+
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error(UPDATE_TIMED_OUT)), timeoutMs);
|
|
448
495
|
let settled = false;
|
|
449
496
|
const settle = (callback, value) => {
|
|
450
497
|
if (settled) return;
|
|
@@ -476,11 +523,15 @@ function spawnRunCommand({ command, args, cwd }, { signal, timeoutMs }) {
|
|
|
476
523
|
child.stderr?.on("data", (chunk) => {
|
|
477
524
|
stderr = (stderr + chunk).slice(-8192);
|
|
478
525
|
});
|
|
479
|
-
|
|
526
|
+
/** Why the install was stopped: the caller's reason or the bound's. */
|
|
527
|
+
const abortReason = () => {
|
|
528
|
+
const reason = controller.signal.reason;
|
|
529
|
+
return reason instanceof Error ? reason : /* @__PURE__ */ new Error("PackyCode plugin update aborted");
|
|
530
|
+
};
|
|
531
|
+
child.on("error", (error) => settle(reject, controller.signal.aborted ? abortReason() : error));
|
|
480
532
|
child.on("close", (code) => {
|
|
481
533
|
if (controller.signal.aborted) {
|
|
482
|
-
|
|
483
|
-
settle(reject, reason instanceof Error ? reason : /* @__PURE__ */ new Error("PackyCode plugin update aborted"));
|
|
534
|
+
settle(reject, abortReason());
|
|
484
535
|
return;
|
|
485
536
|
}
|
|
486
537
|
settle(resolve, {
|
|
@@ -585,16 +636,29 @@ function createPackyCodePluginManager({ fetchImpl = ambientFetch, runCommand = s
|
|
|
585
636
|
});
|
|
586
637
|
} catch (error) {
|
|
587
638
|
if (signal?.aborted) throw error;
|
|
639
|
+
if (error?.message === UPDATE_TIMED_OUT) return {
|
|
640
|
+
version: latest,
|
|
641
|
+
profile: install.profile,
|
|
642
|
+
failed: "unreachable"
|
|
643
|
+
};
|
|
588
644
|
throw new Error("PackyCode plugin update failed");
|
|
589
645
|
}
|
|
590
646
|
if (result.code !== 0) {
|
|
591
|
-
const
|
|
647
|
+
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
648
|
+
const refusal = releaseAgeRefusal(output, now());
|
|
592
649
|
if (refusal !== void 0) return {
|
|
593
650
|
version: latest,
|
|
594
651
|
profile: install.profile,
|
|
595
652
|
pending: true,
|
|
596
653
|
...refusal
|
|
597
654
|
};
|
|
655
|
+
const failure = installFailure(output);
|
|
656
|
+
if (failure !== void 0) return {
|
|
657
|
+
version: latest,
|
|
658
|
+
profile: install.profile,
|
|
659
|
+
failed: failure.reason,
|
|
660
|
+
...failure.registry === void 0 ? {} : { registry: failure.registry }
|
|
661
|
+
};
|
|
598
662
|
throw new Error("PackyCode plugin update failed");
|
|
599
663
|
}
|
|
600
664
|
await confirmInstalled({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "Use PackyCode API keys in DeepSeek Harness with multi-protocol models, image generation and editing, separate key rings, and balance display",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|