dsh-connect-workbuddy 4.6.7 → 4.7.0

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 CHANGED
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.7.0 (2026-10-17)
4
+
5
+ > 版本号是 **minor**:这不是只改内部的 patch —— 它**改变了发往上游的请求体**。DSH Desktop 0.2.1-rc.1 随包 pi-ai 1.1.0,本版修的是该组合下「请求静默丢掉 system prompt 与全部工具声明」。若你停留在更早的宿主,行为不变。
6
+
7
+ ### Fixes
8
+
9
+ - **DSH 0.2.1-rc.1 下所有 workbuddy 模型静默丢失 system prompt 与工具声明(新一轮 #24/#25/#26 同族缺陷)**
10
+ - **症状**:升级到 DSH `0.2.1-rc.1`(随包 `@earendil-works/pi-ai@1.1.0`)后,workbuddy / workbuddy-global 的模型**全部**不再调用工具,界面上**没有任何报错**。模型有时把 DSML 标记当正文吐出来——和 4.4.2 记录的现象一模一样。
11
+ - **根因(三环叠加,缺一不成立)**:
12
+ 1. `dsh-llm-pi-ai` **两版都不做归一化**。0.1.7 与 0.2.1 的 `piContext()` 都直接返回 `{systemPrompt, messages, tools}`,两版里 `normalizeContext` 出现次数均为 **0**。
13
+ 2. pi-ai **0.85.1 读顶层字段**:`dist/api/openai-completions.js:909` 的 `if (context.systemPrompt)`,工具读 `context.tools`。所以 0.85 能自己兜底,宿主不归一化也看不出问题。
14
+ 3. pi-ai **1.1.0 只读 `messages`**:走 `resolveTranscript()` → `collapseSystemMessages()`,顶层 `systemPrompt` / `tools` **从不读取**。
15
+ 于是 `withLegacyContext()` 的 **modern 分支原样透传**(当时一行 `return api`),把 0.85 形状的原始信封直接交给 1.1.0 的 api → 请求体里 system prompt 与 tools 双双为空。
16
+ - **为什么 4.4.2 的修复不够**:4.4.2 修的是「1.x 被判成 legacy → 折成 0.85 形状 → 工具丢」。本版是**同一失效模式的反面**——「1.x 判成 modern(判定是对的)→ 不折 → 工具和 prompt 一起丢」。门是对的,空的是那个分支。`generationOf()` 无需改动。
17
+ - **实测证据(真实 `createProvider` + 真实 `openAICompletionsApi` + 本地 SSE 服务器抓线上 body)**:
18
+
19
+ | pi-ai | 线上 `messages[].role` | system 文本 | `body.tools` |
20
+ |---|---|---|---|
21
+ | 0.85.1 | `["system","user"]` | 原样 | 1 ✅ |
22
+ | **1.1.0(修复前)** | **`["user"]`** | **丢失** | **0** |
23
+ | 1.1.0(修复后) | `["system","user"]` | 原样 | 1 ✅ |
24
+ | 1.1.0(已归一化输入) | `["system","user"]` | 原样 | 1 ✅(幂等) |
25
+
26
+ - **改了什么**:新增 `foldModernContext()`,modern 分支改为**归一化**而非透传——把顶层的 `systemPrompt`/`tools` 折进首条 `system` 消息(文本进 `content`、工具进 `toolsAdded`),并**删掉**这两个顶层字段,与 pi-ai 官方 `normalizeContext` 的输出形状一致。
27
+ - **为什么内联而不是调 `normalizeContext`**:该函数在 **0.85.1 里不存在**,静态 import 会在那里变成 `undefined`;而本模块一贯「绝不静态 import pi-ai」(`pi-ai-runtime.ts` 头部教条,正是它让独立 `bin` 能在没装 peer 的 profile 里跑)。内联折叠零新依赖。
28
+ - **幂等**:输入若已归一化(顶层两字段都不存在),直接原样返回——否则会再前置一条**空的** system 消息。
29
+ - **★ 守卫为什么一直没抓到**:`tests/adapter-outgoing-body.spec.ts` 是唯一断言「线上 body 真的带 tools 与 system」的用例,却被 `it.skipIf(piAiRuntimeInfo().generation !== 'legacy')` 限死,**modern 分支零覆盖**;而作者 checkout 解析到 0.85.1,它一直是绿的。`adapter.spec.ts` 也帮不上忙——它 mock 了 api,只断言「交给 wrapper 的 context」,这个断言恰好**认证**了缺陷。
30
+ - **本版把守卫扩成 2×2 矩阵**:{legacy, modern} × {原始信封, 已归一化 transcript} 四格都断言 `body.tools` 与首条 system 文本。在原用例的基础上补了「modern × 原始信封」这一格——**它就是本缺陷的回归钉**。另加幂等用例与「只有 tools、没有 systemPrompt」用例。
31
+ - **本机与 CI 的代际约束(写在用例注释里)**:legacy 两格只能在 0.85 checkout 上跑(1.x 的 api 读不了 0.85 扁平形状);modern 两格只能在 0.87+/1.x 上跑(0.85 的 `estimateMessageTokens` 会对 system 消息的**字符串**逐字符迭代并读 `block.name.length` 而崩溃,即 #24 的崩溃)。用例按**已安装版本**选格,不会让任何一代完全无人看管。
32
+ - **变异验证**:把 modern 分支改回 `return api` → 新增的「FOLDS a raw seam envelope」用例**红**(1 例)。验证后已还原。
33
+ - **实机验证**:临时把 checkout 的 `node_modules/@earendil-works/pi-ai` 换成 1.1.0(连同 1.1.0 新需要的 `typebox`/`partial-json`/`openai`),modern 四格**全绿**;legacy 两格如预期红(1.1.0 读不了扁平形状)。验证后已完整还原 0.85.1 与依赖树。
34
+
35
+ - **「可关闭思考」的模型被锁成不可关闭,不可关闭的反倒可关(`adapter.ts` 既有缺陷,非本次升级引入)**
36
+ - **根因**:`workBuddyThinkingLevelMap()` 的判断写反了 —— `if (canDisableThinking !== true) map.off = null`,而 `null` 在 pi-ai 里的语义是「该档位**不支持**」(`getSupportedThinkingLevels` 按 `mapped === null` 过滤;`openai-completions` 用 `thinkingLevelMap?.off !== null` 决定能否发「关闭思考」的请求)。
37
+ - **实际后果**:上游声明 `canDisableThinking: true` 且用户没取消勾选 → `off` 被置 `null` → 关闭思考**不可选**;上游声明 `false`/未声明 → 不置 `null` → `off` 落成字符串 `'off'` → **反而可选**。与 `offDefaultFor()` 的语义正好相反。
38
+ - **为什么一直没发现**:`tests/adapter.spec.ts` 只探了 `canDisableThinking: false` 那半边,而写反的形式在那半边也恰好通过;另外两条对 `true` 的用例把期望值写成了「没有 off」或「off 缺失」,**把缺陷当成了规格**。
39
+ - **改了什么**:改为 `canDisableThinking === true ? 'off' : null`,并把三条用例的期望值改正,新增「能力未声明时不给 off」一例。
40
+ - **变异验证**:把判断改回 `!== true` → 2 例红。
41
+
42
+ - **DSH 宿主线守卫的 semver 比较器是错的:判 `0.2.1-rc.1` 通过,而 npm 实算是 false**
43
+ - **根因**:`tests/dsh-line.spec.ts` 自写的 `satisfiesRange()` 只做朴素 `>=` / `<` 比较,没有实现 semver 的**预发布悬崖**——预发布版本只有当同一比较器集合里存在**同一 `major.minor.patch` 元组**上的预发布比较符时才被放行。`>=0.1.7-rc.1` 只对 `0.1.7-x` 开口,`<0.3.0-0` 排除整个 `0.3.0` 元组,`0.2.1-rc.1`(元组 `0.2.1`)两条都不满足。
44
+ - **为什么夹具没触发**:原夹具是 `['0.1.7-rc.1','0.1.7-rc.2','0.2.0-rc.1','0.2.0-rc.9','0.2.0','0.2.1','0.2.99']`——恰好跳过真实发布的 `0.2.1-rc.1`;而 `0.2.0-rc.1` / `0.2.0-rc.9` 被断言成 admitted,那本身也是错的(npm 判 false)。
45
+ - **改了什么**:比较器按规范重写(`parseVersion` / `compareParsed` / `sameTuple` + 悬崖规则),夹具拆成「正式版放行」与「已知预发布偏差」两组,并新增 5 条照抄 npm 实际输出的悬崖用例。
46
+ - **新增的「已知偏差」用例**:显式断言 `0.2.1-rc.1` 与 `0.2.0-rc.1` 在现声明下 **不** admitted、而 `0.2.1` GA admitted。于是偏差是**被记录、被解释**的,而不是被错比较器掩盖;将来若改清单,这条会提醒同步更新说明。
47
+ - **为什么可以暂不改清单**:详见下一条。宿主 `evaluatePluginCompatibility()` 只校验 `@deepseek-ai/dsh*` 命名空间,本机 profile 用 `link:` + 默认不自动装 peer,rc 宿主上插件照常加载;等宿主发 `0.2.1` GA,现声明即放行。
48
+ - **变异验证**:去掉悬崖规则 → 2 例红。
49
+
50
+ ### Changed
51
+
52
+ - **`@earendil-works/pi-ai` 的 peer 范围放宽到 1.x:`>=0.85.0 <0.88.0 || >=1.0.0-0 <2.0.0`(peer 与 dev 两处同步)**
53
+ - **为什么必须改**:旧范围 `>=0.85.0 <0.88.0` **不**放行 DSH 0.2.1-rc.1 随包的 1.1.0,而 1.1.0 恰恰是本版修好后要支持的版本 —— 声明与实际支持面自相矛盾。
54
+ - **范围是实算出来的,不是猜的**:`0.85.0 / 0.85.1 / 0.87.1 / 1.0.0 / 1.0.2 / 1.1.0` 均 `true`;`2.0.0` 与 `1.1.0-beta.1` 均 `false`。上界停在 `<2.0.0` 与 `generationOf()` 刻意不认 2.x 的判断保持一致;预发布串不承诺。
55
+ - **注意**:宿主门禁只校验 `@deepseek-ai/dsh*`,所以这条 peer **从不参与**宿主校验(4.4.2 已记录)。改它主要是让**清单说真话**,真正保护用户的是上面那条代码修复。
56
+ - **`dsh-*` 那 9 条 peer 本版未动**:理由见上一条「已知偏差」——任何为 `0.2.x` rc 硬凑的静态范围都会有漏(已逐一实算否证:`|| >=0.2.0-rc.1 <0.2.99`、`|| >=0.3.0-0 <0.3.0`、`>=0.1.7-rc.1-0 <0.3.0-0`、去掉预发布上界、`0.2.x` 通配符,全部失败)。
57
+
58
+ ### CI
59
+
60
+ - **新增 `test-pi-ai-next` 作业:在 pi-ai 1.1.0 上跑一遍完整 typecheck + 测试 + 构建**
61
+ - **为什么必须有**:`tests/adapter-outgoing-body.spec.ts` 的矩阵是 2×2,但**一个代际跑不了另一个代际的库**——0.85 checkout 上只能跑 legacy 两格(1.x 的 api 读不了扁平形状),1.x checkout 上只能跑 modern 两格(0.85 的分词器在 system 消息上崩,即 #24)。主 `test` 作业钉的是 0.85.1,**modern 半边从未在 CI 里运行过**,而这正是 0.2.1-rc.1 坏掉的那一半。
62
+ - **怎么切版本**:pi-ai 既是 peer 又是直接 devDependency,`pnpm-workspace.yaml` 的 `overrides` 是官方支持的改法。作业用 `sed` 改那一条 override,再 `--no-frozen-lockfile` 让 pnpm 只为它重解析。
63
+ - **有一道断言防止作业空转**:装完立刻 `node -p` 读实际版本并要求等于矩阵值,否则「跑了一遍绿」可能跑的根本不是 1.1.0。
64
+ - **本作业当场抓到一个本机永远看不到的缺陷**:pi-ai 1.x 给 provider 侧上下文加了**品牌**(`TranscriptContext`,`unique symbol` 字段,声明为「只有 `normalizeContext()` 能产出」)。在 1.x 的**类型**上,`stream`/`streamSimple` 的两条分支都收到品牌类型,于是把普通 `Context` 传进去会 `TS2345`——而 `modern` 分支的 `return api` 也一并被类型系统拒绝。本机 0.85 的类型面是裸 `Context`,**这个错误在 0.85 上不存在**。修法是一处**有注释的** `transcript<T>()` 断言,理由写在函数上:调用点已经完成 `normalizeContext` 的职责,而 `normalizeContext` 在 0.85 不存在、不能导入。**变异验证**:去掉该断言 → 1.1.0 上 `tsc` 报 4 个 `TS2345`。
65
+ - **验证过它真的会响**:把 modern 分支改回 `return api`,1.1.0 作业**红 4 例**(`adapter.spec.ts` 与 `adapter-outgoing-body.spec.ts` 各自的 modern 守卫),而 0.85.1 作业保持全绿。这正是本作业存在的意义。
66
+ - 仅跑 ubuntu:这是库层面的断言,不是平台断言;OS 矩阵已由主 `test` 作业覆盖。
67
+
68
+ ### Docs
69
+
70
+ - `src/pi-ai-runtime.ts` 头部更正:peer 范围同步为新值;删去把 1.x 说成「未发布」的过时表述(DSH 0.2.1-rc.1 已随包 1.1.0);补一句「落在 modern 是必要但**不充分**——modern 分支还必须折叠原始信封」。
71
+ - `src/adapter.ts` 的 `adaptLegacyPiAiContext()` 补注 **legacy-only**:它的结果是 0.85 扁平形状,绝不能交给 1.x 的 api(1.x 从 `messages[].toolsAdded` 读工具,会一个都看不到)。
72
+ - 新增 `docs/DSH-0.2.1-RC1-FOLLOWUP.md`:本次升级的完整调研档,含逐包对照表、四项「已核对无需改动」、失败候选与证据来源。
73
+
3
74
  ## 4.6.7 (2026-10-17)
4
75
 
5
76
  > 版本号是 **patch**:只改账号池的排序规则,没有接口或配置变更。但它是**行为变更**——开启账号池的机器上,「这次请求用谁」可能变了,且新行为正是本版要的。
package/README.en.md CHANGED
@@ -152,7 +152,7 @@ One class of state in the pool **does not go through a draft**: probe results, c
152
152
 
153
153
  ## Install
154
154
 
155
- > ⚠️ **Version requirement: DSH 0.1.7-rc.1 or newer (current window: `0.1.7-rc.1` – `0.2.x`).** Since v2.1.0 this plugin only supports hosts on DSH 0.1.7-rc.1 and above (older hosts cannot resolve the dependencies to install this version); hosts on 0.1.5 and earlier should stay on v2.0.15. Since v2.1.2 the upper bound is **`<0.3.0-0`**: the **whole 0.2.0 line, GA included, is verified**; the 0.3.0 line is explicitly refused. See [Supported host range](#supported-host-range) below.
155
+ > ⚠️ **Version requirement: DSH 0.1.7-rc.1 or newer (current window: `0.1.7-rc.1` – `0.2.x` GA).** Since v2.1.0 this plugin only supports hosts on DSH 0.1.7-rc.1 and above (older hosts cannot resolve the dependencies to install this version); hosts on 0.1.5 and earlier should stay on v2.0.15. Since v2.1.2 the upper bound is **`<0.3.0-0`**; the 0.3.0 line is explicitly refused. See [Supported host range](#supported-host-range) below.
156
156
 
157
157
  ### Supported host range
158
158
 
@@ -161,11 +161,20 @@ DSH's shipped gate (`evaluatePluginCompatibility` in `packages/boot/app-boot/src
161
161
  | Host version | Verdict |
162
162
  | --- | --- |
163
163
  | `0.1.7-rc.1` – `0.1.7-rc.2` | ✅ supported |
164
- | `0.2.0-rc.1` – `0.2.x` (`0.2.0` GA included) | ✅ supported (verified from 2.1.2) |
164
+ | `0.2.x` **GA** (`0.2.0`, `0.2.1`, …) | ✅ supported (verified from 2.1.2) |
165
+ | `0.2.x` **prerelease** (`0.2.0-rc.1`, `0.2.1-rc.1`, …) | ⚠️ outside the declared range — see below |
165
166
  | `0.3.0-0` and above | ❌ explicitly refused (that line is unverified) |
166
167
  | `0.1.6` and earlier | ❌ explicitly refused (0.1.5 users should stay on v2.0.15) |
167
168
 
168
- > The upper bound is written `<0.3.0-0`, not `<0.3.0`: **every DSH tag to date is a prerelease** (`-alpha.N` / `-rc.N`), so `<0.3.0` alone would admit the entire unverified `0.3.0-alpha.1` / `0.3.0-rc.1` line, leaving the guardrail inert — exactly the defect shape 2.1.2 fixes. `tests/dsh-line.spec.ts` guards this contract.
169
+ > **On `0.2.x` prereleases (corrected in v4.7.0)**: this table previously listed `0.2.0-rc.1` as "✅ supported". That was **wrong**.
170
+ > npm semver admits a prerelease only when a comparator carries a prerelease on the **same `major.minor.patch` tuple**.
171
+ > The declared `>=0.1.7-rc.1 <0.3.0-0` opens only `0.1.7-x`, while `<0.3.0-0` excludes the `0.3.0` tuple — so versions such as
172
+ > **`0.2.1-rc.1` satisfy neither comparator** (`satisfies('0.2.1-rc.1', …) === false`).
173
+ > The practical impact is small: the host gate only evaluates the `@deepseek-ai/dsh*` namespace, and on rc hosts this plugin loads
174
+ > normally via `link:` / a manual install. It is also **not fixable by editing the range** — no static range can cover "any `0.2.x`
175
+ > prerelease"; five candidate formulations were computed and refuted (see CHANGELOG 4.7.0). It resolves itself once the host ships `0.2.1` GA.
176
+
177
+ > The upper bound is written `<0.3.0-0`, not `<0.3.0`: **every DSH tag to date is a prerelease** (`-alpha.N` / `-rc.N`), so `<0.3.0` alone would admit the entire unverified `0.3.0-alpha.1` / `0.3.0-rc.1` line, leaving the guardrail inert — exactly the defect shape 2.1.2 fixes. `tests/dsh-line.spec.ts` guards this contract, including the prerelease cliff.
169
178
 
170
179
  > 🪟 **Platform support: Windows, macOS, and Linux are supported side by side.** **Windows is a first-class target**, not an afterthought: CI runs the full suite on `windows-latest`, and four areas carry Windows-specific branches with their own tests — credential paths, process heartbeat, desktop-app executable lookup, and settings writes. **When fixing a defect that only reproduces on Windows, the Windows reporter's field evidence and an encryption vector captured from a real Windows install are what the fix is judged against** (see the 2.0.6 / 2.0.11 / 2.0.12 sections of [CHANGELOG](CHANGELOG.md)). See the [Platform support](#platform-support) section for the per-platform differences and known limitations.
171
180
 
package/README.md CHANGED
@@ -160,7 +160,7 @@ DSH PiAiAdapter(每个 provider 一套)
160
160
 
161
161
  ## 安装
162
162
 
163
- > ⚠️ **版本要求:DSH 0.1.7-rc.1 及以上(当前窗口:`0.1.7-rc.1` ~ `0.2.x`)。** 自 v2.1.0 起本插件只支持 DSH 0.1.7-rc.1 及以上的宿主(旧版宿主无法通过依赖解析安装本版);0.1.5 及更早的宿主请停留在 v2.0.15。自 v2.1.2 起上界放宽为 **`<0.3.0-0`**——**0.2.0 全线(含正式版)已实测支持**,0.3.0 线则被明确拒绝。详见下方[受支持的宿主范围](#受支持的宿主范围)。
163
+ > ⚠️ **版本要求:DSH 0.1.7-rc.1 及以上(当前窗口:`0.1.7-rc.1` ~ `0.2.x` 正式版)。** 自 v2.1.0 起本插件只支持 DSH 0.1.7-rc.1 及以上的宿主(旧版宿主无法通过依赖解析安装本版);0.1.5 及更早的宿主请停留在 v2.0.15。自 v2.1.2 起上界放宽为 **`<0.3.0-0`**,0.3.0 线被明确拒绝。详见下方[受支持的宿主范围](#受支持的宿主范围)。
164
164
 
165
165
  ### 受支持的宿主范围
166
166
 
@@ -169,11 +169,20 @@ DSH 的随包门禁(`packages/boot/app-boot/src/plugin-compatibility.ts` 的 `
169
169
  | 宿主版本 | 判定 |
170
170
  | --- | --- |
171
171
  | `0.1.7-rc.1` ~ `0.1.7-rc.2` | ✅ 支持 |
172
- | `0.2.0-rc.1` ~ `0.2.x`(含 `0.2.0` 正式版) | ✅ 支持(2.1.2 起实测) |
172
+ | `0.2.x` **正式版**(`0.2.0`、`0.2.1` …) | ✅ 支持(2.1.2 起实测) |
173
+ | `0.2.x` **预发布**(`0.2.0-rc.1`、`0.2.1-rc.1` …) | ⚠️ 不落在声明范围内,见下方说明 |
173
174
  | `0.3.0-0` 及以上 | ❌ 明确拒绝(该线尚未验证) |
174
175
  | `0.1.6` 及更早 | ❌ 明确拒绝(0.1.5 线请停留在 v2.0.15) |
175
176
 
176
- > 上界写作 `<0.3.0-0` 而非 `<0.3.0`:DSH 至今**全部 tag 都是预发布**(`-alpha.N` / `-rc.N`),只写 `<0.3.0` 会放行整条 `0.3.0-alpha.1` / `0.3.0-rc.1` 这条从未验证过的线,围栏形同虚设——这正是 2.1.2 修掉的缺陷形状。该口径由 `tests/dsh-line.spec.ts` 守卫。
177
+ > **关于 `0.2.x` 预发布(v4.7.0 更正)**:此前本表把 `0.2.0-rc.1` 写作「✅ 支持」,那是**错的**。
178
+ > npm semver 规定:预发布版本只有当一个比较器**在同一条 `major.minor.patch` 元组上**带预发布标签时才被放行。
179
+ > 现声明 `>=0.1.7-rc.1 <0.3.0-0` 只对 `0.1.7-x` 开口,`<0.3.0-0` 又排除了 `0.3.0` 元组,因此
180
+ > **`0.2.1-rc.1` 这类版本两条比较器都不满足**(`satisfies('0.2.1-rc.1', …) === false`)。
181
+ > 实际影响很小:宿主门禁只校验 `@deepseek-ai/dsh*` 命名空间,且 rc 宿主上本插件按 `link:` / 手工安装照常加载。
182
+ > 这**不是**靠改范围能解决的问题——任何静态范围都覆盖不了「任意 `0.2.x` 预发布」,已逐一实算否证五种写法
183
+ > (见 CHANGELOG 4.7.0)。等宿主发 `0.2.1` 正式版即自然成立。
184
+
185
+ > 上界写作 `<0.3.0-0` 而非 `<0.3.0`:DSH 至今**全部 tag 都是预发布**(`-alpha.N` / `-rc.N`),只写 `<0.3.0` 会放行整条 `0.3.0-alpha.1` / `0.3.0-rc.1` 这条从未验证过的线,围栏形同虚设——这正是 2.1.2 修掉的缺陷形状。该口径由 `tests/dsh-line.spec.ts` 守卫(含预发布悬崖的用例)。
177
186
 
178
187
  > 🪟 **平台支持:Windows、macOS、Linux 三者并列支持。** **Windows 是一等目标平台**,不是「顺带能跑」:CI 在 `windows-latest` 上跑完整套件,凭据路径、进程心跳、App 可执行文件定位、设置写入四处都有 Windows 专属分支与测试,并且**修复只对 Windows 生效的缺陷时,以 Windows 用户回报的现场与真机取证的加密向量为准**(见 [CHANGELOG](CHANGELOG.md) 的 2.0.6 / 2.0.11 / 2.0.12 各节)。见下方[平台支持](#平台支持)一节了解各平台的具体差异与已知限制。
179
188
 
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-D7a100yu.js";
2
+ import { C as defaultDesktopAuthDirs, F as WorkBuddyUpstreamClient, K as WORKBUDDY_APP_EXECUTABLE_ENV, O as legacyWorkbuddyOwnAuthPath, Q as findWorkbuddyAppExecutableWithSource, S as defaultDesktopAuthCandidates, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, j as workbuddyOwnAuthPath, l as piAiRuntimeInfo, o as workbuddyHostHeartbeatPath, ot as hostPeersInfo, r as isHeartbeatProcessAlive, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, y as WorkBuddyCredentialStore } from "./host-heartbeat-Dd9fsFMR.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -3801,9 +3801,9 @@ var WorkBuddyCatalog = class {
3801
3801
  * Which `@earendil-works/pi-ai` copy THIS plugin actually resolves at runtime.
3802
3802
  *
3803
3803
  * WHY THIS EXISTS (issues #24 / #25 / #26). The plugin declares pi-ai as a
3804
- * peer (`>=0.85.0 <0.88.0`), so the module it imports is whatever the host
3805
- * environment provides — and the two generations consume DIFFERENT context
3806
- * shapes:
3804
+ * peer (`>=0.85.0 <0.88.0 || >=1.0.0-0 <2.0.0`), so the module it imports is
3805
+ * whatever the host environment provides — and the two generations consume
3806
+ * DIFFERENT context shapes:
3807
3807
  *
3808
3808
  * - 0.87 hands providers a normalized transcript: `{ messages: [ {role:
3809
3809
  * 'system', content, toolsAdded}, … ] }`. The system prompt and the tool
@@ -3920,8 +3920,9 @@ function readVersion(moduleFile) {
3920
3920
  * doctor shows it as unreadable, so the guess is visible rather than silent).
3921
3921
  *
3922
3922
  * The 1.x line MUST land on `modern`, and it is not a hypothetical: DSH
3923
- * 0.2.1-alpha.2 ships `@earendil-works/pi-ai@1.0.2`, while npm currently
3924
- * publishes 1.1.0. Both 0.87.1 and 1.0.2/1.1.0 read tools from the same place
3923
+ * 0.2.1-alpha.2 ships `@earendil-works/pi-ai@1.0.2`, and DSH 0.2.1-rc.1 ships
3924
+ * 1.1.0 (which is the version this plugin is now tested against). Both 0.87.1
3925
+ * and 1.0.2/1.1.0 read tools from the same place
3925
3926
  * (`context.messages` → the leading system message's `toolsAdded`; see
3926
3927
  * pi-ai's `resolveTranscriptTools`). Matching only `0.<minor>.` classified
3927
3928
  * 1.0.2 as `legacy`, so `withLegacyContext` folded the transcript into the
@@ -3937,6 +3938,13 @@ function readVersion(moduleFile) {
3937
3938
  * observed 1.x line (including current 1.1.0) without assuming an uninspected
3938
3939
  * future major such as 2.x keeps the same transcript contract. The declared
3939
3940
  * peer range remains the plugin's supported-version boundary.
3941
+ *
3942
+ * ⚠️ Being on `modern` is necessary but NOT sufficient. The modern branch must
3943
+ * also FOLD a raw seam envelope: `dsh-llm-pi-ai` never normalizes, and 1.x
3944
+ * reads only `context.messages`. Between 4.4.2 (which taught this function
3945
+ * about major 1) and the 0.2.1-rc.1 fix, `withLegacyContext` returned the
3946
+ * modern api UNWRAPPED, so every request on pi-ai 1.x went upstream with no
3947
+ * prompt and no tool declarations. See `foldModernContext` in `adapter.ts`.
3940
3948
  */
3941
3949
  function generationOf(version) {
3942
3950
  if (version === void 0) return "legacy";
@@ -3949,7 +3957,7 @@ function generationOf(version) {
3949
3957
  //#endregion
3950
3958
  //#region src/version.ts
3951
3959
  /** The npm package version this build was produced from. */
3952
- const WORKBUDDY_CONNECT_VERSION = "4.6.7";
3960
+ const WORKBUDDY_CONNECT_VERSION = "4.7.0";
3953
3961
  //#endregion
3954
3962
  //#region src/host-heartbeat.ts
3955
3963
  /**
package/lib/index.d.ts CHANGED
@@ -1200,9 +1200,9 @@ declare function createWorkBuddyShim(options: WorkBuddyShimOptions): WorkBuddySh
1200
1200
  * Which `@earendil-works/pi-ai` copy THIS plugin actually resolves at runtime.
1201
1201
  *
1202
1202
  * WHY THIS EXISTS (issues #24 / #25 / #26). The plugin declares pi-ai as a
1203
- * peer (`>=0.85.0 <0.88.0`), so the module it imports is whatever the host
1204
- * environment provides — and the two generations consume DIFFERENT context
1205
- * shapes:
1203
+ * peer (`>=0.85.0 <0.88.0 || >=1.0.0-0 <2.0.0`), so the module it imports is
1204
+ * whatever the host environment provides — and the two generations consume
1205
+ * DIFFERENT context shapes:
1206
1206
  *
1207
1207
  * - 0.87 hands providers a normalized transcript: `{ messages: [ {role:
1208
1208
  * 'system', content, toolsAdded}, … ] }`. The system prompt and the tool
@@ -1300,7 +1300,27 @@ declare function workBuddyModelInput(info: WorkBuddyModelInfo): ('text' | 'image
1300
1300
  * `x0.00`, matching WorkBuddy's rendering of free models.
1301
1301
  */
1302
1302
  declare function workBuddyDisplayName(info: WorkBuddyModelInfo): string;
1303
- /** Map only levels advertised by WorkBuddy; undeclared DSH levels stay unavailable. */
1303
+ /**
1304
+ * Map only levels advertised by WorkBuddy; undeclared DSH levels stay unavailable.
1305
+ *
1306
+ * `off` is offered EXACTLY when `canDisableThinking === true` and withheld
1307
+ * (mapped to `null`) otherwise. `null` is pi-ai's "this level is not
1308
+ * supported": `getSupportedThinkingLevels` filters on `mapped === null`, and
1309
+ * `openai-completions` reads `thinkingLevelMap?.off !== null` to decide whether
1310
+ * it may send a thinking-disabled request.
1311
+ *
1312
+ * This condition used to be inverted (`!== true` set the null), which locked
1313
+ * `off` OFF for precisely the models that CAN disable thinking while leaving it
1314
+ * ON for those that cannot. It stayed invisible because the only test probed
1315
+ * the `canDisableThinking: false` half, which the inverted form also passes.
1316
+ *
1317
+ * The value read here is already the EFFECTIVE one: the runtime catalog is
1318
+ * stamped by `withEffectiveOff()` before it reaches the adapter, so this is the
1319
+ * user's `off` selection when they made one (and the upstream declaration,
1320
+ * minus `REFUSES_OFF_MODEL_IDS`, when they never did). Absence is "unknown" and
1321
+ * stays unoffered, matching how the rest of the plugin treats undeclared
1322
+ * capability.
1323
+ */
1304
1324
  declare function workBuddyThinkingLevelMap(info: WorkBuddyModelInfo): WorkBuddyThinkingLevelMap | undefined;
1305
1325
  /**
1306
1326
  * Assemble the adapter. The provider's `getModels` reads the live catalog,
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as isEncryptedFieldWrapper, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as deriveAtRestKey, K as WORKBUDDY_APP_EXECUTABLE_ENV, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as fetchAtRestKeyPayload, Y as deriveAtRestKeyId, Z as findWorkbuddyAppExecutable, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as workbuddyAppExecutableCandidates, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as withFileLock, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as isWorkbuddyBundle, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as readAtRestKey, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as writeFileAtomic, m as deriveCatalog, n as clearHostHeartbeat, nt as macosNestedAppBundles, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as clearAtRestKeyCache, r as isHeartbeatProcessAlive, rt as openEncryptedField, s as writeHostHeartbeat, st as resolveDshHome, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosBundleExecutable, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-D7a100yu.js";
1
+ import { $ as isEncryptedFieldWrapper, A as workbuddyAccountId, B as modelIdOfBody, C as defaultDesktopAuthDirs, D as isVaultAccountId, E as isEncryptedCredentialError, F as WorkBuddyUpstreamClient, G as regionOf, H as parseReasoning, I as classifyUpstreamError, J as deriveAtRestKey, K as WORKBUDDY_APP_EXECUTABLE_ENV, L as declaredTools, M as CREDENTIAL_REJECTED_CODE, N as WORKBUDDY_FALLBACK_SYSTEM_PROMPT, O as legacyWorkbuddyOwnAuthPath, P as WorkBuddyCredentialRejectedError, R as isCredentialRejectedError, S as defaultDesktopAuthCandidates, T as hasEncryptedCredentialFields, U as parseUpstreamModel, V as parseCreditMultiplier, W as prepareChatBody, X as fetchAtRestKeyPayload, Y as deriveAtRestKeyId, Z as findWorkbuddyAppExecutable, _ as WORKBUDDY_AUTH_FILENAME, a as readHostHeartbeat, at as workbuddyAppExecutableCandidates, b as WorkBuddyEncryptedCredentialError, c as WORKBUDDY_CONNECT_VERSION, ct as withFileLock, d as FALLBACK_WORKBUDDY_MODELS_GLOBAL, et as isWorkbuddyBundle, f as WorkBuddyCatalog, g as ENCRYPTED_CREDENTIAL_CODE, h as fallbackModelsFor, i as processStartTimeMs, it as readAtRestKey, j as workbuddyOwnAuthPath, k as parseWorkBuddyAuth, l as piAiRuntimeInfo, lt as writeFileAtomic, m as deriveCatalog, n as clearHostHeartbeat, nt as macosNestedAppBundles, o as workbuddyHostHeartbeatPath, p as applyContextBudgets, q as clearAtRestKeyCache, r as isHeartbeatProcessAlive, rt as openEncryptedField, s as writeHostHeartbeat, st as resolveDshHome, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, tt as macosBundleExecutable, u as FALLBACK_WORKBUDDY_MODELS, v as WORKBUDDY_AUTH_FILE_ENV, w as defaultDesktopAuthPath, x as authFileName, y as WorkBuddyCredentialStore, z as knownRegionOf } from "./host-heartbeat-Dd9fsFMR.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -184,12 +184,33 @@ function workBuddyModelInput(info) {
184
184
  function workBuddyDisplayName(info) {
185
185
  return info.creditMultiplier === void 0 ? info.name : `${info.name} · x${info.creditMultiplier.toFixed(2)}`;
186
186
  }
187
- /** Map only levels advertised by WorkBuddy; undeclared DSH levels stay unavailable. */
187
+ /**
188
+ * Map only levels advertised by WorkBuddy; undeclared DSH levels stay unavailable.
189
+ *
190
+ * `off` is offered EXACTLY when `canDisableThinking === true` and withheld
191
+ * (mapped to `null`) otherwise. `null` is pi-ai's "this level is not
192
+ * supported": `getSupportedThinkingLevels` filters on `mapped === null`, and
193
+ * `openai-completions` reads `thinkingLevelMap?.off !== null` to decide whether
194
+ * it may send a thinking-disabled request.
195
+ *
196
+ * This condition used to be inverted (`!== true` set the null), which locked
197
+ * `off` OFF for precisely the models that CAN disable thinking while leaving it
198
+ * ON for those that cannot. It stayed invisible because the only test probed
199
+ * the `canDisableThinking: false` half, which the inverted form also passes.
200
+ *
201
+ * The value read here is already the EFFECTIVE one: the runtime catalog is
202
+ * stamped by `withEffectiveOff()` before it reaches the adapter, so this is the
203
+ * user's `off` selection when they made one (and the upstream declaration,
204
+ * minus `REFUSES_OFF_MODEL_IDS`, when they never did). Absence is "unknown" and
205
+ * stays unoffered, matching how the rest of the plugin treats undeclared
206
+ * capability.
207
+ */
188
208
  function workBuddyThinkingLevelMap(info) {
189
209
  const supported = info.reasoning?.supportedEfforts?.filter((effort) => THINKING_LEVELS.includes(effort));
190
210
  if (supported === void 0 || supported.length === 0) return void 0;
191
211
  const map = Object.fromEntries(THINKING_LEVELS.map((level) => [level, supported.includes(level) ? level : null]));
192
- if (info.reasoning?.canDisableThinking !== true) map.off = null;
212
+ if (info.reasoning?.canDisableThinking === true) map.off = "off";
213
+ else map.off = null;
193
214
  return map;
194
215
  }
195
216
  /** Build one pi-ai model descriptor pointing at the loopback shim. */
@@ -267,6 +288,14 @@ function toPiModel(info, baseUrl, providerId) {
267
288
  * text is empty but which carries `toolsAdded` still folds its TOOLS up; the
268
289
  * placeholder for the missing prompt then comes from `ensureSystemHead` at
269
290
  * the wire (the last of the three layers).
291
+ *
292
+ * LEGACY-ONLY. This is the inverse direction from {@link foldModernContext}:
293
+ * it produces the FLAT `{systemPrompt, tools, messages}` shape that ONLY
294
+ * pi-ai 0.85 reads. Never hand its result to a 1.x api — 1.x reads tools from
295
+ * `messages[].toolsAdded` and would see none (issues #25/#26). Type note: the
296
+ * parameter is the 0.85 `Context`; pi-ai 1.1.0 types the api's second parameter
297
+ * as `TranscriptContext`, so this function cannot be called with 1.x typings —
298
+ * that is intentional, and it is why the modern branch below does not touch it.
270
299
  */
271
300
  function adaptLegacyPiAiContext(context) {
272
301
  if (context === null || typeof context !== "object") return context;
@@ -330,30 +359,117 @@ function systemTextOf(message) {
330
359
  return texts.join("\n\n");
331
360
  }
332
361
  /**
362
+ * Fold a RAW context into the normalized transcript the modern generation
363
+ * consumes: a leading `system` message carrying the prompt AND the tool
364
+ * declarations in its `toolsAdded` field.
365
+ *
366
+ * WHY THIS IS NEEDED AT ALL (the 0.2.1-rc.1 regression). This used to be
367
+ * unnecessary: the modern branch returned pi-ai's api UNWRAPPED, on the belief
368
+ * that the modern generation normalized for itself. It does not.
369
+ * `dsh-llm-pi-ai` — both the 0.1.7 and the 0.2.1 lines — hands every provider a
370
+ * RAW envelope (`piContext()` builds `{systemPrompt, messages, tools}` and
371
+ * never calls `normalizeContext`), while:
372
+ *
373
+ * - pi-ai 0.85.x READS that raw shape (its request builder consults
374
+ * `context.systemPrompt` / `context.tools` directly), and
375
+ * - pi-ai 1.x READS ONLY `context.messages` (it resolves the transcript and
376
+ * collapses system messages; the top-level fields are consulted nowhere).
377
+ *
378
+ * On pi-ai 1.x a pass-through request therefore went upstream with NO system
379
+ * prompt and NO tool declarations, silently: the model stopped calling tools,
380
+ * and the domestic gateway tolerated the prompt-less body. Same failure class
381
+ * as issues #24/#25/#26, reached from the opposite side — 4.4.2 fixed "1.x
382
+ * misjudged as legacy and folded"; this fixes "1.x judged modern and NOT
383
+ * folded, though nothing had normalized".
384
+ *
385
+ * Built inline rather than by importing pi-ai's `normalizeContext`, which does
386
+ * not exist in 0.85.x: a static import would be `undefined` there, and this
387
+ * module deliberately never imports pi-ai statically (see `pi-ai-runtime.ts` —
388
+ * that doctrine is what lets the standalone `bin` run in a profile where the
389
+ * peer is never installed). The folded shape matches what `normalizeContext`
390
+ * produces for the same input: `{role:'system', content, toolsAdded, timestamp:0}`.
391
+ *
392
+ * The top-level `systemPrompt`/`tools` fields are DROPPED, exactly as
393
+ * `normalizeContext` drops them, so a raw transcript and an already-normalized
394
+ * one converge on the same object.
395
+ */
396
+ function foldModernContext(context) {
397
+ const prompt = context.systemPrompt;
398
+ const tools = context.tools;
399
+ const hasPrompt = prompt !== void 0 && prompt !== "";
400
+ const hasTools = Array.isArray(tools) && tools.length > 0;
401
+ if (!hasPrompt && !hasTools) return context;
402
+ const system = {
403
+ role: "system",
404
+ content: prompt ?? "",
405
+ ...hasTools ? { toolsAdded: tools } : {},
406
+ timestamp: 0
407
+ };
408
+ const { systemPrompt: _prompt, tools: _tools, ...rest } = context;
409
+ return {
410
+ ...rest,
411
+ messages: [system, ...context.messages]
412
+ };
413
+ }
414
+ /**
333
415
  * Wrap one pi-ai API module so both stream entry points receive a context this
334
- * build can actually consume (see {@link adaptLegacyPiAiContext}) — but only
335
- * when the module RESOLVED for this plugin is the legacy generation.
416
+ * generation can actually consume.
336
417
  *
337
418
  * The gate is the plugin's own module, resolved via {@link piAiRuntimeInfo}
338
- * (manifest version of the copy this plugin resolves — no pi-ai import): a
339
- * modern (0.87+) api consumes the normalized
340
- * transcript natively — its request builder reads the prompt from the leading
341
- * system message and the tools from that message's `toolsAdded`, and never
342
- * reads the top-level `systemPrompt`/`tools` fields at all. Folding for it
343
- * would delete the only carrier of both (issues #25/#26: the international
344
- * gateway answers the prompt-less body with 400 / 11128, the domestic one
345
- * accepts it and the model fabricates tool calls it can never actually make).
346
- * The unwrapped modern api is returned AS-IS — byte-for-byte pass-through.
419
+ * (manifest version of the copy this plugin resolves — no pi-ai import):
420
+ *
421
+ * - `legacy` (0.85) wants the flat `{systemPrompt, tools, messages}` shape, so
422
+ * a normalized transcript is UN-folded by {@link adaptLegacyPiAiContext}.
423
+ * - `modern` (0.87+, incl. 1.x) wants the normalized transcript, so a raw
424
+ * envelope is FOLDED by {@link foldModernContext}. Passing it through
425
+ * unnormalized is what silently deleted the prompt and the tools on pi-ai
426
+ * 1.x — the regression this branch now prevents.
427
+ *
428
+ * Both branches are idempotent, so a future seam that starts normalizing on its
429
+ * own side changes nothing here.
347
430
  *
348
431
  * The deferred-fetch entry points take no context and are passed through as-is
349
432
  * in both branches.
350
433
  */
434
+ /**
435
+ * Present an adapted context as the type the api's parameter currently expects.
436
+ *
437
+ * pi-ai 1.x BRANDS its provider-facing context (`TranscriptContext`, a
438
+ * `unique symbol` field) precisely so a raw `Context` cannot reach provider
439
+ * code by accident; the brand's stated sole producer is `normalizeContext()`.
440
+ * On a 1.x typings surface BOTH branches below receive that branded type, so
441
+ * merely returning a plain `Context` fails `tsc` with TS2345 — an error that
442
+ * only appears when the peer TYPES are 1.x, which is why the 0.85 checkout
443
+ * never saw it.
444
+ *
445
+ * The cast is a deliberate, documented assertion of exactly what the brand
446
+ * protects, and it is true after the call site has done the work:
447
+ *
448
+ * - modern: {@link foldModernContext} has already performed
449
+ * `normalizeContext`'s job — prompt and tools live on the leading system
450
+ * message, top-level fields gone;
451
+ * - legacy: {@link adaptLegacyPiAiContext} produced the FLAT 0.85 shape, which
452
+ * is only ever observed on a 0.85 checkout, where this type is a plain
453
+ * `Context` and the cast is an upcast.
454
+ *
455
+ * Do NOT "fix" this by importing `normalizeContext`: it does not exist in
456
+ * 0.85, so that import would be `undefined` at runtime on the very hosts this
457
+ * code must keep working — the same reason `pi-ai-runtime.ts` documents why
458
+ * this module never imports pi-ai statically.
459
+ */
460
+ function transcript(context) {
461
+ return context;
462
+ }
351
463
  function withLegacyContext(api, generation) {
352
- if (generation === "modern") return api;
464
+ if (generation === "modern") return {
465
+ ...api,
466
+ stream: (model, context, options) => api.stream(model, transcript(foldModernContext(context)), options),
467
+ streamSimple: (model, context, options) => api.streamSimple(model, transcript(foldModernContext(context)), options)
468
+ };
353
469
  return {
354
470
  ...api,
355
- stream: (model, context, options) => api.stream(model, adaptLegacyPiAiContext(context), options),
356
- streamSimple: (model, context, options) => api.streamSimple(model, adaptLegacyPiAiContext(context), options)
471
+ stream: (model, context, options) => api.stream(model, transcript(adaptLegacyPiAiContext(context)), options),
472
+ streamSimple: (model, context, options) => api.streamSimple(model, transcript(adaptLegacyPiAiContext(context)), options)
357
473
  };
358
474
  }
359
475
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-workbuddy",
3
3
  "displayName": "DSH Connect WorkBuddy",
4
4
  "description": "把本机登录的 WorkBuddy 模型接入 DeepSeek Harness,并提供只读的积分概览与模型管理。",
5
- "version": "4.6.7",
5
+ "version": "4.7.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
@@ -91,7 +91,7 @@
91
91
  "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
92
92
  "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
93
93
  "@deepseek-ai/schemastery": ">=3.18.1-rc.1 <4.0.0 || >=3.18.2 <4.0.0",
94
- "@earendil-works/pi-ai": ">=0.85.0 <0.88.0",
94
+ "@earendil-works/pi-ai": ">=0.85.0 <0.88.0 || >=1.0.0-0 <2.0.0",
95
95
  "react": "^18.2.0"
96
96
  },
97
97
  "devDependencies": {
@@ -110,7 +110,7 @@
110
110
  "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
111
111
  "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
112
112
  "@deepseek-ai/schemastery": ">=3.18.1 <4.0.0 || >=3.18.2 <4.0.0",
113
- "@earendil-works/pi-ai": ">=0.85.0 <0.88.0",
113
+ "@earendil-works/pi-ai": ">=0.85.0 <0.88.0 || >=1.0.0-0 <2.0.0",
114
114
  "@types/node": "^22.20.0",
115
115
  "@types/react": "~18.3.31",
116
116
  "@types/react-dom": "18.3.7",