dsh-packycode-api 1.2.7 → 1.2.8
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 +7 -0
- package/README.en.md +9 -7
- package/README.md +9 -7
- package/lib/client.js +201 -0
- package/lib/index.js +343 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.8
|
|
4
|
+
|
|
5
|
+
- **Diagnostics for request problems.** Settings → PackyCode gains a **Diagnostics** card (诊断信息). A user who hits "已达到输出 token 上限" or repeated failures picks the session (the 20 most recent top-level sessions, newest first, labelled by local start time, the workspace folder's base name and whether it is open), clicks **Generate diagnostics** and then **Copy**, and sends the text to the plugin author. Without a clipboard API the text is selected for a manual copy. The host builds the text from DSH's session history over two new RPC endpoints, `diagnostics/sessions` and `diagnostics/session`, in format `packycode-diagnostics/1`. It holds the plugin version; the session id, start time and live state; each stored key by position with its groups and reachable models; the logged request settings (provider, model, reasoning effort, `maxTokens`, and whether DSH's adapter supplied that ceiling); the steps that ended at the output limit; the latest steps' token usage and finish reasons; turn endings; retries and failed attempts with their codes; and the compaction count. Error messages are redacted and cut to 160 characters. Conversation text, reasoning, titles, tool arguments and results, paths, key labels and keys never enter it. An invalid session id or a runtime without session history is refused with its own message.
|
|
6
|
+
- **Claude session titles come back as plain text in the user's language.** A Claude session's title runs on `claude-haiku-5-5` inside the Claude Code CLI, where DSH's title rules are only an appendix to the CLI's own system prompt and the user's text arrives as English framing around a JSON array. For a short message such as 在吗, the title came back as English or as raw JSON (`{"title": "Casual greeting check-in"}`). The CLI title prompt now ends by restating the rules: the bare title, not JSON or quoted, in the messages' language, named outright when the text is Chinese, Japanese or Korean. Only the CLI's last text block is read as the reply, so narration before a tool call is never taken for the title. The reply is then unwrapped from a code fence, a JSON object's `title` or a JSON string, a "Title:"/"标题:" label, or wrapping quotes before DSH reads it. A reply that holds no usable title yields no text, so DSH keeps its first-message fallback: several lines (live, Haiku once wrote its thinking on one line and the title on another), a placeholder such as 对话标题 / "Session title", or an empty wrapper. HTTP models' titles and delegated turns are unchanged.
|
|
7
|
+
- README: an FAQ entry on reporting truncated replies and failing requests with the Diagnostics card, and a note on how Claude-session titles are restated and unwrapped.
|
|
8
|
+
- Validation, 2026-10-15: 437 offline tests (10 new for diagnostics, 5 for titles), both rebuilt bundles and the versioned tarball. A browser replay of the built settings page showed the session picker, the generated text and, where the clipboard refused the write, the select-all fallback. Titles were replayed live through the built CLI adapter with DSH 0.2.0-rc.2's exact title request (system prompt, JSON framing, 64-token cap) and a `cc` key. Before the fix, 在吗 came back as `Greeting Check-In` and as `{"title": "Session greeting check"}`. After it, ten titles for 在吗, 你好, 你饿吗, hi and a Chinese repository question all came back as plain text, Chinese for Chinese messages, for about $0.08. No DSH restart.
|
|
9
|
+
|
|
3
10
|
## 1.2.7
|
|
4
11
|
|
|
5
12
|
- **Image keys can be renamed.** Settings → PackyCode → Image generation and editing gains the same **Rename** action as conversation keys, through a new `images/keys/rename` endpoint on the image key ring (`packycode-api/image-keys`). Both cards share one rename form: the name becomes a text field, Enter or **Save** stores it, Escape or **Cancel** keeps the old one, and only the key id and the new label reach the host.
|
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.2.
|
|
51
|
+
dsh-packycode-api@1.2.8
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Choose the **official** install source, then click **Install**. `1.2.
|
|
54
|
+
Choose the **official** install source, then click **Install**. `1.2.8` 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. `deepseek-flash`, `gpt-6.1-sol`, `gpt
|
|
|
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.2.
|
|
75
|
+
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.8`. 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
|
|
|
@@ -204,7 +204,7 @@ Only GPT Responses requests keep `prompt_cache_key`; other Responses models omit
|
|
|
204
204
|
- The reasoning block opens with `▸ 启动 Claude Code CLI…` and then shows the CLI's version and model once it is up. When the gateway throttles or is busy (429, 503 `账号并发排队已满` / per-account wait queue full, and similar), the CLI retries up to 10 times on its own; each retry now shows as `↻ 网关返回 HTTP 503,CLI 自动重试 n/10` instead of looking hung. When the retries run out, the turn says PackyCode's Claude upstream is busy, not that the key is bad. A CLI that prints nothing for 2 minutes after starting is stopped and reported.
|
|
205
205
|
- The conversation and its images reach the CLI over stdin (`--input-format stream-json`), not argv: a Windows command line is length-limited and cannot carry an image. Images in the conversation are sent to Claude as images, at the HTTP route's request size (at most 2048×2048, about 1 MiB each; at most 20 images and 20 MiB per turn, dropping the oldest first). An image left out is named with its read-only local copy, which the CLI can open with Read.
|
|
206
206
|
- Every run carries `--append-system-prompt` (the delegation note). In `-p` mode the official CLI introduces itself as "You are Claude Code, Anthropic's official CLI for Claude …" when it has appended instructions and as "You are a Claude agent, built on Anthropic's Claude Agent SDK." when it has none; the latter was refused upstream as not coming from the official CLI (403 `only accepts requests from the official Claude Code CLI`). That is the official CLI's own behavior; the plugin does not rewrite its system prompt.
|
|
207
|
-
- While the session model is a Claude model, titles are generated with `claude-haiku-5-5`, read-only, in a temporary directory, instead of a full Opus turn.
|
|
207
|
+
- While the session model is a Claude model, titles are generated with `claude-haiku-5-5`, read-only, in a temporary directory, instead of a full Opus turn. Inside the CLI, DSH's title rules are only an appendix to the CLI's own system prompt, and a very short message such as 在吗 used to come back as English JSON (`{"title": "Casual greeting check-in"}`). The prompt therefore ends by restating them: the bare title as plain text, in the language of the messages (Chinese, Japanese and Korean are named by their script). A reply that still arrives in JSON, a code fence, quotes or after a "Title:" label is unwrapped before DSH reads it. A reply of several lines (the model wrote out its thinking or an explanation, so the title line cannot be told) or a bare placeholder such as "Session title" yields no title, and DSH keeps its default title from the first message.
|
|
208
208
|
- While the session model is `gpt-6-astra`, titles are generated with `gpt-6.1-sol` (same Codex group); the turns themselves stay on Astra. Other models title their own sessions.
|
|
209
209
|
- The `claude_code` tool can delegate one task to the local CLI while the session model is `gpt-6.1-sol`, `gpt-6-astra`, or `deepseek-flash`. The requested `mode` cannot exceed what the session preset allows. While the session is in plan mode the delegation plans too, and returns the plan text to the parent model.
|
|
210
210
|
- An effort level the model does not support is rejected before the request is sent. Do not edit a model's effort map casually.
|
|
@@ -213,7 +213,7 @@ Only GPT Responses requests keep `prompt_cache_key`; other Responses models omit
|
|
|
213
213
|
|
|
214
214
|
## Update and uninstall
|
|
215
215
|
|
|
216
|
-
**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. 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.2.
|
|
216
|
+
**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. 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.2.8`); without it you may get a version that is merely the newest one published at least 24 hours ago.
|
|
217
217
|
|
|
218
218
|
**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.
|
|
219
219
|
|
|
@@ -229,7 +229,7 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
229
229
|
|
|
230
230
|
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.
|
|
231
231
|
|
|
232
|
-
Recovery: **uninstall** the plugin on the **Plugins** page, **install** it again with the version in the package name (for example `dsh-packycode-api@1.2.
|
|
232
|
+
Recovery: **uninstall** the plugin on the **Plugins** page, **install** it again with the version in the package name (for example `dsh-packycode-api@1.2.8`), 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.
|
|
233
233
|
|
|
234
234
|
**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:
|
|
235
235
|
|
|
@@ -257,13 +257,15 @@ Recovery: quit and reopen the desktop application, and the old version works. Th
|
|
|
257
257
|
|
|
258
258
|
**The CLI is installed, but Claude still fails?** A host launched from Finder or the Start menu usually has a shorter `PATH` than a terminal. The plugin also checks the official install location, common package-manager directories, and `npm prefix -g`. If it still cannot find the binary, set `claudeCliPath` or click **Check again**. After a Host change or a reinstall, quit and reopen the desktop application.
|
|
259
259
|
|
|
260
|
+
**Replies stop at the output token limit (已达到输出 token 上限), or requests keep failing — how do I report it?** Open **Settings → PackyCode → Diagnostics**, pick the session (the 20 most recent, newest first, labelled with their folder name), click **Generate diagnostics** and then **Copy**, and paste the text into an [Issue](https://github.com/BaronCyrus/dsh-packycode-api/issues) or send it to the plugin author. It holds settings and numbers only: the plugin version, the session id and start time, each key's position and groups (no labels, no keys), each request's model, reasoning effort and output limit, the latest steps' token usage and finish reasons, and retry and failure codes (messages redacted and shortened). It contains no conversation text, file paths or keys. Where copying is unavailable, the text is selected instead; press ⌘C or Ctrl+C.
|
|
261
|
+
|
|
260
262
|
## Security and scope
|
|
261
263
|
|
|
262
264
|
This is a community plugin, not affiliated with or endorsed by DeepSeek or PackyCode. Model requests go to the PackyCode host you configured. Keeping credentials out of browser responses does not mean inference is local. Claude models also start the official CLI on this device.
|
|
263
265
|
|
|
264
266
|
Keys stay in the local DSH credential file (`~/.dsh/.credentials.yaml`, owner-only as enforced by DSH). They are not written into settings, logs, or the transcript. Balance requests run only on the host, with a timeout and redirects refused.
|
|
265
267
|
|
|
266
|
-
Use [Issues](https://github.com/BaronCyrus/dsh-packycode-api/issues) for ordinary bugs. Never include a key, token, or credential-file contents. See [SECURITY.md](SECURITY.md).
|
|
268
|
+
Use [Issues](https://github.com/BaronCyrus/dsh-packycode-api/issues) for ordinary bugs; for a failing request, attach the text from the **Diagnostics** card. Never include a key, token, or credential-file contents. See [SECURITY.md](SECURITY.md).
|
|
267
269
|
|
|
268
270
|
## Local development
|
|
269
271
|
|
package/README.md
CHANGED
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
打开桌面应用的**插件**页,点击**添加插件**,在**包名或地址**中填入**带版本号**的包名:
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
|
-
dsh-packycode-api@1.2.
|
|
51
|
+
dsh-packycode-api@1.2.8
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
在**安装源**中选择**官方源**,然后点击**安装**。`1.2.
|
|
54
|
+
在**安装源**中选择**官方源**,然后点击**安装**。`1.2.8` 是写这份说明时的最新版,当前最新版本号以 [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.2.7
|
|
|
72
72
|
|
|
73
73
|
桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
|
|
74
74
|
|
|
75
|
-
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.
|
|
75
|
+
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.2.8`。不要对 `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
|
|
|
@@ -204,7 +204,7 @@ Responses 请求只有 GPT 模型保留 `prompt_cache_key`,其他 Responses
|
|
|
204
204
|
- 思考区第一行是「▸ 启动 Claude Code CLI…」,CLI 起来后显示版本和模型。网关限流或繁忙(429、503 `账号并发排队已满` 等)时,CLI 会自己重试最多 10 次,每次重试都会显示成 `↻ 网关返回 HTTP 503,CLI 自动重试 n/10`,不再像卡住。重试用完仍失败时,会说明是 PackyCode 的 Claude 上游繁忙,不是 Key 的问题。CLI 启动后 2 分钟内没有任何输出会被停止并报告原因。
|
|
205
205
|
- 对话记录和图片通过 stdin 交给 CLI(`--input-format stream-json`),不走命令行参数:Windows 命令行长度有上限,而且命令行带不了图片。对话里的图片会以图片形式发给 Claude(与 HTTP 路由相同的压缩规格:每张不超过 2048×2048、约 1 MiB;一回合最多 20 张、合计 20 MiB,超出时先舍弃最早的)。没随消息发出的图片会写明本机只读副本的路径,CLI 需要时可以用 Read 读取。
|
|
206
206
|
- 每次运行都带 `--append-system-prompt`(委派说明)。在 `-p` 模式下,官方 CLI 有追加说明时会以 "You are Claude Code, Anthropic's official CLI for Claude …" 自我介绍,没有时是 "You are a Claude agent, built on Anthropic's Claude Agent SDK.";后者曾被上游当成非官方 CLI 拒绝(403 `only accepts requests from the official Claude Code CLI`)。这是官方 CLI 自己的行为,插件没有改写它的系统提示词。
|
|
207
|
-
- 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。
|
|
207
|
+
- 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。DSH 的标题要求在 CLI 里只是追加在它自己系统提示词后面的一段,「在吗」这类很短的消息曾被起成 `{"title": "Casual greeting check-in"}` 这样的英文 JSON。所以插件会在提示词末尾再强调一次:只回标题纯文本,用消息原文的语言(中文、日文、韩文按文字直接点名)。回复里仍带 JSON、代码块、引号或「Title:」前缀时,插件会先剥掉再交给 DSH。如果回复有好几行(模型把思考或解释也写了出来,分不清哪行是标题),或者只是「对话标题」这类占位词,就不给标题,DSH 会保留按首条消息生成的默认标题。
|
|
208
208
|
- 会话跑在 `gpt-6-astra` 上时,标题改由 `gpt-6.1-sol` 生成(同一个 Codex 分组);对话本身仍然用 Astra。其它模型的标题由会话模型自己生成。
|
|
209
209
|
- 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol`、`gpt-6-astra` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限;会话处于计划模式时,这个委派也只规划,并把计划正文交回父模型。
|
|
210
210
|
- 声明了模型不支持的推理档位会被 harness 在发请求前拒绝,不要随手改档位映射。
|
|
@@ -213,7 +213,7 @@ Responses 请求只有 GPT 模型保留 `prompt_cache_key`,其他 Responses
|
|
|
213
213
|
|
|
214
214
|
## 更新与卸载
|
|
215
215
|
|
|
216
|
-
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装,同样要填写带版本号的包名(例如 `dsh-packycode-api@1.2.
|
|
216
|
+
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装,同样要填写带版本号的包名(例如 `dsh-packycode-api@1.2.8`),否则装上的可能是发布满 24 小时的旧版。
|
|
217
217
|
|
|
218
218
|
**从本地路径安装切回官方源时,必须先卸载。** 「更新」是 `pnpm add`,它只重写包目录里的文件,不会删除更早一次本地路径安装留在包目录内的 `node_modules`;那份旧依赖会继续挡住 DSH 自带的包,插件行加载失败且看不出原因。「卸载」执行的是 `pnpm remove`,会删掉整个包目录,重装才是干净的。用 `node scripts/install-check.mjs` 可以确认当前 profile 是否处于这个状态。
|
|
219
219
|
|
|
@@ -229,7 +229,7 @@ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
|
|
|
229
229
|
|
|
230
230
|
有输出就是残留:从 npm 安装的包不会自带 `node_modules`,这一份通常来自更早一次「本地路径」安装(`file:` 或直接写路径,而不是 `link:`)。它位于 profile 目录内部,而 DSH 解析插件导入时优先使用 profile 内的副本,所以插件拿到的仍是那份旧依赖 —— 例如 `@deepseek-ai/schemastery` 3.18.2 没有 `Schema.volatile()`,模块在导入阶段就抛错。导入失败时 Loader 不会为这一行创建 fiber,客户端 bundle 也不会被组合,于是模型路由、RPC 通道和设置页全都不会出现。
|
|
231
231
|
|
|
232
|
-
修复:到**插件**页**卸载**本插件,再用带版本号的包名重新**安装**(例如 `dsh-packycode-api@1.2.
|
|
232
|
+
修复:到**插件**页**卸载**本插件,再用带版本号的包名重新**安装**(例如 `dsh-packycode-api@1.2.8`),然后完全退出并重新打开桌面应用。卸载执行的是 `pnpm remove`,会删掉整个包目录;直接重装或点「更新」只重写文件,不会清掉这份残留。也可以手动删掉上面那个 `node_modules` 再重启。
|
|
233
233
|
|
|
234
234
|
**首次安装后,设置页每张卡片都显示 `transport failure for /packycode-api/…: HTTP 405`?** 实际装上的是 1.1.6 或更早的版本,通常是因为安装时只填了包名:安装提示显示的是最新版,pnpm 实际装的却是发布满 24 小时的旧版(见「安装插件」)。这些旧版要等 DSH 重启、重建它的 `connection` 服务之后,才能注册设置通道。所以首次安装后 `/packycode-api` 通道还不存在,请求落到静态页面,返回 405;模型路由和设置页本身照常加载。1.1.7 起首次安装不需要重启。先确认装上的版本:
|
|
235
235
|
|
|
@@ -257,13 +257,15 @@ grep '"version"' ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/package.
|
|
|
257
257
|
|
|
258
258
|
**装了 CLI 还是不能用 Claude?** 从 Finder 或开始菜单启动时,`PATH` 往往比终端短。插件会再查官方安装位置、常见包管理器目录和 `npm prefix -g`。仍找不到就在设置里填 `claudeCliPath`,或点「重新检测」。改的是 Host 或重新安装了插件时,要完全退出并重启桌面应用。
|
|
259
259
|
|
|
260
|
+
**回答被截断(「已达到输出 token 上限」)、请求反复失败,怎么反馈?** 打开 **设置 → PackyCode → 诊断信息**,选出问题的会话(最近 20 个,按时间倒序,标着所在文件夹名),点「生成诊断信息」再点「复制」,把内容贴到 [Issue](https://github.com/BaronCyrus/dsh-packycode-api/issues) 或发给插件作者。内容只有设置和数字:插件版本、会话 id 与创建时间、每个 Key 的序号与分组(不含备注名和 Key 本身)、每次请求用的模型、推理档位和输出上限、最近若干步的 token 用量与结束原因,以及重试和失败的错误码(错误信息已脱敏并截短)。不含对话内容、文件路径和 Key。复制不可用时,文本会被全选,按 ⌘C 或 Ctrl+C 即可。
|
|
261
|
+
|
|
260
262
|
## 安全边界
|
|
261
263
|
|
|
262
264
|
本项目是社区插件,与 DeepSeek、PackyCode 无隶属或背书关系。模型请求会发到你配置的 PackyCode 接口;凭据不返回浏览器,不代表推理在本地完成。Claude 模型还会在本机启动官方 CLI。
|
|
263
265
|
|
|
264
266
|
Key 存在本机 DSH 凭据文件(`~/.dsh/.credentials.yaml`,权限由 DSH 设为仅所有者可读),不进设置文档、日志或对话记录。余额请求只在宿主侧发出,带超时并禁止重定向。
|
|
265
267
|
|
|
266
|
-
普通问题请提交 [Issue](https://github.com/BaronCyrus/dsh-packycode-api/issues)
|
|
268
|
+
普通问题请提交 [Issue](https://github.com/BaronCyrus/dsh-packycode-api/issues),请求出错时可附上「诊断信息」卡片生成的内容。不要附带 Key、令牌或凭据文件内容。更多说明见 [SECURITY.md](SECURITY.md)。
|
|
267
269
|
|
|
268
270
|
## 本地开发
|
|
269
271
|
|
package/lib/client.js
CHANGED
|
@@ -185,6 +185,20 @@ window.__ModuleLoader__.load({
|
|
|
185
185
|
versionRestartHint: "插件无法安全地重启桌面应用。请完全退出 DSH,再重新打开。",
|
|
186
186
|
versionRefresh: "刷新界面",
|
|
187
187
|
versionLater: "稍后",
|
|
188
|
+
diagTitle: "诊断信息",
|
|
189
|
+
diagIntro: "遇到回答被截断(已达到输出 token 上限)、请求反复失败等问题时,选择出问题的会话,生成诊断信息并复制给插件作者。",
|
|
190
|
+
diagSession: "会话",
|
|
191
|
+
diagGenerate: "生成诊断信息",
|
|
192
|
+
diagGenerating: "正在生成…",
|
|
193
|
+
diagReport: "诊断信息内容",
|
|
194
|
+
diagCopy: "复制",
|
|
195
|
+
diagCopied: "已复制",
|
|
196
|
+
diagSelected: "已选中全部内容,请按 ⌘C 或 Ctrl+C 复制。",
|
|
197
|
+
diagPrivacy: "只包含模型、推理档位、输出上限、每一步的 token 用量和错误码,不含对话内容、文件路径和 Key。",
|
|
198
|
+
diagNoSessions: "还没有会话。",
|
|
199
|
+
diagLive: "进行中",
|
|
200
|
+
diagNoFolder: "无目录",
|
|
201
|
+
diagError: "生成诊断信息失败",
|
|
188
202
|
searchTitle: "网页搜索",
|
|
189
203
|
searchScope: "DSH 全局设置",
|
|
190
204
|
searchIntro: "DSH 的 web_search 和 Claude CLI 的 MCP 搜索可使用这里选择的 DeepSeek/GPT 后端。每次搜索是独立的模型调用,用插件保存的 Key 计费。",
|
|
@@ -362,6 +376,20 @@ window.__ModuleLoader__.load({
|
|
|
362
376
|
versionRestartHint: "The plugin cannot safely restart the desktop application. Quit DSH completely, then open it again.",
|
|
363
377
|
versionRefresh: "Refresh page",
|
|
364
378
|
versionLater: "Later",
|
|
379
|
+
diagTitle: "Diagnostics",
|
|
380
|
+
diagIntro: "If replies are cut off (\"output token limit reached\") or requests keep failing, pick the affected session, generate its diagnostics and send them to the plugin author.",
|
|
381
|
+
diagSession: "Session",
|
|
382
|
+
diagGenerate: "Generate diagnostics",
|
|
383
|
+
diagGenerating: "Generating…",
|
|
384
|
+
diagReport: "Diagnostics text",
|
|
385
|
+
diagCopy: "Copy",
|
|
386
|
+
diagCopied: "Copied",
|
|
387
|
+
diagSelected: "All text is selected; press ⌘C or Ctrl+C to copy.",
|
|
388
|
+
diagPrivacy: "Contains only models, reasoning effort, output limits, per-step token usage and error codes — no conversation text, file paths or keys.",
|
|
389
|
+
diagNoSessions: "No sessions yet.",
|
|
390
|
+
diagLive: "open",
|
|
391
|
+
diagNoFolder: "no folder",
|
|
392
|
+
diagError: "Could not generate diagnostics",
|
|
365
393
|
searchTitle: "Web search",
|
|
366
394
|
searchScope: "DSH-wide setting",
|
|
367
395
|
searchIntro: "DSH’s web_search and Claude CLI’s MCP search can use the DeepSeek/GPT backend selected here. Each search is a separate model call billed to the plugin-stored keys.",
|
|
@@ -429,6 +457,8 @@ window.__ModuleLoader__.load({
|
|
|
429
457
|
.pkc-input:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
430
458
|
.pkc-input::placeholder{color:var(--dsw-alias-label-caption)}
|
|
431
459
|
.pkc-input:disabled{opacity:.5}
|
|
460
|
+
.pkc-diag-session{flex:1 1 260px;max-width:480px;cursor:pointer}
|
|
461
|
+
.pkc-diag-text{width:100%;height:auto;min-height:220px;padding:8px 10px;line-height:18px;resize:vertical;white-space:pre;overflow:auto}
|
|
432
462
|
.pkc-image-choice{flex:none;width:100%;max-width:300px;cursor:pointer}
|
|
433
463
|
.pkc-image-shortcut{display:inline-flex;align-items:center;gap:8px}
|
|
434
464
|
.pkc-quota{position:relative;display:inline-flex;align-items:center}
|
|
@@ -1721,6 +1751,173 @@ window.__ModuleLoader__.load({
|
|
|
1721
1751
|
});
|
|
1722
1752
|
}
|
|
1723
1753
|
//#endregion
|
|
1754
|
+
//#region src/client-diagnostics.jsx
|
|
1755
|
+
/**
|
|
1756
|
+
* The settings card that turns one session into a numbers-only diagnosis a
|
|
1757
|
+
* user can copy into a bug report. The host builds the text; this card only
|
|
1758
|
+
* picks the session, shows the result and copies it.
|
|
1759
|
+
*
|
|
1760
|
+
* @module dsh-packycode-api/client-diagnostics
|
|
1761
|
+
*/
|
|
1762
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1763
|
+
/** The picker's local date and time; the report itself keeps UTC ISO times. */
|
|
1764
|
+
function localTime(iso) {
|
|
1765
|
+
const date = new Date(iso);
|
|
1766
|
+
if (Number.isNaN(date.getTime())) return "—";
|
|
1767
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
1768
|
+
}
|
|
1769
|
+
const messageOf = (failure) => failure instanceof Error ? failure.message : String(failure);
|
|
1770
|
+
function DiagnosticsCard({ call, t }) {
|
|
1771
|
+
const [sessions, setSessions] = react.useState();
|
|
1772
|
+
const [selected, setSelected] = react.useState("");
|
|
1773
|
+
const [report, setReport] = react.useState();
|
|
1774
|
+
const [busy, setBusy] = react.useState(false);
|
|
1775
|
+
const [error, setError] = react.useState();
|
|
1776
|
+
const [copied, setCopied] = react.useState();
|
|
1777
|
+
const area = react.useRef(null);
|
|
1778
|
+
const mounted = react.useRef(true);
|
|
1779
|
+
react.useEffect(() => () => {
|
|
1780
|
+
mounted.current = false;
|
|
1781
|
+
}, []);
|
|
1782
|
+
const loadSessions = react.useCallback(() => {
|
|
1783
|
+
setError(void 0);
|
|
1784
|
+
return call("diagnostics/sessions").then((value) => {
|
|
1785
|
+
if (!mounted.current) return;
|
|
1786
|
+
const list = Array.isArray(value?.sessions) ? value.sessions : [];
|
|
1787
|
+
setSessions(list);
|
|
1788
|
+
setSelected((current) => list.some((item) => item.id === current) ? current : list[0]?.id ?? "");
|
|
1789
|
+
}).catch((failure) => {
|
|
1790
|
+
if (mounted.current) setError(messageOf(failure));
|
|
1791
|
+
});
|
|
1792
|
+
}, [call]);
|
|
1793
|
+
react.useEffect(() => {
|
|
1794
|
+
loadSessions();
|
|
1795
|
+
}, [loadSessions]);
|
|
1796
|
+
const generate = () => {
|
|
1797
|
+
if (selected === "") return;
|
|
1798
|
+
setBusy(true);
|
|
1799
|
+
setError(void 0);
|
|
1800
|
+
setCopied(void 0);
|
|
1801
|
+
call("diagnostics/session", { sessionId: selected }).then((value) => {
|
|
1802
|
+
if (mounted.current) setReport(JSON.stringify(value, null, 2));
|
|
1803
|
+
}).catch((failure) => {
|
|
1804
|
+
if (mounted.current) setError(messageOf(failure));
|
|
1805
|
+
}).finally(() => {
|
|
1806
|
+
if (mounted.current) setBusy(false);
|
|
1807
|
+
});
|
|
1808
|
+
};
|
|
1809
|
+
/** Copy through the clipboard API, or select everything for a manual copy. */
|
|
1810
|
+
const copy = () => {
|
|
1811
|
+
if (report === void 0) return;
|
|
1812
|
+
const selectAll = () => {
|
|
1813
|
+
area.current?.focus?.();
|
|
1814
|
+
area.current?.select?.();
|
|
1815
|
+
if (mounted.current) setCopied("selected");
|
|
1816
|
+
};
|
|
1817
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
1818
|
+
if (typeof clipboard?.writeText !== "function") {
|
|
1819
|
+
selectAll();
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
clipboard.writeText(report).then(() => {
|
|
1823
|
+
if (mounted.current) setCopied("copied");
|
|
1824
|
+
}, selectAll);
|
|
1825
|
+
};
|
|
1826
|
+
const label = (item) => [
|
|
1827
|
+
localTime(item.createdAt),
|
|
1828
|
+
item.folder ?? t("diagNoFolder"),
|
|
1829
|
+
item.live ? t("diagLive") : void 0
|
|
1830
|
+
].filter(Boolean).join(" · ");
|
|
1831
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1832
|
+
className: "pkc-card",
|
|
1833
|
+
children: [
|
|
1834
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1835
|
+
className: "pkc-head",
|
|
1836
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1837
|
+
className: "pkc-title",
|
|
1838
|
+
children: t("diagTitle")
|
|
1839
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1840
|
+
className: "pkc-actions",
|
|
1841
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1842
|
+
className: "pkc-btn",
|
|
1843
|
+
type: "button",
|
|
1844
|
+
disabled: busy,
|
|
1845
|
+
onClick: () => {
|
|
1846
|
+
loadSessions();
|
|
1847
|
+
},
|
|
1848
|
+
children: t("refresh")
|
|
1849
|
+
})
|
|
1850
|
+
})]
|
|
1851
|
+
}),
|
|
1852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1853
|
+
className: "pkc-note",
|
|
1854
|
+
children: t("diagIntro")
|
|
1855
|
+
}),
|
|
1856
|
+
sessions !== void 0 && sessions.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1857
|
+
className: "pkc-note",
|
|
1858
|
+
children: t("diagNoSessions")
|
|
1859
|
+
}),
|
|
1860
|
+
sessions !== void 0 && sessions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1861
|
+
className: "pkc-form",
|
|
1862
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1863
|
+
className: "pkc-input pkc-diag-session",
|
|
1864
|
+
"aria-label": t("diagSession"),
|
|
1865
|
+
value: selected,
|
|
1866
|
+
disabled: busy,
|
|
1867
|
+
onChange: (event) => {
|
|
1868
|
+
setSelected(event.target.value);
|
|
1869
|
+
setReport(void 0);
|
|
1870
|
+
setCopied(void 0);
|
|
1871
|
+
},
|
|
1872
|
+
children: sessions.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1873
|
+
value: item.id,
|
|
1874
|
+
children: label(item)
|
|
1875
|
+
}, item.id))
|
|
1876
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1877
|
+
className: "pkc-btn",
|
|
1878
|
+
"data-variant": "primary",
|
|
1879
|
+
"data-size": "md",
|
|
1880
|
+
type: "button",
|
|
1881
|
+
disabled: busy || selected === "",
|
|
1882
|
+
onClick: generate,
|
|
1883
|
+
children: busy ? t("diagGenerating") : t("diagGenerate")
|
|
1884
|
+
})]
|
|
1885
|
+
}),
|
|
1886
|
+
report !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1887
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1888
|
+
ref: area,
|
|
1889
|
+
className: "pkc-input pkc-mono pkc-diag-text",
|
|
1890
|
+
readOnly: true,
|
|
1891
|
+
rows: 14,
|
|
1892
|
+
value: report,
|
|
1893
|
+
"aria-label": t("diagReport")
|
|
1894
|
+
}),
|
|
1895
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1896
|
+
className: "pkc-form",
|
|
1897
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1898
|
+
className: "pkc-btn",
|
|
1899
|
+
type: "button",
|
|
1900
|
+
onClick: copy,
|
|
1901
|
+
children: copied === "copied" ? t("diagCopied") : t("diagCopy")
|
|
1902
|
+
}), copied === "selected" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1903
|
+
className: "pkc-note",
|
|
1904
|
+
children: t("diagSelected")
|
|
1905
|
+
})]
|
|
1906
|
+
}),
|
|
1907
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1908
|
+
className: "pkc-note",
|
|
1909
|
+
children: t("diagPrivacy")
|
|
1910
|
+
})
|
|
1911
|
+
] }),
|
|
1912
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1913
|
+
className: "pkc-error",
|
|
1914
|
+
role: "alert",
|
|
1915
|
+
children: `${t("diagError")}: ${error}`
|
|
1916
|
+
})
|
|
1917
|
+
]
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
//#endregion
|
|
1724
1921
|
//#region src/client.jsx
|
|
1725
1922
|
/**
|
|
1726
1923
|
* Browser half: the PackyCode settings page and the composer balance chip.
|
|
@@ -2736,6 +2933,10 @@ window.__ModuleLoader__.load({
|
|
|
2736
2933
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionCard, {
|
|
2737
2934
|
call,
|
|
2738
2935
|
t
|
|
2936
|
+
}),
|
|
2937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagnosticsCard, {
|
|
2938
|
+
call,
|
|
2939
|
+
t
|
|
2739
2940
|
})
|
|
2740
2941
|
]
|
|
2741
2942
|
});
|
package/lib/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdtempSync, op
|
|
|
8
8
|
import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
11
|
-
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
12
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
13
13
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
14
14
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
15
15
|
import { lazyStream } from "@earendil-works/pi-ai/api/lazy";
|
|
@@ -1334,7 +1334,9 @@ function pickKeyForModel(modelId, active, usable, covers) {
|
|
|
1334
1334
|
*
|
|
1335
1335
|
* A session-title request is not a delegation: it runs on the small model,
|
|
1336
1336
|
* read-only and outside the workspace, with the title instructions appended,
|
|
1337
|
-
* so naming a session does not cost an Opus agent turn.
|
|
1337
|
+
* so naming a session does not cost an Opus agent turn. Under the CLI's own
|
|
1338
|
+
* system prompt those instructions are easily outweighed, so the prompt ends
|
|
1339
|
+
* by restating them and the answer is unwrapped before DSH reads it.
|
|
1338
1340
|
*
|
|
1339
1341
|
* @module dsh-packycode-api/claude-adapter
|
|
1340
1342
|
*/
|
|
@@ -1796,6 +1798,87 @@ function renderAuxiliaryPrompt(options) {
|
|
|
1796
1798
|
return (Array.isArray(options?.messages) ? options.messages : []).filter((message) => message !== null && typeof message === "object" && message.role !== "system").flatMap((message) => Array.isArray(message.content) ? message.content : []).filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n\n").trim();
|
|
1797
1799
|
}
|
|
1798
1800
|
/**
|
|
1801
|
+
* Languages named by their script. The caller frames a title request in
|
|
1802
|
+
* English, so text in one of these scripts is the user's own.
|
|
1803
|
+
*/
|
|
1804
|
+
const TITLE_SCRIPTS = Object.freeze([
|
|
1805
|
+
["Korean", /\p{Script=Hangul}/u],
|
|
1806
|
+
["Japanese", /[\p{Script=Hiragana}\p{Script=Katakana}]/u],
|
|
1807
|
+
["Chinese", /\p{Script=Han}/u]
|
|
1808
|
+
]);
|
|
1809
|
+
/**
|
|
1810
|
+
* The prompt of a CLI title request: the caller's framed input, verbatim,
|
|
1811
|
+
* then the two rules the small model breaks under the CLI's own system
|
|
1812
|
+
* prompt. There the caller's title instructions are only an appendix and its
|
|
1813
|
+
* input is English framing around a JSON array, so a short message came back
|
|
1814
|
+
* as JSON or in English (`{"title": "Session greeting check"}` for 在吗).
|
|
1815
|
+
* @param options - the assembled title request.
|
|
1816
|
+
* @returns the prompt text.
|
|
1817
|
+
*/
|
|
1818
|
+
function renderTitlePrompt(options) {
|
|
1819
|
+
const framed = renderAuxiliaryPrompt(options);
|
|
1820
|
+
const language = TITLE_SCRIPTS.find(([, script]) => script.test(framed))?.[0];
|
|
1821
|
+
return [
|
|
1822
|
+
framed,
|
|
1823
|
+
"",
|
|
1824
|
+
"Reply with the title alone, as plain text on one line: not JSON, not quoted, with no label such as \"Title:\".",
|
|
1825
|
+
language === void 0 ? "Write it in the language of the messages' text, not in the language of these instructions." : `Write it in ${language}, the language of the messages' text, not in the language of these instructions.`
|
|
1826
|
+
].join("\n");
|
|
1827
|
+
}
|
|
1828
|
+
/** Pairs a reply wraps a whole title in. Book-title marks belong to a title. */
|
|
1829
|
+
const TITLE_WRAPPERS = Object.freeze([
|
|
1830
|
+
["\"", "\""],
|
|
1831
|
+
["'", "'"],
|
|
1832
|
+
["“", "”"],
|
|
1833
|
+
["‘", "’"],
|
|
1834
|
+
["「", "」"],
|
|
1835
|
+
["『", "』"],
|
|
1836
|
+
["```", "```"],
|
|
1837
|
+
["`", "`"],
|
|
1838
|
+
["**", "**"],
|
|
1839
|
+
["«", "»"]
|
|
1840
|
+
]);
|
|
1841
|
+
const TITLE_LABEL = /^[*"'`]*(?:(?:session\s+)?title|(?:会话)?标题)[*"'`]*\s*[::]\**\s*/iu;
|
|
1842
|
+
/** A reply that names the field instead of filling it. */
|
|
1843
|
+
const TITLE_PLACEHOLDER = /^(?:(?:(?:session|conversation|chat)\s+)?title|(?:会话|对话|聊天)?标题)$/iu;
|
|
1844
|
+
/** The title inside a JSON reply: a string, or an object's `title` string. */
|
|
1845
|
+
function jsonTitle(text) {
|
|
1846
|
+
if (!/^[{"]/u.test(text)) return void 0;
|
|
1847
|
+
let value;
|
|
1848
|
+
try {
|
|
1849
|
+
value = JSON.parse(text);
|
|
1850
|
+
} catch {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (typeof value === "string") return value.trim();
|
|
1854
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1855
|
+
const field = Object.keys(value).find((key) => key.toLowerCase() === "title");
|
|
1856
|
+
return field !== void 0 && typeof value[field] === "string" ? value[field].trim() : void 0;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Recover the bare title from a reply that wrapped it: in a code fence, as a
|
|
1860
|
+
* JSON object's `title` or a JSON string, after a "Title:" label, or in
|
|
1861
|
+
* quotes. Anything else comes back trimmed.
|
|
1862
|
+
*
|
|
1863
|
+
* Some replies hold no usable title: several lines (the model wrote out its
|
|
1864
|
+
* thinking or an explanation, and which line is the title cannot be told), a
|
|
1865
|
+
* placeholder such as "Session title", or an empty wrapper. Those yield no
|
|
1866
|
+
* text, so DSH keeps its own fallback title, the first message's words.
|
|
1867
|
+
* @param text - a title request's whole text answer.
|
|
1868
|
+
* @returns the title text, or '' when the reply holds none.
|
|
1869
|
+
*/
|
|
1870
|
+
function unwrapTitle(text) {
|
|
1871
|
+
const raw = typeof text === "string" ? text.trim() : "";
|
|
1872
|
+
const body = /^```[^\n]*\n([\s\S]*?)\n?```$/u.exec(raw)?.[1].trim() ?? raw;
|
|
1873
|
+
const lines = body.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
1874
|
+
const json = jsonTitle(body) ?? jsonTitle(lines[0] ?? "");
|
|
1875
|
+
if (json === void 0 && lines.length > 1) return "";
|
|
1876
|
+
let title = (json ?? lines[0] ?? "").replace(TITLE_LABEL, "");
|
|
1877
|
+
const wrapper = TITLE_WRAPPERS.find(([open, close]) => title.length > open.length + close.length && title.startsWith(open) && title.endsWith(close));
|
|
1878
|
+
if (wrapper !== void 0) title = title.slice(wrapper[0].length, title.length - wrapper[1].length).trim();
|
|
1879
|
+
return TITLE_PLACEHOLDER.test(title) ? "" : title;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1799
1882
|
* Emit text and reasoning as well-formed harness blocks.
|
|
1800
1883
|
*
|
|
1801
1884
|
* The harness's stream grammar requires every delta to address an open block
|
|
@@ -1948,14 +2031,15 @@ function retryNote(event) {
|
|
|
1948
2031
|
}
|
|
1949
2032
|
/**
|
|
1950
2033
|
* How one request runs on the CLI: a title request on the small model with
|
|
1951
|
-
* the caller's instructions appended, anything else as a
|
|
2034
|
+
* the caller's instructions appended and restated, anything else as a
|
|
2035
|
+
* delegated turn.
|
|
1952
2036
|
*
|
|
1953
2037
|
* The CLI's own system prompt and tool set stay in place even for a title:
|
|
1954
2038
|
* they are part of what the gateway sees from the official client.
|
|
1955
2039
|
*/
|
|
1956
2040
|
async function cliCall(options, { titleModel, cwdOf, modeOf, attachmentsOf, imageLimits }) {
|
|
1957
2041
|
if (options?.purpose === "session-title") return {
|
|
1958
|
-
prompt:
|
|
2042
|
+
prompt: renderTitlePrompt(options),
|
|
1959
2043
|
model: titleModel,
|
|
1960
2044
|
mode: "read-only",
|
|
1961
2045
|
enableSearch: false,
|
|
@@ -2040,6 +2124,7 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
|
|
|
2040
2124
|
}
|
|
2041
2125
|
async function* cliStream(options) {
|
|
2042
2126
|
const quiet = options?.purpose === "session-title";
|
|
2127
|
+
let titleText = "";
|
|
2043
2128
|
const blocks = createBlockWriter();
|
|
2044
2129
|
if (!quiet) yield* blocks.note("▸ 启动 Claude Code CLI…\n");
|
|
2045
2130
|
let call;
|
|
@@ -2112,10 +2197,12 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
|
|
|
2112
2197
|
if (!quiet) yield* blocks.note(` ↻ ${retryNote(event)}\n`);
|
|
2113
2198
|
break;
|
|
2114
2199
|
case "block":
|
|
2200
|
+
if (quiet && event.kind === "text") titleText = "";
|
|
2115
2201
|
blocks.boundary(event.kind);
|
|
2116
2202
|
break;
|
|
2117
2203
|
case "text":
|
|
2118
|
-
|
|
2204
|
+
if (quiet) titleText += event.text;
|
|
2205
|
+
else yield* blocks.write("text", event.text);
|
|
2119
2206
|
break;
|
|
2120
2207
|
case "reasoning":
|
|
2121
2208
|
yield* blocks.write("reasoning", event.text);
|
|
@@ -2150,6 +2237,7 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
|
|
|
2150
2237
|
};
|
|
2151
2238
|
return;
|
|
2152
2239
|
}
|
|
2240
|
+
if (quiet) yield* blocks.write("text", unwrapTitle(titleText));
|
|
2153
2241
|
yield* blocks.close();
|
|
2154
2242
|
yield {
|
|
2155
2243
|
type: "usage",
|
|
@@ -5439,6 +5527,221 @@ function createClaudeCodeTool({ runner, modeCeiling, planActive }) {
|
|
|
5439
5527
|
});
|
|
5440
5528
|
}
|
|
5441
5529
|
//#endregion
|
|
5530
|
+
//#region src/diagnostics.js
|
|
5531
|
+
/**
|
|
5532
|
+
* A support summary of one DSH session, for reports such as "已达到输出 token
|
|
5533
|
+
* 上限" or failing requests: which route, reasoning effort and output ceiling
|
|
5534
|
+
* each request used, how every model step ended and what it consumed, and
|
|
5535
|
+
* which retries and failures happened.
|
|
5536
|
+
*
|
|
5537
|
+
* The summary carries numbers and identifiers only. Message text, reasoning,
|
|
5538
|
+
* tool arguments and results, titles, paths and key material never enter it;
|
|
5539
|
+
* error messages are redacted and shortened. A user can paste it into a bug
|
|
5540
|
+
* report as it is.
|
|
5541
|
+
*
|
|
5542
|
+
* @module dsh-packycode-api/diagnostics
|
|
5543
|
+
*/
|
|
5544
|
+
const DIAGNOSTICS_FORMAT = "packycode-diagnostics/1";
|
|
5545
|
+
const SESSION_LIST_LIMIT = 20;
|
|
5546
|
+
const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
5547
|
+
const MESSAGE_CHARS = 160;
|
|
5548
|
+
/** How many of each record the summary keeps, newest last. */
|
|
5549
|
+
const DIAGNOSTICS_LIMITS = Object.freeze({
|
|
5550
|
+
requests: 8,
|
|
5551
|
+
truncated: 12,
|
|
5552
|
+
steps: 20,
|
|
5553
|
+
turns: 10,
|
|
5554
|
+
retries: 10,
|
|
5555
|
+
attempts: 10
|
|
5556
|
+
});
|
|
5557
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5558
|
+
/** Identifier-like strings only (routes, models, efforts, reason kinds, codes). */
|
|
5559
|
+
const word = (value) => typeof value === "string" && /^[\w.:/@[\]-]{1,80}$/u.test(value) ? value : void 0;
|
|
5560
|
+
const iso = (value) => finite(value) === void 0 ? void 0 : new Date(value).toISOString();
|
|
5561
|
+
const last = (list, count) => list.slice(Math.max(0, list.length - count));
|
|
5562
|
+
const compact = (object) => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
|
|
5563
|
+
const chunksOf = (stream) => Array.isArray(stream) ? stream.map((item) => item?.chunk).filter((chunk) => chunk !== null && typeof chunk === "object") : [];
|
|
5564
|
+
/** A provider or harness error message, redacted and shortened. */
|
|
5565
|
+
function safeMessage$1(text, secrets) {
|
|
5566
|
+
if (typeof text !== "string" || text === "") return void 0;
|
|
5567
|
+
const safe = redactSecrets(text, secrets).replace(/sk-[A-Za-z0-9_-]{8,}/gu, "sk-[redacted]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/giu, "Bearer [redacted]").replace(/\s+/gu, " ").trim();
|
|
5568
|
+
return safe.length > MESSAGE_CHARS ? `${safe.slice(0, MESSAGE_CHARS)}…` : safe;
|
|
5569
|
+
}
|
|
5570
|
+
function failureOf(reason, secrets) {
|
|
5571
|
+
const failure = reason?.failure ?? reason?.error;
|
|
5572
|
+
return failure === void 0 ? {} : {
|
|
5573
|
+
code: word(failure.code),
|
|
5574
|
+
message: safeMessage$1(failure.message, secrets)
|
|
5575
|
+
};
|
|
5576
|
+
}
|
|
5577
|
+
/**
|
|
5578
|
+
* Summarize one session's events.
|
|
5579
|
+
* @param events - the session's events in log order.
|
|
5580
|
+
* @param options - values to redact from error messages.
|
|
5581
|
+
* @returns the numbers-only summary body.
|
|
5582
|
+
*/
|
|
5583
|
+
function summarizeSessionEvents(events, { secrets = [] } = {}) {
|
|
5584
|
+
const requests = [];
|
|
5585
|
+
const steps = [];
|
|
5586
|
+
const turnEnds = [];
|
|
5587
|
+
const retries = [];
|
|
5588
|
+
const attempts = [];
|
|
5589
|
+
let turns = 0;
|
|
5590
|
+
let compactions = 0;
|
|
5591
|
+
for (const event of events ?? []) {
|
|
5592
|
+
const type = event?.type;
|
|
5593
|
+
const data = event?.data ?? {};
|
|
5594
|
+
if (typeof type !== "string") continue;
|
|
5595
|
+
if (/compact/u.test(type)) compactions += 1;
|
|
5596
|
+
if (type === "turn/start") turns += 1;
|
|
5597
|
+
else if (type === "request/header") {
|
|
5598
|
+
const config = data.header?.config ?? {};
|
|
5599
|
+
requests.push(compact({
|
|
5600
|
+
time: iso(event.time),
|
|
5601
|
+
reason: word(data.reason),
|
|
5602
|
+
provider: word(config.provider),
|
|
5603
|
+
model: word(config.model),
|
|
5604
|
+
reasoningEffort: word(config.reasoningEffort),
|
|
5605
|
+
maxTokens: finite(config.maxTokens),
|
|
5606
|
+
maxTokensFromAdapter: data.header?.adapterDefaults?.maxTokens === true ? true : void 0
|
|
5607
|
+
}));
|
|
5608
|
+
} else if (type === "assistant/message") {
|
|
5609
|
+
const chunks = chunksOf(data.stream);
|
|
5610
|
+
const usage = data.usage ?? chunks.find((chunk) => chunk.type === "usage")?.usage ?? {};
|
|
5611
|
+
const input = finite(usage.inputTokens);
|
|
5612
|
+
const cacheRead = finite(usage.cacheReadTokens);
|
|
5613
|
+
steps.push(compact({
|
|
5614
|
+
turn: finite(data.turn),
|
|
5615
|
+
step: finite(data.step),
|
|
5616
|
+
time: iso(event.time),
|
|
5617
|
+
finish: word(chunks.find((chunk) => chunk.type === "finish")?.reason?.kind) ?? "none",
|
|
5618
|
+
input,
|
|
5619
|
+
cacheRead,
|
|
5620
|
+
cacheWrite: finite(usage.cacheWriteTokens),
|
|
5621
|
+
output: finite(usage.outputTokens),
|
|
5622
|
+
context: input === void 0 && cacheRead === void 0 ? void 0 : (input ?? 0) + (cacheRead ?? 0)
|
|
5623
|
+
}));
|
|
5624
|
+
} else if (type === "assistant/attempt") {
|
|
5625
|
+
const reason = chunksOf(data.stream).find((chunk) => chunk.type === "finish")?.reason;
|
|
5626
|
+
attempts.push(compact({
|
|
5627
|
+
turn: finite(data.turn),
|
|
5628
|
+
step: finite(data.step),
|
|
5629
|
+
time: iso(event.time),
|
|
5630
|
+
finish: word(reason?.kind),
|
|
5631
|
+
...failureOf(reason, secrets)
|
|
5632
|
+
}));
|
|
5633
|
+
} else if (type === "llm/retry") retries.push(compact({
|
|
5634
|
+
turn: finite(data.turn),
|
|
5635
|
+
step: finite(data.step),
|
|
5636
|
+
time: iso(event.time),
|
|
5637
|
+
provider: word(data.provider),
|
|
5638
|
+
retry: finite(data.retry),
|
|
5639
|
+
maxRetries: finite(data.maxRetries),
|
|
5640
|
+
...failureOf({ failure: data.failure }, secrets)
|
|
5641
|
+
}));
|
|
5642
|
+
else if (type === "turn/end") turnEnds.push(compact({
|
|
5643
|
+
turn: finite(data.turn),
|
|
5644
|
+
time: iso(event.time),
|
|
5645
|
+
kind: word(data.reason?.kind),
|
|
5646
|
+
...failureOf(data.reason, secrets)
|
|
5647
|
+
}));
|
|
5648
|
+
}
|
|
5649
|
+
const truncated = steps.filter((step) => step.finish === "max-tokens");
|
|
5650
|
+
return {
|
|
5651
|
+
totals: {
|
|
5652
|
+
turns,
|
|
5653
|
+
steps: steps.length,
|
|
5654
|
+
maxTokenSteps: truncated.length,
|
|
5655
|
+
retries: retries.length,
|
|
5656
|
+
failedAttempts: attempts.length,
|
|
5657
|
+
compactionEvents: compactions
|
|
5658
|
+
},
|
|
5659
|
+
requestSettings: last(requests, DIAGNOSTICS_LIMITS.requests),
|
|
5660
|
+
maxTokenSteps: last(truncated, DIAGNOSTICS_LIMITS.truncated),
|
|
5661
|
+
recentSteps: last(steps, DIAGNOSTICS_LIMITS.steps),
|
|
5662
|
+
turnEnds: last(turnEnds, DIAGNOSTICS_LIMITS.turns),
|
|
5663
|
+
retries: last(retries, DIAGNOSTICS_LIMITS.retries),
|
|
5664
|
+
failedAttempts: last(attempts, DIAGNOSTICS_LIMITS.attempts)
|
|
5665
|
+
};
|
|
5666
|
+
}
|
|
5667
|
+
/**
|
|
5668
|
+
* The diagnostics service behind the settings card.
|
|
5669
|
+
* @param options - DSH's session query service (read lazily), the session
|
|
5670
|
+
* event reader the image originals already use, the key ring and group
|
|
5671
|
+
* reader, the stored secrets to redact, and this package's manifest URL.
|
|
5672
|
+
*/
|
|
5673
|
+
function createDiagnostics({ sessionQuery, readEvents, ring, groups, secrets = async () => [], ownPackageJsonUrl }) {
|
|
5674
|
+
const query = () => {
|
|
5675
|
+
const service = sessionQuery?.();
|
|
5676
|
+
if (typeof service?.listSessions !== "function") throw new Error("PackyCode diagnostics need DSH session history, which this runtime does not provide");
|
|
5677
|
+
return service;
|
|
5678
|
+
};
|
|
5679
|
+
const version = async () => {
|
|
5680
|
+
try {
|
|
5681
|
+
return word(JSON.parse(await readFile(fileURLToPath(ownPackageJsonUrl), "utf8")).version);
|
|
5682
|
+
} catch {
|
|
5683
|
+
return;
|
|
5684
|
+
}
|
|
5685
|
+
};
|
|
5686
|
+
/** Stored conversation keys by position: state and groups, never labels or keys. */
|
|
5687
|
+
const keys = async () => {
|
|
5688
|
+
const listed = (await ring.list()).keys ?? [];
|
|
5689
|
+
let reading;
|
|
5690
|
+
try {
|
|
5691
|
+
reading = await groups?.read({ force: false });
|
|
5692
|
+
} catch {
|
|
5693
|
+
reading = void 0;
|
|
5694
|
+
}
|
|
5695
|
+
return listed.map((entry, index) => {
|
|
5696
|
+
const group = reading?.keys?.find((item) => item.id === entry.id);
|
|
5697
|
+
return compact({
|
|
5698
|
+
key: index + 1,
|
|
5699
|
+
active: entry.active === true,
|
|
5700
|
+
disabled: entry.disabled === true ? true : void 0,
|
|
5701
|
+
groups: Array.isArray(group?.groups) ? group.groups.map(word).filter(Boolean) : void 0,
|
|
5702
|
+
reaches: group?.coverage === void 0 ? void 0 : Object.keys(group.coverage).filter((model) => group.coverage[model] === true)
|
|
5703
|
+
});
|
|
5704
|
+
});
|
|
5705
|
+
};
|
|
5706
|
+
return Object.freeze({
|
|
5707
|
+
/** Recent top-level sessions, newest first, for the picker. */
|
|
5708
|
+
async list(signal) {
|
|
5709
|
+
return { sessions: (await query().listSessions(signal)).filter((record) => typeof record?.header?.id === "string" && !(record.header.delegationDepth > 0)).slice(0, SESSION_LIST_LIMIT).map((record) => compact({
|
|
5710
|
+
id: record.header.id,
|
|
5711
|
+
createdAt: iso(record.header.createdAt),
|
|
5712
|
+
live: record.live === true,
|
|
5713
|
+
folder: typeof record.header.cwd === "string" && record.header.cwd !== "" ? basename(record.header.cwd) : void 0
|
|
5714
|
+
})) };
|
|
5715
|
+
},
|
|
5716
|
+
/** The numbers-only summary of one session. */
|
|
5717
|
+
async read(payload, signal) {
|
|
5718
|
+
const sessionId = payload?.sessionId;
|
|
5719
|
+
if (typeof sessionId !== "string" || !SESSION_ID.test(sessionId)) throw new Error("PackyCode diagnostics need a session to read");
|
|
5720
|
+
query();
|
|
5721
|
+
let events;
|
|
5722
|
+
try {
|
|
5723
|
+
events = await readEvents(sessionId, signal);
|
|
5724
|
+
} catch {
|
|
5725
|
+
throw new Error("PackyCode could not read that session");
|
|
5726
|
+
}
|
|
5727
|
+
const record = (await query().listSessions(signal)).find((item) => item?.header?.id === sessionId);
|
|
5728
|
+
return {
|
|
5729
|
+
format: DIAGNOSTICS_FORMAT,
|
|
5730
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5731
|
+
plugin: compact({ version: await version() }),
|
|
5732
|
+
session: compact({
|
|
5733
|
+
id: sessionId,
|
|
5734
|
+
createdAt: iso(record?.header?.createdAt),
|
|
5735
|
+
live: record === void 0 ? void 0 : record.live === true,
|
|
5736
|
+
delegationDepth: finite(record?.header?.delegationDepth)
|
|
5737
|
+
}),
|
|
5738
|
+
keys: await keys(),
|
|
5739
|
+
...summarizeSessionEvents(events, { secrets: await secrets() })
|
|
5740
|
+
};
|
|
5741
|
+
}
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
//#endregion
|
|
5442
5745
|
//#region src/preferences.js
|
|
5443
5746
|
/**
|
|
5444
5747
|
* Where the plugin's preferences are read from and written to, on both
|
|
@@ -6962,8 +7265,9 @@ function createRouteAdapter({ ring, keys, fallbackSessionId, resolveAttachments,
|
|
|
6962
7265
|
* Every reply carries only what this plugin owns: key labels and state, the
|
|
6963
7266
|
* balance and group projections the host built, host overrides, whether this
|
|
6964
7267
|
* device has the Claude Code CLI, the plugin version, and the web search
|
|
6965
|
-
* preference and model visibility,
|
|
6966
|
-
* chunks. Key material never crosses this
|
|
7268
|
+
* preference and model visibility, bounded session-authorized original PNG
|
|
7269
|
+
* chunks, or a numbers-only session diagnosis. Key material never crosses this
|
|
7270
|
+
* boundary.
|
|
6967
7271
|
*
|
|
6968
7272
|
* @module dsh-packycode-api/rpc
|
|
6969
7273
|
*/
|
|
@@ -6989,7 +7293,7 @@ const keyId = (payload) => String(payload?.id ?? "");
|
|
|
6989
7293
|
* web search controller (without one, its endpoints refuse).
|
|
6990
7294
|
* @returns a handler for `connection.rpc.handle(CHANNEL, handler)`.
|
|
6991
7295
|
*/
|
|
6992
|
-
function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCli, pluginManager, search, modelSelection, images, imageOriginals }) {
|
|
7296
|
+
function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCli, pluginManager, search, modelSelection, images, imageOriginals, diagnostics }) {
|
|
6993
7297
|
const need = (service, message) => {
|
|
6994
7298
|
if (service === void 0) throw new Error(message);
|
|
6995
7299
|
return service;
|
|
@@ -7001,6 +7305,7 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
|
|
|
7001
7305
|
const models = () => need(modelSelection, "PackyCode model selection is unavailable in this runtime");
|
|
7002
7306
|
const imageSupport = () => need(images, "PackyCode images are unavailable in this runtime");
|
|
7003
7307
|
const originals = () => need(imageOriginals, "PackyCode original images are unavailable in this runtime");
|
|
7308
|
+
const diagnosis = () => need(diagnostics, "PackyCode diagnostics are unavailable in this runtime");
|
|
7004
7309
|
const status = async () => ({
|
|
7005
7310
|
...await ring.list(),
|
|
7006
7311
|
hosts: hosts(),
|
|
@@ -7087,7 +7392,9 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
|
|
|
7087
7392
|
}),
|
|
7088
7393
|
"images/keys/remove": (payload) => imageSupport().mutate("remove", { id: payload?.id }),
|
|
7089
7394
|
"images/keys/select": (payload) => imageSupport().mutate("select", { id: payload?.id }),
|
|
7090
|
-
"images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id })
|
|
7395
|
+
"images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id }),
|
|
7396
|
+
"diagnostics/sessions": (_payload, signal) => diagnosis().list(signal),
|
|
7397
|
+
"diagnostics/session": (payload, signal) => diagnosis().read(payload, signal)
|
|
7091
7398
|
};
|
|
7092
7399
|
return async (endpoint, payload, signal) => {
|
|
7093
7400
|
try {
|
|
@@ -8283,6 +8590,23 @@ function apply(ctx, config) {
|
|
|
8283
8590
|
search.register(webCtx.web, (callback, label) => scope.effect(callback, label));
|
|
8284
8591
|
});
|
|
8285
8592
|
keys.remember().catch(() => void 0);
|
|
8593
|
+
/** One session's events from DSH's session query service, without projections. */
|
|
8594
|
+
const readSessionEvents = async (sessionId, signal) => {
|
|
8595
|
+
const query = ctx.get?.("sessionQuery");
|
|
8596
|
+
if (typeof query?.observeSession === "function") {
|
|
8597
|
+
const observation = await query.observeSession(sessionId, {
|
|
8598
|
+
signal,
|
|
8599
|
+
projectionMode: "none"
|
|
8600
|
+
});
|
|
8601
|
+
try {
|
|
8602
|
+
return [...observation.events];
|
|
8603
|
+
} finally {
|
|
8604
|
+
observation[Symbol.dispose]();
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8607
|
+
if (typeof query?.readSession === "function") return (await query.readSession(sessionId)).events;
|
|
8608
|
+
return [];
|
|
8609
|
+
};
|
|
8286
8610
|
const handler = createPackyCodeRpc({
|
|
8287
8611
|
ring,
|
|
8288
8612
|
balance,
|
|
@@ -8296,22 +8620,15 @@ function apply(ctx, config) {
|
|
|
8296
8620
|
images,
|
|
8297
8621
|
imageOriginals: createImageOriginalReader({
|
|
8298
8622
|
imageDirectory: join(dshHome, "packycode-api", "images"),
|
|
8299
|
-
getSessionEvents:
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
} finally {
|
|
8309
|
-
observation[Symbol.dispose]();
|
|
8310
|
-
}
|
|
8311
|
-
}
|
|
8312
|
-
if (typeof query?.readSession === "function") return (await query.readSession(sessionId)).events;
|
|
8313
|
-
return [];
|
|
8314
|
-
}
|
|
8623
|
+
getSessionEvents: readSessionEvents
|
|
8624
|
+
}),
|
|
8625
|
+
diagnostics: createDiagnostics({
|
|
8626
|
+
sessionQuery: () => ctx.get?.("sessionQuery"),
|
|
8627
|
+
readEvents: readSessionEvents,
|
|
8628
|
+
ring,
|
|
8629
|
+
groups,
|
|
8630
|
+
secrets: async () => (await Promise.all([ring.secrets(), imageRing.secrets()])).flat().map((entry) => entry.key),
|
|
8631
|
+
ownPackageJsonUrl: new URL("../package.json", import.meta.url)
|
|
8315
8632
|
})
|
|
8316
8633
|
});
|
|
8317
8634
|
/**
|
|
@@ -8347,4 +8664,4 @@ function apply(ctx, config) {
|
|
|
8347
8664
|
});
|
|
8348
8665
|
}
|
|
8349
8666
|
//#endregion
|
|
8350
|
-
export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DISPLAY_NAME, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, LEGACY_STATUS_HOSTS, MAX_CLI_IMAGE_CALLS, MAX_IMAGE_BYTES, MCP_IMAGE_SCHEMA, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeImageService, createClaudeRunner, createClaudeSearchBridge, createGroupReader, createImageController, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decodeImagePng, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, planModeForToolExecution, pngDimensions, readClaudeStreamEvents, readHiddenModels, readImagePreference, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, resolveSpawnCommand, statusHostOrder, summarizeToolUse, validateImageOptions };
|
|
8667
|
+
export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DIAGNOSTICS_FORMAT, DIAGNOSTICS_LIMITS, DISPLAY_NAME, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, LEGACY_STATUS_HOSTS, MAX_CLI_IMAGE_CALLS, MAX_IMAGE_BYTES, MCP_IMAGE_SCHEMA, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeImageService, createClaudeRunner, createClaudeSearchBridge, createDiagnostics, createGroupReader, createImageController, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decodeImagePng, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, planModeForToolExecution, pngDimensions, readClaudeStreamEvents, readHiddenModels, readImagePreference, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, renderTitlePrompt, resolveSpawnCommand, statusHostOrder, summarizeSessionEvents, summarizeToolUse, unwrapTitle, validateImageOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
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",
|