From 2c90e2ebb0598fb2e3d4b31c78265eec8a2e6d71 Mon Sep 17 00:00:00 2001 From: vastsa Date: Sun, 4 Oct 2026 01:08:35 +0800 Subject: [PATCH 1/2] fix(pi): adapt runtime to Pi 1.0.1 Refresh the pinned Pi packages and rebase the Desktop compatibility patches on the published release. Preserve the verified DeepSeek Flash capability and limit fresh-release exceptions to exact package versions. --- .../electron/main/pi-model-metadata.ts | 3 +- apps/desktop/package.json | 4 +- .../test/oauth-standalone-bundle.test.mjs | 45 ++++- docs/project/README.md | 1 + docs/project/pi-101-adoption.md | 75 ++++++++ docs/spec/02-architecture/02-tech-stack.md | 6 +- .../06-pi-runtime-dependency-boundary.md | 6 +- docs/spec/03-runtime/02-agent-runtime.md | 4 +- .../03-runtime/11-provider-model-system.md | 2 +- docs/spec/03-runtime/21-image-generation.md | 2 +- docs/spec/06-delivery/04-e2e-test-plan.md | 4 +- .../spec/02-architecture/02-tech-stack.md | 6 +- .../zh-CN/spec/03-runtime/02-agent-runtime.md | 2 +- .../03-runtime/11-provider-model-system.md | 4 +- .../13-model-catalog-and-selection.md | 2 +- .../spec/03-runtime/21-image-generation.md | 2 +- packages/agent-runtime/package.json | 6 +- .../src/pi-upstream-1.0-contract.test.ts | 24 +++ ...earendil-works__pi-agent-core@1.0.1.patch} | 0 ...tch => @earendil-works__pi-ai@1.0.1.patch} | 15 +- ...rendil-works__pi-coding-agent@1.0.1.patch} | 2 +- pnpm-lock.yaml | 105 +++++------ pnpm-workspace.yaml | 18 +- scripts/check-pi-dependencies.mjs | 23 ++- scripts/check-pi-patches.mjs | 28 ++- scripts/e2e-agent-live.mjs | 165 ++++++++++++------ 26 files changed, 399 insertions(+), 155 deletions(-) create mode 100644 docs/project/pi-101-adoption.md rename patches/{@earendil-works__pi-agent-core@1.0.0.patch => @earendil-works__pi-agent-core@1.0.1.patch} (100%) rename patches/{@earendil-works__pi-ai@1.0.0.patch => @earendil-works__pi-ai@1.0.1.patch} (96%) rename patches/{@earendil-works__pi-coding-agent@1.0.0.patch => @earendil-works__pi-coding-agent@1.0.1.patch} (99%) diff --git a/apps/desktop/electron/main/pi-model-metadata.ts b/apps/desktop/electron/main/pi-model-metadata.ts index b07164373c..aa20fa1c64 100644 --- a/apps/desktop/electron/main/pi-model-metadata.ts +++ b/apps/desktop/electron/main/pi-model-metadata.ts @@ -67,7 +67,8 @@ export function relayChatMetadata(models: Models, id: string): Model | unde /** * Display-only compatibility data for speech/embedding/image settings rows that - * Pi 1.0.0 does not publish. It has no auth, prices, refresh, routing or dispatch. + * Pi's chat-provider records do not publish. It has no auth, prices, refresh, + * routing or dispatch. * Chat selection and typed Pi operation lookups never consult this data. */ export function settingsOperationMetadata(providerId: string, vendor?: string, id?: string): ModelInfo[] { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 07adcb08a6..9728f96ef4 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -36,8 +36,8 @@ "electron-updater": "^6.8.9" }, "devDependencies": { - "@earendil-works/pi-ai": "1.0.0", - "@earendil-works/pi-mcp": "1.0.0", + "@earendil-works/pi-ai": "1.0.1", + "@earendil-works/pi-mcp": "1.0.1", "@pi-desktop/agent-host": "workspace:*", "@pi-desktop/agent-runtime": "workspace:*", "@pi-desktop/host-runtime": "workspace:*", diff --git a/apps/desktop/test/oauth-standalone-bundle.test.mjs b/apps/desktop/test/oauth-standalone-bundle.test.mjs index 945f92ae5d..73e91c33e2 100644 --- a/apps/desktop/test/oauth-standalone-bundle.test.mjs +++ b/apps/desktop/test/oauth-standalone-bundle.test.mjs @@ -59,7 +59,28 @@ const exercise = String.raw` import assert from "node:assert/strict"; import http from "node:http"; import { syncBuiltinESMExports } from "node:module"; -http.Server.prototype.listen = function () { throw new Error("mock callback port unavailable"); }; +let callbackServer; +const originalAddress = http.Server.prototype.address; +http.Server.prototype.listen = function (port, host, callback) { + assert.equal(port, 1455); + assert.equal(host, "127.0.0.1"); + callbackServer = this; + queueMicrotask(() => callback?.()); + return this; +}; +http.Server.prototype.address = function () { + if (this === callbackServer) return { address: "127.0.0.1", family: "IPv4", port: 1455 }; + return originalAddress.call(this); +}; +http.Server.prototype.close = function (callback) { + if (this === callbackServer) { + callback?.(); + return this; + } + return Reflect.apply(originalClose, this, [callback]); +}; +const originalClose = http.Server.prototype.close; +http.Server.prototype.closeAllConnections = function () {}; syncBuiltinESMExports(); const { VendorOAuth, secretRefForProviderOauth } = await import("./oauth.mjs"); const secrets = new Map(); @@ -107,16 +128,28 @@ const deps = { if (!url.startsWith("https://auth.openai.com/")) return; authorization = new URL(url); hostIds.push(authorization.searchParams.get("ext_agent_host_id")); + const callback = new URL("http://127.0.0.1:1455/auth/callback"); + callback.searchParams.set("state", authorization.searchParams.get("state")); + callback.searchParams.set("code", "test-code"); + callback.searchParams.set("client_id", "test-issued-client"); + const requestHandler = callbackServer?.listeners("request")[0]; + assert.equal(typeof requestHandler, "function"); + await new Promise((resolve, reject) => { + requestHandler({ method: "GET", url: callback.pathname + callback.search }, { + writeHead(status) { + if (status !== 200) reject(new Error("unexpected OAuth callback response: " + status)); + }, + end() { + resolve(); + }, + }); + }); }, emit: (event) => { if (event.kind === "error") failed(new Error(event.message)); if (event.kind === "done") complete(event.providerId); if (event.kind === "prompt") { - const callback = new URL("http://127.0.0.1:1455/auth/callback"); - callback.searchParams.set("state", authorization.searchParams.get("state")); - callback.searchParams.set("code", "test-code"); - callback.searchParams.set("client_id", "test-issued-client"); - oauth.respond({ loginId: event.loginId, promptId: event.request.promptId, value: callback.href }); + assert.equal(event.request.type, "manual_code"); } }, }; diff --git a/docs/project/README.md b/docs/project/README.md index 3f6214541e..f65c09da1d 100644 --- a/docs/project/README.md +++ b/docs/project/README.md @@ -5,5 +5,6 @@ - Historical project board (archived; last refreshed 2026-08-11 for the 0.5.x line): [`BOARD.md`](BOARD.md) - Documentation/code alignment audit: [2026-07-30 audit](2026-07-30-docs-code-audit.md) - Plan implementation plan: [`plan-mode-implementation-plan.md`](plan-mode-implementation-plan.md) +- Pi 1.0.1 adoption: [`pi-101-adoption.md`](pi-101-adoption.md) - GitHub Issues + Milestones: repository Issues page - GitHub Projects: create after adding the `project` token scope diff --git a/docs/project/pi-101-adoption.md b/docs/project/pi-101-adoption.md new file mode 100644 index 0000000000..adf8998600 --- /dev/null +++ b/docs/project/pi-101-adoption.md @@ -0,0 +1,75 @@ +# Pi 1.0.1 adoption + +Status: task candidate on `fix/pi-1.0.1-adaptation`, based on the latest +`origin/main` at `7f1d7c1ce5f21a533c8910249bb6d22320d4581e`. Validation and +landing status are recorded below after candidate checks. + +## Scope + +All five direct Pi dependencies used by Agent Runtime and Electron Main are +pinned exactly to `1.0.1`. The three existing Desktop patches were rebased on +the published 1.0.1 package contents; the pnpm lockfile records the resulting +patch hashes and upstream package versions. + +| Workspace entry | Direct dependencies | +| --- | --- | +| `packages/agent-runtime` | `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent` | +| `apps/desktop` | `@earendil-works/pi-ai`, `@earendil-works/pi-mcp` | + +The 1.0.1 release adds retry classification for model-capacity errors in +`pi-ai`, updates Anthropic request handling and its SDK, and includes fixes for +Bedrock thinking state and OAuth callback port collisions. `pi-agent-core`' +runtime implementation is unchanged. `pi-coding-agent` and its transitive Pi +packages receive the 1.0.1 release updates. `pi-mcp` replaces the +`clientMetadataUrl` option with `clientMetadataDocument`; repository code does +not call that option directly, and the updated runtime consumes the package. + +No Host RPC, Plugin SDK, provider persistence, session format, or permission +contract changes. The desktop retry classifier already retries generic +provider failures. A regression contract test now confirms both that +classification and Pi's 1.0.1 native classifier treat “Selected model is at +capacity” as retryable. + +## Fresh-release policy + +At adoption time, Pi 1.0.1 and seven packages in its release group are inside +the configured minimum-release-age window. `pnpm-workspace.yaml` therefore +contains exact-version exclusions for those eight packages. The dependency +check rejects missing, additional, wildcard, or non-1.0.1 exclusions. Remove +these entries once the release group clears the age window and regenerate the +lockfile; do not broaden them. + +## Patch identity + +| Package | Patch SHA-256 | +| --- | --- | +| `@earendil-works/pi-agent-core@1.0.1` | `02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da` | +| `@earendil-works/pi-ai@1.0.1` | `b8c1b3aa54b7649346e1eb2fc225471b32e47a209bbda021704f26dd2647e603` | +| `@earendil-works/pi-coding-agent@1.0.1` | `ab63d8f7817d606be15d14daedd329df64bb34e73b71c6842b02f67643ba0769` | + +The `check:pi-dependencies` and `check:pi-patches` scripts verify exact direct +pins, installed package versions, patch mappings and lockfile identities, and +representative patched source and declaration contracts. + +## Validation + +Preliminary checks on the task working tree based on +`7f1d7c1ce5f21a533c8910249bb6d22320d4581e` passed below. They are not the +final candidate result: rerun the relevant checks after committing and +refreshing the branch against the newer `origin/main` before landing. + +- Frozen install, Pi pin/patch checks, and `pnpm audit --recursive --prod`. +- `pnpm build:js`, agent-runtime and desktop typechecks, and `pnpm lint`. +- Agent Runtime: 85 files / 1,163 tests. Desktop: 3,482 tests. +- `pnpm docs:check` and `pnpm check:agent-policy`. +- `node scripts/e2e-agent-live.mjs` using the configured `.env` model through + Pi Agent Runtime and candidate host-core: passed with the expected reply. +- `pnpm test:e2e` using the same `.env` model: 25/25 passed, including live + completion and streaming requests. +- `cargo build --locked -p host-core` using the shared host Cargo target. + +## Rollback + +Rollback the dependency manifests, lockfile, release-age entries, patch files, +and version references together. Do not roll back an individual patched Pi +package while retaining the others at 1.0.1. diff --git a/docs/spec/02-architecture/02-tech-stack.md b/docs/spec/02-architecture/02-tech-stack.md index c7f4b94388..d42d50ae8c 100644 --- a/docs/spec/02-architecture/02-tech-stack.md +++ b/docs/spec/02-architecture/02-tech-stack.md @@ -12,9 +12,9 @@ | Host backend | **Rust** | stable Rust toolchain | tools/plugins/permissions/persistence adapters | | Rust async | tokio | stable | host services | | Host RPC | stdio JSON-RPC (NDJSON) | frozen (D001) | Electron main ↔ Rust host | -| Agent engine | `@earendil-works/pi-agent-core` | 1.0.0 | agent loop | -| Model API | `@earendil-works/pi-ai` | 1.0.0 | provider adapters, OAuth, and stream handling | -| Model catalog | pi-ai Providers/Models | pinned 1.0.0 + explicit provider refresh | account-scoped metadata and typed operations | +| Agent engine | `@earendil-works/pi-agent-core` | 1.0.1 | agent loop | +| Model API | `@earendil-works/pi-ai` | 1.0.1 | provider adapters, OAuth, and stream handling | +| Model catalog | pi-ai Providers/Models | pinned 1.0.1 + explicit provider refresh | account-scoped metadata and typed operations | > pi-ai owns published metadata, native thinking support, transports and auth. > Host persists Desktop account rows, credentials, and explicit binding overrides. diff --git a/docs/spec/02-architecture/06-pi-runtime-dependency-boundary.md b/docs/spec/02-architecture/06-pi-runtime-dependency-boundary.md index 8fbfc374dd..15c2d8958a 100644 --- a/docs/spec/02-architecture/06-pi-runtime-dependency-boundary.md +++ b/docs/spec/02-architecture/06-pi-runtime-dependency-boundary.md @@ -9,10 +9,10 @@ boundaries must keep that split clear as upstream packages evolve. ## 2. Current ownership The sidecar pins `@earendil-works/pi-agent-core`, `pi-ai`, and -`pi-coding-agent` together at exactly `1.0.0`; Desktop's `pi-ai` and `pi-mcp` +`pi-coding-agent` together at exactly `1.0.1`; Desktop's `pi-ai` and `pi-mcp` development dependencies use the same release. Target-release package patches remain narrow compatibility deltas and are listed with their audited behavior -in `docs/project/pi-1000-patch-audit.md`. +in `docs/project/pi-101-adoption.md`. - `@earendil-works/pi-agent-core` supplies the agent loop and its stable agent, event, and tool types. Agent Runtime owns desktop-specific compaction, @@ -31,7 +31,7 @@ in `docs/project/pi-1000-patch-audit.md`. host-core checkpoint-compaction path, and durable session store do not use the package's AgentSession. -The extension shim is a versioned compatibility subset, not the full 1.0.0 +The extension shim is a versioned compatibility subset, not the full 1.0.1 coding-agent API. Its legacy `VERSION` marker remains sourced from `TRUSTED_EXTENSION_KERNEL_VERSION` and intentionally identifies the modeled 0.87.1 extension surface. diff --git a/docs/spec/03-runtime/02-agent-runtime.md b/docs/spec/03-runtime/02-agent-runtime.md index ccc989a632..230e520aff 100644 --- a/docs/spec/03-runtime/02-agent-runtime.md +++ b/docs/spec/03-runtime/02-agent-runtime.md @@ -1650,7 +1650,7 @@ with the original v3 `SessionManager`, Pi `ModelRuntime`, `SettingsManager`, and leaf, compaction, model/thinking changes, and context-bearing custom messages; it is never reconstructed from renderer `UiMessage` rows. -The Pi 1.0.0 SDK also applies append-only `context_edit` entries to this model +The Pi 1.0.1 SDK also applies append-only `context_edit` entries to this model projection. An edit can omit or replace an earlier message for later provider requests without rewriting its raw JSONL entry or the visible native history. Native Pi extensions use the SDK's boundary hooks; all entries they append, @@ -1720,7 +1720,7 @@ and never triggers a provider transport rebuild. Protocol errors such as `EPROTO` keep their existing retry behavior. See [certificate trust ADR](../../adr/provider-system-certificates.md). -## Pi 1.0.0 execution boundary +## Pi 1.0.1 execution boundary Published model metadata and account entitlement come from one account-scoped Pi Models collection. Effective binding projection is shared by launch, delegates diff --git a/docs/spec/03-runtime/11-provider-model-system.md b/docs/spec/03-runtime/11-provider-model-system.md index aac2860771..e86d6dbbea 100644 --- a/docs/spec/03-runtime/11-provider-model-system.md +++ b/docs/spec/03-runtime/11-provider-model-system.md @@ -464,7 +464,7 @@ same vendor key. ### Anthropic token endpoint rate limits -The pinned pi-ai 1.0.0 patch gives Anthropic authorization-code exchange and +The pinned pi-ai 1.0.1 patch gives Anthropic authorization-code exchange and refresh a shared, bounded token-request policy: retry only an explicit HTTP 429, at most three total requests. Wait at least 1 s then 2 s, or longer when `Retry-After` gives delta seconds or an HTTP date. A server delay beyond the diff --git a/docs/spec/03-runtime/21-image-generation.md b/docs/spec/03-runtime/21-image-generation.md index eb5682cde7..153afd9afb 100644 --- a/docs/spec/03-runtime/21-image-generation.md +++ b/docs/spec/03-runtime/21-image-generation.md @@ -123,7 +123,7 @@ previews, editing a generated file, collapsed results, and setup navigation. Live verification is opt-in via `scripts/test-image-generation-live.mjs`, limited to one generation plus one edit and never a default test command. -## Pi 1.0.0 operation boundary +## Pi 1.0.1 operation boundary Image generation and edits execute through account-scoped Pi `Models.generateImages`. Use native OpenRouter images or a registered compatible OpenAI-images adapter, diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index a01b7aecef..bd4b516122 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -3322,7 +3322,7 @@ identify the platform validation still needed. #### E2E-MCP-pi-client-owner-policy: Pi protocol under existing Desktop owners -- **Preconditions**: Pi 1.0.0 with the pinned host-policy patch; offline stdio and +- **Preconditions**: Pi 1.0.1 with the pinned host-policy patch; offline stdio and HTTP fixtures, no paid provider or user credentials. - **Steps**: Run `plugin-mcp.test.mjs`, `user-mcp.test.mjs`, `mcp-stdio-launch.test.mjs`, `mcp-call-registry.test.mjs`, `mcp-oauth.test.mjs`, @@ -16381,7 +16381,7 @@ renderer's durable transcript reads. No real model or provider is contacted. ## E2E-OAUTH-pi-installation-identity-and-standalone-load -- **Preconditions**: Pi 1.0.0; temporary Host secrets and account fixtures; +- **Preconditions**: Pi 1.0.1; temporary Host secrets and account fixtures; network/browser/callback I/O mocked; no user account or paid service. - **Steps**: Run `installation-identity.test.mjs`, `vendor-oauth-login.test.mjs` and `oauth-standalone-bundle.test.mjs` under `apps/desktop/test`. diff --git a/docs/zh-CN/spec/02-architecture/02-tech-stack.md b/docs/zh-CN/spec/02-architecture/02-tech-stack.md index e89dd76ba1..4560bb50fb 100644 --- a/docs/zh-CN/spec/02-architecture/02-tech-stack.md +++ b/docs/zh-CN/spec/02-architecture/02-tech-stack.md @@ -15,9 +15,9 @@ | 主机后端 | **Rust** | 稳定的 Rust 工具链 | tools/plugins/permissions/persistence 适配器 | | Rust 异步 | 东京 | 稳定 | 主机服务 | | 主机 RPC | stdio JSON-RPC (NDJSON) | 冷冻(D001) | Electron 主 ↔ Rust 主机 | -| Agent 引擎 | `@earendil-works/pi-agent-core` | 1.0.0 | Agent 循环及稳定的 agent/event/tool 类型 | -| 模型 API | `@earendil-works/pi-ai` | 1.0.0 | 提供商 | -| Model catalog | pi-ai Providers/Models | pinned 1.0.0 + explicit provider refresh | account-scoped metadata and typed operations | +| Agent 引擎 | `@earendil-works/pi-agent-core` | 1.0.1 | Agent 循环及稳定的 agent/event/tool 类型 | +| 模型 API | `@earendil-works/pi-ai` | 1.0.1 | 提供商 | +| Model catalog | pi-ai Providers/Models | pinned 1.0.1 + explicit provider refresh | account-scoped metadata and typed operations | > pi-ai 提供已发布的模型元数据、原生思考能力、传输和认证。 > Host 持有 Desktop 账户行、凭据和显式绑定覆盖。 diff --git a/docs/zh-CN/spec/03-runtime/02-agent-runtime.md b/docs/zh-CN/spec/03-runtime/02-agent-runtime.md index 63fdcd288c..03346fba5d 100644 --- a/docs/zh-CN/spec/03-runtime/02-agent-runtime.md +++ b/docs/zh-CN/spec/03-runtime/02-agent-runtime.md @@ -1171,7 +1171,7 @@ System/Direct/Custom 代理路由保持不变。 provider transport 重建。`EPROTO` 等协议错误继续使用原有重试行为。详见 [证书信任 ADR](../../../adr/provider-system-certificates.md)。 -## Pi 1.0.0 execution boundary +## Pi 1.0.1 execution boundary Published model metadata and account entitlement come from one account-scoped Pi Models collection. Effective binding projection is shared by launch, delegates diff --git a/docs/zh-CN/spec/03-runtime/11-provider-model-system.md b/docs/zh-CN/spec/03-runtime/11-provider-model-system.md index f86259229f..5e1738dc2b 100644 --- a/docs/zh-CN/spec/03-runtime/11-provider-model-system.md +++ b/docs/zh-CN/spec/03-runtime/11-provider-model-system.md @@ -174,7 +174,7 @@ PI-Desktop 不得把用户永久限制在一份简短的固定模型列表上。 ### 6.2 Catalog responsibilities -1. pi-ai 1.0.0 Providers/Models own published metadata, transport, thinking +1. pi-ai 1.0.1 Providers/Models own published metadata, transport, thinking support and native operation types. Electron's historically named `ModelsDevCatalog` is an account-aware adapter over this public API. 2. Startup is cache-only and disables ambient environment/file credentials. @@ -381,7 +381,7 @@ Codex CLI 版本(`CODEX_MODELS_CLIENT_VERSION`),账户模型缺失时调 ### Anthropic token 端点限流 -固定版本 pi-ai 1.0.0 的仓库补丁为 Anthropic 授权码交换与刷新提供同一套 +固定版本 pi-ai 1.0.1 的仓库补丁为 Anthropic 授权码交换与刷新提供同一套 有限策略:只重试明确的 HTTP 429,最多总共三次请求。先等待至少 1 秒、再 等待至少 2 秒;若 `Retry-After` 给出更长的秒数或 HTTP 日期,则遵守该时间。 服务器要求的等待超出剩余预算时结束本次尝试,不缩短等待后提前重试。 diff --git a/docs/zh-CN/spec/03-runtime/13-model-catalog-and-selection.md b/docs/zh-CN/spec/03-runtime/13-model-catalog-and-selection.md index b065dd87df..98a3d24187 100644 --- a/docs/zh-CN/spec/03-runtime/13-model-catalog-and-selection.md +++ b/docs/zh-CN/spec/03-runtime/13-model-catalog-and-selection.md @@ -136,7 +136,7 @@ type RecentModelRef = { ## 6. Refresh behavior -The pinned pi-ai 1.0.0 catalog is the startup baseline. Startup reads no remote +The pinned pi-ai 1.0.1 catalog is the startup baseline. Startup reads no remote catalog and uses no ambient credentials. `providers.refreshModelCatalog` invokes Pi's public refresh API; settings metadata lookups themselves are local. diff --git a/docs/zh-CN/spec/03-runtime/21-image-generation.md b/docs/zh-CN/spec/03-runtime/21-image-generation.md index d17453c7f5..e93a686958 100644 --- a/docs/zh-CN/spec/03-runtime/21-image-generation.md +++ b/docs/zh-CN/spec/03-runtime/21-image-generation.md @@ -58,7 +58,7 @@ `node scripts/e2e-image-chat.mjs` 在隔离桌面中使用本地模型/图片 HTTP 夹具,覆盖相邻默认设置、Composer 提交、批量结果、引用生成文件编辑、收起详情和配置跳转。真实接口验证通过 `scripts/test-image-generation-live.mjs` 显式启用,仅限一次生成和一次编辑,不属于默认测试命令。 -## Pi 1.0.0 operation boundary +## Pi 1.0.1 operation boundary Image generation and edits execute through account-scoped Pi `Models.generateImages`. Use native OpenRouter images or a registered compatible OpenAI-images adapter, diff --git a/packages/agent-runtime/package.json b/packages/agent-runtime/package.json index 3121a2924f..1dec93f734 100644 --- a/packages/agent-runtime/package.json +++ b/packages/agent-runtime/package.json @@ -27,9 +27,9 @@ "clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('dist-bundle',{recursive:true,force:true})\"" }, "dependencies": { - "@earendil-works/pi-agent-core": "1.0.0", - "@earendil-works/pi-ai": "1.0.0", - "@earendil-works/pi-coding-agent": "1.0.0", + "@earendil-works/pi-agent-core": "1.0.1", + "@earendil-works/pi-ai": "1.0.1", + "@earendil-works/pi-coding-agent": "1.0.1", "@pi-desktop/shared": "workspace:*", "jiti": "2.7.0", "typebox": "1.3.27", diff --git a/packages/agent-runtime/src/pi-upstream-1.0-contract.test.ts b/packages/agent-runtime/src/pi-upstream-1.0-contract.test.ts index 6626c92b76..238aad282b 100644 --- a/packages/agent-runtime/src/pi-upstream-1.0-contract.test.ts +++ b/packages/agent-runtime/src/pi-upstream-1.0-contract.test.ts @@ -3,6 +3,8 @@ import { convertResponsesMessages } from "@earendil-works/pi-ai/api/openai-respo import { retryProviderRequest } from "@earendil-works/pi-ai/utils/provider-retry"; import { isContextOverflow } from "@earendil-works/pi-ai/utils/overflow"; import { normalizeContext, type AssistantMessage, type Model } from "@earendil-works/pi-ai"; +import { isRetryableAssistantError } from "@earendil-works/pi-ai/compat"; +import { classifyProviderError, isTransientProviderRetryCode } from "./provider-retry.js"; const testModel: Model<"openai-responses"> = { id: "test-model", @@ -80,6 +82,28 @@ describe("Pi 1.0 upstream regression contracts", () => { } }); + it("keeps model-capacity failures retryable in Pi and Desktop runtimes", () => { + const message: AssistantMessage = { + role: "assistant", + content: [], + api: testModel.api, + provider: testModel.provider, + model: testModel.id, + usage: { + input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "error", + errorMessage: "Selected model is at capacity", + timestamp: 1, + }; + + expect(isRetryableAssistantError(message)).toBe(true); + const desktopError = classifyProviderError(message); + expect(desktopError).toMatchObject({ code: "PROVIDER_ERROR", retriable: true }); + expect(isTransientProviderRetryCode(desktopError.code)).toBe(true); + }); + it("recognizes the Z.AI context-overflow finish reason", () => { const message: AssistantMessage = { role: "assistant", diff --git a/patches/@earendil-works__pi-agent-core@1.0.0.patch b/patches/@earendil-works__pi-agent-core@1.0.1.patch similarity index 100% rename from patches/@earendil-works__pi-agent-core@1.0.0.patch rename to patches/@earendil-works__pi-agent-core@1.0.1.patch diff --git a/patches/@earendil-works__pi-ai@1.0.0.patch b/patches/@earendil-works__pi-ai@1.0.1.patch similarity index 96% rename from patches/@earendil-works__pi-ai@1.0.0.patch rename to patches/@earendil-works__pi-ai@1.0.1.patch index 7b8d647aae..62c0b05c35 100644 --- a/patches/@earendil-works__pi-ai@1.0.0.patch +++ b/patches/@earendil-works__pi-ai@1.0.1.patch @@ -1,5 +1,5 @@ diff --git a/dist/api/anthropic-messages.js b/dist/api/anthropic-messages.js -index 106f2b9617fa3b810261e067a3c965f209907af3..b4b92125484b40d54fc12aa4121a52f8ba9d6324 100644 +index 38571b31e143e913c2bf59dad73382982e93664a..27867e896c0ba13b843baddc6d2a4b87e5c8e4ab 100644 --- a/dist/api/anthropic-messages.js +++ b/dist/api/anthropic-messages.js @@ -1,3 +1,6 @@ @@ -205,7 +205,7 @@ index 106f2b9617fa3b810261e067a3c965f209907af3..b4b92125484b40d54fc12aa4121a52f8 function isOAuthToken(apiKey) { return apiKey.includes("sk-ant-oat"); } -@@ -936,6 +1011,15 @@ function buildParams(model, context, isOAuthToken, options) { +@@ -933,6 +1008,15 @@ function buildParams(model, context, isOAuthToken, options) { params.tools = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl); } } @@ -221,7 +221,7 @@ index 106f2b9617fa3b810261e067a3c965f209907af3..b4b92125484b40d54fc12aa4121a52f8 // Managed effort models always use adaptive thinking so prefix mismatches can // be dropped instead of surfacing as persistent 400 responses. if (model.compat?.supportsMidConvoEffort === true) { -@@ -1140,6 +1224,15 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE +@@ -1141,6 +1225,15 @@ convertToolDefinitions) { input: block.arguments ?? {}, }); } @@ -932,8 +932,15 @@ index 4fafddad713ac5811fe2b1dfea629b4b8f6627e1..2e4ed41e548807b836625b35ba753a84 +export * from "./utils/hosted-search.js"; //# sourceMappingURL=index.js.map \ No newline at end of file +diff --git a/dist/providers/data/deepseek.json b/dist/providers/data/deepseek.json +index 1443bb592b2913ba09579f3f555a2b09456e63cf..5fc0625778014863aa267ac5186db2cdf356d789 100644 +--- a/dist/providers/data/deepseek.json ++++ b/dist/providers/data/deepseek.json +@@ -1 +1 @@ +-{"openai-completions":{"chat:deepseek-flash":{"id":"deepseek-flash","name":"DeepSeek V4.1 Flash","api":"openai-completions","baseUrl":"https://api.deepseek.com","provider":"deepseek","reasoning":true,"thinkingLevelMap":{"minimal":null,"low":"low","medium":null,"high":"high","max":"max"},"input":["text","image"],"cost":{"input":0.3,"output":1.2,"cacheRead":0.006,"cacheWrite":0},"contextWindow":1000000,"maxTokens":384000,"compat":{"supportsStore":false,"supportsDeveloperRole":false,"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek","supportsStrictMode":true},"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","api":"openai-completions","baseUrl":"https://api.deepseek.com","provider":"deepseek","reasoning":true,"input":["text"],"cost":{"input":1.32,"output":3.96,"cacheRead":0.044,"cacheWrite":0},"contextWindow":1000000,"maxTokens":384000,"compat":{"supportsStore":false,"supportsDeveloperRole":false,"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek","supportsStrictMode":true,"supportsMidConvoSystemMessages":true},"thinkingLevelMap":{"minimal":null,"low":null,"medium":null,"high":"high","max":"max"},"type":"chat"}}} ++{"openai-completions":{"chat:deepseek-flash":{"id":"deepseek-flash","name":"DeepSeek V4.1 Flash","api":"openai-completions","baseUrl":"https://api.deepseek.com","provider":"deepseek","reasoning":true,"thinkingLevelMap":{"minimal":null,"low":"low","medium":null,"high":"high","max":"max"},"input":["text","image"],"cost":{"input":0.3,"output":1.2,"cacheRead":0.006,"cacheWrite":0},"contextWindow":1000000,"maxTokens":384000,"compat":{"supportsStore":false,"supportsDeveloperRole":false,"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek","supportsStrictMode":true,"supportsMidConvoSystemMessages":true},"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","api":"openai-completions","baseUrl":"https://api.deepseek.com","provider":"deepseek","reasoning":true,"input":["text"],"cost":{"input":1.32,"output":3.96,"cacheRead":0.044,"cacheWrite":0},"contextWindow":1000000,"maxTokens":384000,"compat":{"supportsStore":false,"supportsDeveloperRole":false,"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek","supportsStrictMode":true,"supportsMidConvoSystemMessages":true},"thinkingLevelMap":{"minimal":null,"low":null,"medium":null,"high":"high","max":"max"},"type":"chat"}}} diff --git a/dist/types.d.ts b/dist/types.d.ts -index 68aa0553de03aab4f7a47084995732f83ae4de00..d6d73b0caf995a5bc0b40456890d499dd39b625b 100644 +index b141efdc528c806524d40f097256f7173c12ba3b..d0007a3d5b0bd0a54fee93a3a26b1af7f5ce04a9 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -286,6 +286,30 @@ export interface ToolCall { diff --git a/patches/@earendil-works__pi-coding-agent@1.0.0.patch b/patches/@earendil-works__pi-coding-agent@1.0.1.patch similarity index 99% rename from patches/@earendil-works__pi-coding-agent@1.0.0.patch rename to patches/@earendil-works__pi-coding-agent@1.0.1.patch index 6ca162d5bd..2d2ff1bfda 100644 --- a/patches/@earendil-works__pi-coding-agent@1.0.0.patch +++ b/patches/@earendil-works__pi-coding-agent@1.0.1.patch @@ -1,5 +1,5 @@ diff --git a/dist/core/agent-session.js b/dist/core/agent-session.js -index 1da213f5fb33ff8d0e98ed044a1844c97463bcd3..a447789cfc1782cd7a0aaa96c2fc92b03d944ce6 100644 +index 33338d518743ba2fd4ee2d710c5496a6ddcd1856..7ea30773b6d3aa2e960fe8da753e0dd334c602d1 100644 --- a/dist/core/agent-session.js +++ b/dist/core/agent-session.js @@ -68,10 +68,10 @@ function withoutDeletedHeaders(headers) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89ae000894..8556b102f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,9 +199,9 @@ overrides: vite@5: '>=6.4.3 <7' patchedDependencies: - '@earendil-works/pi-agent-core@1.0.0': 02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da - '@earendil-works/pi-ai@1.0.0': 9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0 - '@earendil-works/pi-coding-agent@1.0.0': 2d46470684b572b37bba57cf3cec31d81cf7747be163812b74563c8be8a95da3 + '@earendil-works/pi-agent-core@1.0.1': 02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da + '@earendil-works/pi-ai@1.0.1': 0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981 + '@earendil-works/pi-coding-agent@1.0.1': ab63d8f7817d606be15d14daedd329df64bb34e73b71c6842b02f67643ba0769 importers: @@ -227,11 +227,11 @@ importers: version: 0.2.2 devDependencies: '@earendil-works/pi-ai': - specifier: 1.0.0 - version: 1.0.0(patch_hash=9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@6.28.0)(ws@8.21.1)(zod@4.4.3) + specifier: 1.0.1 + version: 1.0.1(patch_hash=0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@6.28.0)(ws@8.21.1)(zod@4.4.3) '@earendil-works/pi-mcp': - specifier: 1.0.0 - version: 1.0.0 + specifier: 1.0.1 + version: 1.0.1 '@pi-desktop/agent-host': specifier: workspace:* version: link:../../packages/agent-host @@ -419,14 +419,14 @@ importers: packages/agent-runtime: dependencies: '@earendil-works/pi-agent-core': - specifier: 1.0.0 - version: 1.0.0(patch_hash=02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) + specifier: 1.0.1 + version: 1.0.1(patch_hash=02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) '@earendil-works/pi-ai': - specifier: 1.0.0 - version: 1.0.0(patch_hash=9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) + specifier: 1.0.1 + version: 1.0.1(patch_hash=0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) '@earendil-works/pi-coding-agent': - specifier: 1.0.0 - version: 1.0.0(patch_hash=2d46470684b572b37bba57cf3cec31d81cf7747be163812b74563c8be8a95da3)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(ws@8.21.1)(zod@4.4.3) + specifier: 1.0.1 + version: 1.0.1(patch_hash=ab63d8f7817d606be15d14daedd329df64bb34e73b71c6842b02f67643ba0769)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(ws@8.21.1)(zod@4.4.3) '@pi-desktop/shared': specifier: workspace:* version: link:../shared @@ -661,8 +661,8 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@anthropic-ai/sdk@0.124.0': - resolution: {integrity: sha512-cN5O8i9UVxHeOQAzj/XjshWXG8KiibJDw9OGpH2Z/eR3n/RBxdoLxDJOcfqAJWvjaMDFfHTBADU04hWRJVkDyA==} + '@anthropic-ai/sdk@0.129.0': + resolution: {integrity: sha512-MH7LB20kNpGLUpPTe2OG5XvL/KhX8HUO9XyBaFjgFk6TLS4Xjt0VPIuMKywe7POKKPslX+QxSlVok9e+8kG5Nw==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -938,38 +938,38 @@ packages: search-insights: optional: true - '@earendil-works/chord@1.0.0': - resolution: {integrity: sha512-BIfWfrByM0pKq6tfKYXuwx0ed2CvaqIM3EDA7Dps+fP+d3CiPWdlHi1cDsJC05llqKJoRz5//G0hz0yQwwjISQ==} + '@earendil-works/chord@1.0.1': + resolution: {integrity: sha512-woq15kjUZ38fUIMqFrFzTeT0fYYM0CfGS2ELCUE5Ufni32tdxfs0Av2+zz8PXFNxyiC3SB1EnyGPp63CLtp8Fg==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-agent-core@1.0.0': - resolution: {integrity: sha512-bHFONjtEBDqiV+g1DmmtkSlfAaqHELr+XO+6I5Nah4gswwZrLJO4yZB/JjsnlI4AKWTayBLfgS6DCbeBBz9e2Q==} + '@earendil-works/pi-agent-core@1.0.1': + resolution: {integrity: sha512-os85rJM2hgCOOLdtcQ5WxRRhAbQiTNq9+48/pj6dOUU7czJhU8NTdHmDs41hBfAXR/ZQgstIgrjowEICEXimbQ==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-ai@1.0.0': - resolution: {integrity: sha512-3/W1vdDaVtpeMd23ElvJC12HLA5yS/BGqqcXF+0SK082dN7cbgNcCwguTBRBC258Ke8SzSvUW1B75iAf8w8IxA==} + '@earendil-works/pi-ai@1.0.1': + resolution: {integrity: sha512-eSA53pdfDLuQTTJn3yz1VC8BBmcX33OKkk8WihOXdVBvbgqqC4zuR6Sf+TeeWuAmh8LuqARoK14R1s2HZqVcsw==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-codemode@1.0.0': - resolution: {integrity: sha512-LPpFI4+T9NzDnhBDs15izWAolaoM8xnwqdziRd6Zx8BQeoEPvzefD2vMMzSyF0rOtq34TfQqkZ0ki16f6cGdMg==} + '@earendil-works/pi-codemode@1.0.1': + resolution: {integrity: sha512-RpZKpdKceYmIODfqKLJtZWUvfkbDGmEHxEEEYN+i21OFm8uY0sTcBMP4oaLf6SkBVBMPae1Z9GtW27+dIRAYPw==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-coding-agent@1.0.0': - resolution: {integrity: sha512-/FtbxoSQU/mEv1QnichJjRjqteqaIaMWxmhB4G367+MwZfX7/DI5B9YAg5lqbN7nztFskBEtUSZ+FlmMBECtMw==} + '@earendil-works/pi-coding-agent@1.0.1': + resolution: {integrity: sha512-B7FGYpHpBPvS+Ux16CbCuVnE9S4v6c2h6ykocPNarC4msD/4JM/eFrCrO2wbRkedJZ25myFozSWHvCOx4QnN+w==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-mcp@1.0.0': - resolution: {integrity: sha512-rYra0aF5iPmJd+fsB+VBuqxWNABGJ3Iiyhg0CqIc/91ta2n7IvQmCp0Cac/YkUTNnvRsVZnjgAiBkRi6+ouWuw==} + '@earendil-works/pi-mcp@1.0.1': + resolution: {integrity: sha512-XuhcCpNT9FgsMQTzjmwy2hbakg9CODcDHtC+KeHfr37HjKdj4QsfOrOThxLXYRN4kmC5HDvFyLzthAnHe/T4jw==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-telemetry@1.0.0': - resolution: {integrity: sha512-WjNBj5TYIiPZFQEz2WlULcDwPLaKwIlmsKjVeYM+LJSbnSp38kWsHUJysIDGnu23IcLbKoPtywsvYfUJCeZePA==} + '@earendil-works/pi-telemetry@1.0.1': + resolution: {integrity: sha512-SuJ/4KyqZ6j6Whlau710DmusWDKMWCxKXpWqZclY/Cl3tGUuX8IFmbTbW2VRoVuoJpZYVMg6DJmt1mwHMUt9uw==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-tui@1.0.0': - resolution: {integrity: sha512-JsT7kXnpZA2YOtQu6RyriyxEO0eJIzPyfiH09bH+OLN5+s18HYkwaUD/tBkjhnSfMu6/50CQPRYJagzSP6HdPw==} + '@earendil-works/pi-tui@1.0.1': + resolution: {integrity: sha512-Rk/pWLoDKWI7WvywhLxf+DTYppS7XWTN5IacpqlD+QF+CbrT/y5CCnAHqPEoF41y+XtQJKbNjjzUuJE4yq0Dfg==} engines: {node: '>=22.19.0'} '@electron-internal/extract-zip@1.0.5': @@ -4940,7 +4940,7 @@ snapshots: package-manager-detector: 1.8.0 tinyexec: 1.2.4 - '@anthropic-ai/sdk@0.124.0(zod@4.4.3)': + '@anthropic-ai/sdk@0.129.0(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 standardwebhooks: 1.1.1 @@ -5321,13 +5321,13 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@earendil-works/chord@1.0.0': + '@earendil-works/chord@1.0.1': dependencies: esbuild: 0.28.2 - '@earendil-works/pi-agent-core@1.0.0(patch_hash=02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3)': + '@earendil-works/pi-agent-core@1.0.1(patch_hash=02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3)': dependencies: - '@earendil-works/pi-ai': 1.0.0(patch_hash=9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) + '@earendil-works/pi-ai': 1.0.1(patch_hash=0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) typebox: 1.3.27 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' @@ -5342,11 +5342,11 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@1.0.0(patch_hash=9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@6.28.0)(ws@8.21.1)(zod@4.4.3)': + '@earendil-works/pi-ai@1.0.1(patch_hash=0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@6.28.0)(ws@8.21.1)(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.124.0(zod@4.4.3) + '@anthropic-ai/sdk': 0.129.0(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1127.0 - '@earendil-works/pi-telemetry': 1.0.0 + '@earendil-works/pi-telemetry': 1.0.1 '@google/genai': 2.21.0(supports-color@7.2.0) '@smithy/node-http-handler': 4.12.1 http-proxy-agent: 9.1.0(supports-color@7.2.0) @@ -5367,11 +5367,11 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@1.0.0(patch_hash=9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3)': + '@earendil-works/pi-ai@1.0.1(patch_hash=0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.124.0(zod@4.4.3) + '@anthropic-ai/sdk': 0.129.0(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1127.0 - '@earendil-works/pi-telemetry': 1.0.0 + '@earendil-works/pi-telemetry': 1.0.1 '@google/genai': 2.21.0(supports-color@7.2.0) '@smithy/node-http-handler': 4.12.1 http-proxy-agent: 9.1.0(supports-color@7.2.0) @@ -5392,19 +5392,20 @@ snapshots: - ws - zod - '@earendil-works/pi-codemode@1.0.0': + '@earendil-works/pi-codemode@1.0.1': dependencies: quickjs-wasi: 3.6.2 - '@earendil-works/pi-coding-agent@1.0.0(patch_hash=2d46470684b572b37bba57cf3cec31d81cf7747be163812b74563c8be8a95da3)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(ws@8.21.1)(zod@4.4.3)': + '@earendil-works/pi-coding-agent@1.0.1(patch_hash=ab63d8f7817d606be15d14daedd329df64bb34e73b71c6842b02f67643ba0769)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(ws@8.21.1)(zod@4.4.3)': dependencies: - '@earendil-works/chord': 1.0.0 - '@earendil-works/pi-agent-core': 1.0.0(patch_hash=02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) - '@earendil-works/pi-ai': 1.0.0(patch_hash=9a4038bcac99877bfda2690848abf7801f5b325d28872b15eedd1f4ca8619ba0)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) - '@earendil-works/pi-codemode': 1.0.0 - '@earendil-works/pi-mcp': 1.0.0 - '@earendil-works/pi-tui': 1.0.0 + '@earendil-works/chord': 1.0.1 + '@earendil-works/pi-agent-core': 1.0.1(patch_hash=02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) + '@earendil-works/pi-ai': 1.0.1(patch_hash=0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981)(@aws-sdk/credential-provider-node@3.972.83)(@smithy/signature-v4@5.7.3)(supports-color@7.2.0)(undici@8.10.2)(ws@8.21.1)(zod@4.4.3) + '@earendil-works/pi-codemode': 1.0.1 + '@earendil-works/pi-mcp': 1.0.1 + '@earendil-works/pi-tui': 1.0.1 '@silvia-odwyer/photon-node': 0.3.4 + brace-expansion: 5.0.12 chalk: 6.0.0 cross-spawn: 7.0.6 diff: 8.0.4 @@ -5432,13 +5433,13 @@ snapshots: - ws - zod - '@earendil-works/pi-mcp@1.0.0': + '@earendil-works/pi-mcp@1.0.1': dependencies: cross-spawn: 7.0.6 - '@earendil-works/pi-telemetry@1.0.0': {} + '@earendil-works/pi-telemetry@1.0.1': {} - '@earendil-works/pi-tui@1.0.0': + '@earendil-works/pi-tui@1.0.1': dependencies: get-east-asian-width: 1.6.0 marked: 18.0.11 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8107aa3dde..42cf760637 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,6 +12,18 @@ allowBuilds: # koffi (FFI of transcribe-cpp) loads the prebuilt @koromix/koffi- # binary; its from-source install script was never approved and is not needed. koffi: false +# pnpm records exact-version exclusions for the current, explicitly requested +# Pi 1.0.1 release while it is inside the minimum release age window. Keep these +# entries exact; do not widen them to package names or ranges. +minimumReleaseAgeExclude: + - '@earendil-works/chord@1.0.1' + - '@earendil-works/pi-agent-core@1.0.1' + - '@earendil-works/pi-ai@1.0.1' + - '@earendil-works/pi-codemode@1.0.1' + - '@earendil-works/pi-coding-agent@1.0.1' + - '@earendil-works/pi-mcp@1.0.1' + - '@earendil-works/pi-telemetry@1.0.1' + - '@earendil-works/pi-tui@1.0.1' # Advisory fixes for transitives whose parents still resolve the vulnerable # version. Drop each one once its parent ships a release that resolves the # patched version on its own. @@ -41,9 +53,9 @@ overrides: # every range to its floor — silently downgrading dependencies instead of # refreshing them. Pin the intent: `pnpm update` means "newest within range". patchedDependencies: - '@earendil-works/pi-agent-core@1.0.0': patches/@earendil-works__pi-agent-core@1.0.0.patch - '@earendil-works/pi-ai@1.0.0': patches/@earendil-works__pi-ai@1.0.0.patch - '@earendil-works/pi-coding-agent@1.0.0': patches/@earendil-works__pi-coding-agent@1.0.0.patch + '@earendil-works/pi-agent-core@1.0.1': patches/@earendil-works__pi-agent-core@1.0.1.patch + '@earendil-works/pi-ai@1.0.1': patches/@earendil-works__pi-ai@1.0.1.patch + '@earendil-works/pi-coding-agent@1.0.1': patches/@earendil-works__pi-coding-agent@1.0.1.patch resolutionMode: highest # shamefully-hoist also lives in .npmrc, but pnpm 12 applies it only from # here; without it public hoisting is empty and examples/ plugins cannot diff --git a/scripts/check-pi-dependencies.mjs b/scripts/check-pi-dependencies.mjs index 162cace731..c31cc6acf5 100644 --- a/scripts/check-pi-dependencies.mjs +++ b/scripts/check-pi-dependencies.mjs @@ -3,7 +3,7 @@ import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const targetVersion = "1.0.0"; +const targetVersion = "1.0.1"; function readJson(path) { return JSON.parse(readFileSync(join(root, path), "utf8")); @@ -53,10 +53,23 @@ for (const packageName of ["pi-agent-core", "pi-ai", "pi-coding-agent", "pi-mcp" } const workspace = readFileSync(join(root, "pnpm-workspace.yaml"), "utf8"); const releaseAgeExclusions = workspace.match( - /^minimumReleaseAgeExclude:[ \t]*([\s\S]*?)(?=^[A-Za-z][\w-]*:|\s*$)/m, + /^minimumReleaseAgeExclude:[ \t]*\r?\n((?:[ \t]+-[^\r\n]*\r?\n)*)/m, )?.[1] ?? ""; -if (/@earendil-works\/(?:chord|pi-agent-core|pi-ai|pi-telemetry|pi-coding-agent|pi-codemode|pi-mcp|pi-tui)@/.test(releaseAgeExclusions)) { - throw new Error("Remove the obsolete Pi-specific minimumReleaseAgeExclude entries"); +const actualReleaseAgeExclusions = [...releaseAgeExclusions.matchAll(/^[ \t]+-[ \t]*['"]?([^'"\s]+)['"]?[ \t]*$/gm)] + .map((match) => match[1]) + .sort(); +const expectedReleaseAgeExclusions = [ + "@earendil-works/chord@1.0.1", + "@earendil-works/pi-agent-core@1.0.1", + "@earendil-works/pi-ai@1.0.1", + "@earendil-works/pi-codemode@1.0.1", + "@earendil-works/pi-coding-agent@1.0.1", + "@earendil-works/pi-mcp@1.0.1", + "@earendil-works/pi-telemetry@1.0.1", + "@earendil-works/pi-tui@1.0.1", +].sort(); +if (JSON.stringify(actualReleaseAgeExclusions) !== JSON.stringify(expectedReleaseAgeExclusions)) { + throw new Error("Pi minimumReleaseAgeExclude entries must match only the exact 1.0.1 release packages"); } -process.stdout.write("Pi direct pins and installed package instances are aligned at 1.0.0.\n"); +process.stdout.write(`Pi direct pins and installed package instances are aligned at ${targetVersion}.\n`); diff --git a/scripts/check-pi-patches.mjs b/scripts/check-pi-patches.mjs index 951d9f8a79..2164f4f812 100644 --- a/scripts/check-pi-patches.mjs +++ b/scripts/check-pi-patches.mjs @@ -3,22 +3,23 @@ import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const targetVersion = "1.0.1"; const entries = [ { name: "@earendil-works/pi-agent-core", - patch: "patches/@earendil-works__pi-agent-core@1.0.0.patch", + patch: `patches/@earendil-works__pi-agent-core@${targetVersion}.patch`, packagePath: "packages/agent-runtime/node_modules/@earendil-works/pi-agent-core", markers: ["hosted_search_update", "localRequestErrorDetails"], }, { name: "@earendil-works/pi-ai", - patch: "patches/@earendil-works__pi-ai@1.0.0.patch", + patch: `patches/@earendil-works__pi-ai@${targetVersion}.patch`, packagePath: "packages/agent-runtime/node_modules/@earendil-works/pi-ai", markers: ["hostedSearch", "withLocalRequestErrors", "AnthropicOAuthTokenError", "Retry-After"], }, { name: "@earendil-works/pi-coding-agent", - patch: "patches/@earendil-works__pi-coding-agent@1.0.0.patch", + patch: `patches/@earendil-works__pi-coding-agent@${targetVersion}.patch`, packagePath: "packages/agent-runtime/node_modules/@earendil-works/pi-coding-agent", markers: ["hostedSearchReplayProjection", "estimateProjectedContextTokens"], }, @@ -35,17 +36,30 @@ for (const entry of entries) { for (const marker of entry.markers) { if (!patchText.includes(marker)) throw new Error(`${entry.patch} is missing audited behavior marker ${marker}`); } - const workspaceMapping = `'${entry.name}@1.0.0': ${entry.patch}`; + const workspaceMapping = `'${entry.name}@${targetVersion}': ${entry.patch}`; if (!workspace.includes(workspaceMapping)) throw new Error(`pnpm-workspace.yaml does not map ${entry.name} to ${entry.patch}`); const packagePath = join(root, entry.packagePath); const resolved = realpathSync(packagePath); const patchHash = resolved.match(/patch_hash=([a-f0-9]+)/)?.[1]; - if (!patchHash || !lockfile.includes(`${entry.name}@1.0.0(patch_hash=${patchHash}`)) { - throw new Error(`${entry.name}@1.0.0 installed patch hash is absent from pnpm-lock.yaml`); + if (!patchHash || !lockfile.includes(`${entry.name}@${targetVersion}(patch_hash=${patchHash}`)) { + throw new Error(`${entry.name}@${targetVersion} installed patch hash is absent from pnpm-lock.yaml`); } } const piAiPatch = readFileSync(join(root, entries[1].patch), "utf8"); +const patchedDeepseekCatalogLine = piAiPatch + .split("\n") + .find((line) => line.startsWith('+{"openai-completions":') && line.includes('"chat:deepseek-flash"')); +if (!patchedDeepseekCatalogLine) { + throw new Error("Pi AI patch is missing its DeepSeek Flash transcript-capability record"); +} +const patchedDeepseekCatalog = JSON.parse(patchedDeepseekCatalogLine.slice(1)); +if ( + patchedDeepseekCatalog["openai-completions"]?.["chat:deepseek-flash"]?.baseUrl !== "https://api.deepseek.com" || + patchedDeepseekCatalog["openai-completions"]?.["chat:deepseek-flash"]?.compat?.supportsMidConvoSystemMessages !== true +) { + throw new Error("Pi AI patch must scope mid-conversation system support to the published DeepSeek Flash endpoint"); +} for (const declarationPath of [ "dist/index.d.ts", "dist/types.d.ts", @@ -65,4 +79,4 @@ if (!codingAgentPatch.includes("+export declare function estimateProjectedContex throw new Error("Pi coding-agent runtime estimator declaration is missing from its patch"); } -process.stdout.write("All three Pi 1.0.0 patches are mapped, locked, installed, and contain the audited contracts.\n"); +process.stdout.write(`All three Pi ${targetVersion} patches are mapped, locked, installed, and contain the audited contracts.\n`); diff --git a/scripts/e2e-agent-live.mjs b/scripts/e2e-agent-live.mjs index 8f4e77e115..18b653b021 100644 --- a/scripts/e2e-agent-live.mjs +++ b/scripts/e2e-agent-live.mjs @@ -1,15 +1,11 @@ -import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; -import { mkdtempSync, rmSync, existsSync } from "node:fs"; +import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; -import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; +import { join } from "node:path"; import { DesktopAgentRuntime } from "../packages/agent-runtime/dist/runtime.js"; -import { HostClient } from "../packages/agent-runtime/dist/host-client.js"; import { PROTOCOL_VERSION } from "../packages/shared/dist/protocol.js"; +import { Host, resolveHostBinary } from "./e2e/host.mjs"; -const root = join(dirname(fileURLToPath(import.meta.url)), ".."); -const hostBin = join(root, "target/debug/pi-desktop-host-core"); const dataDir = mkdtempSync(join(tmpdir(), "pi-agent-live-")); // No defaults on purpose: this script sends a real prompt with a real key, so // the endpoint and model must be chosen explicitly by whoever runs it. @@ -28,58 +24,125 @@ const API_KEY = process.env.PI_DESKTOP_TEST_API_KEY; const BASE_URL = process.env.PI_DESKTOP_TEST_BASE_URL; const MODEL = process.env.PI_DESKTOP_TEST_MODEL; -if (!existsSync(hostBin)) { - console.error(`missing host bin at ${hostBin}; run 'cargo build -p host-core' first`); +let hostBin; +try { + hostBin = resolveHostBinary(); +} catch (error) { + console.error(error instanceof Error ? error.message : "host-core binary missing"); rmSync(dataDir, { recursive: true, force: true }); process.exit(1); } -const host = new HostClient(hostBin, { PI_DESKTOP_DATA_DIR: dataDir }); -await host.call("app.handshake", { protocolVersion: PROTOCOL_VERSION }); -const provider = await host.call("providers.create", { - name: "Live", - baseUrl: BASE_URL, - defaultModelId: MODEL, - secretValue: API_KEY, - type: "openai_compatible", - protocol: "openai_compatible", - authKind: "api_key_and_base_url", -}); - const events = []; -const runtime = new DesktopAgentRuntime({ - host, - sessionId: randomUUID(), - mode: "chat", - provider: { - id: provider.provider.id, +const host = new Host(hostBin, dataDir); +let runtime; +let durableTurnId; +try { + await host.start(PROTOCOL_VERSION); + const provider = await host.call("providers.create", { name: "Live", baseUrl: BASE_URL, - modelId: MODEL, - apiKey: API_KEY, - }, - onEvent: (e) => { - events.push(e.event.type); - if (e.event.type === "message_update" && e.event.deltaText) { - process.stdout.write(e.event.deltaText); + defaultModelId: MODEL, + secretValue: API_KEY, + type: "openai_compatible", + protocol: "openai_compatible", + authKind: "api_key_and_base_url", + }); + const session = await host.call("session.create", { + title: "Pi live E2E", + mode: "agent", + projectPath: process.cwd(), + }); + const turn = await host.call("session.beginTurn", { sessionId: session.session.id }); + durableTurnId = turn.turnId; + const userMessageId = randomUUID(); + const prompt = "Reply with exactly: hello-from-pi-desktop"; + await host.call("session.appendMessage", { + sessionId: session.session.id, + turnId: durableTurnId, + message: { + id: userMessageId, + role: "user", + content: prompt, + createdAt: new Date().toISOString(), + status: "complete", + }, + }); + + runtime = new DesktopAgentRuntime({ + host, + sessionId: session.session.id, + turnId: durableTurnId, + mode: "agent", + provider: { + id: provider.provider.id, + name: "Live", + baseUrl: BASE_URL, + modelId: MODEL, + apiKey: API_KEY, + authKind: "api_key_and_base_url", + apiStyle: "chat_completions", + supportsReasoning: false, + supportedThinkingLevels: ["off"], + }, + thinkingLevel: "off", + commandShell: { id: "bash", label: "Bash", dialect: "posix", available: true, isDefault: true }, + onEvent: (e) => { + events.push(e.event); + if (e.event.type === "message_update" && e.event.deltaText) { + process.stdout.write(e.event.deltaText); + } + if (e.event.type === "message_end" && e.event.message.role === "assistant") { + process.stdout.write("\n"); + console.log("assistant:", e.event.message.content.slice(0, 200)); + } + if (e.event.type === "error") { + console.error("error event", e.event.error); + } + }, + }); + + console.log("prompting…"); + await runtime.prompt(prompt, userMessageId, durableTurnId); + for (const event of events) { + if (event.type === "message_end" && event.message.role === "assistant") { + await host.call("session.appendMessage", { + sessionId: session.session.id, + turnId: durableTurnId, + message: event.message, + }); } - if (e.event.type === "message_end" && e.event.message.role === "assistant") { - process.stdout.write("\n"); - console.log("assistant:", e.event.message.content.slice(0, 200)); + } + const response = events.findLast( + (event) => event.type === "message_end" && event.message.role === "assistant", + )?.message.content; + const ok = typeof response === "string" && + response.toLowerCase().includes("hello-from-pi-desktop") && + !events.some((event) => event.type === "error"); + await host.call("session.endTurn", { + turnId: durableTurnId, + status: ok ? "completed" : "error", + createNotification: false, + }); + durableTurnId = undefined; + console.log(ok ? "PASS E2E-agent-live" : "FAIL E2E-agent-live"); +} finally { + await runtime?.dispose(); + try { + if (durableTurnId) { + await host.call("session.endTurn", { + turnId: durableTurnId, + status: "error", + createNotification: false, + }); } - if (e.event.type === "error") { - console.error("error event", e.event.error); + } finally { + try { + await host.stop(); + } finally { + rmSync(dataDir, { recursive: true, force: true }); } - }, -}); - -console.log("prompting…"); -await runtime.prompt("Reply with exactly: hello-from-pi-desktop"); -await runtime.dispose(); -await host.dispose(); -rmSync(dataDir, { recursive: true, force: true }); + } +} -const ok = events.includes("message_end") || events.includes("agent_end"); -console.log("events:", events.join(" > ")); -console.log(ok ? "PASS E2E-agent-live" : "FAIL E2E-agent-live"); -process.exit(ok ? 0 : 1); +console.log("events:", events.map((event) => event.type).join(" > ")); From d95e3aa503a1b567c3ac8e1ddf68ce60ffc3207e Mon Sep 17 00:00:00 2001 From: vastsa Date: Sun, 4 Oct 2026 01:23:39 +0800 Subject: [PATCH 2/2] docs(project): record Pi 1.0.1 qualification Record the refreshed main base and candidate validation so the new runtime dependency can be reviewed and landed against known test evidence. --- docs/project/pi-101-adoption.md | 40 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/project/pi-101-adoption.md b/docs/project/pi-101-adoption.md index adf8998600..111f2c5665 100644 --- a/docs/project/pi-101-adoption.md +++ b/docs/project/pi-101-adoption.md @@ -1,8 +1,8 @@ # Pi 1.0.1 adoption -Status: task candidate on `fix/pi-1.0.1-adaptation`, based on the latest -`origin/main` at `7f1d7c1ce5f21a533c8910249bb6d22320d4581e`. Validation and -landing status are recorded below after candidate checks. +Status: task candidate commit `2c90e2ebb0598fb2e3d4b31c78265eec8a2e6d71` +on `fix/pi-1.0.1-adaptation`, based on `origin/main` at +`3b036cc7810e18b3ef7689a2b93385125a8d0a3f`. ## Scope @@ -44,7 +44,7 @@ lockfile; do not broaden them. | Package | Patch SHA-256 | | --- | --- | | `@earendil-works/pi-agent-core@1.0.1` | `02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da` | -| `@earendil-works/pi-ai@1.0.1` | `b8c1b3aa54b7649346e1eb2fc225471b32e47a209bbda021704f26dd2647e603` | +| `@earendil-works/pi-ai@1.0.1` | `0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981` | | `@earendil-works/pi-coding-agent@1.0.1` | `ab63d8f7817d606be15d14daedd329df64bb34e73b71c6842b02f67643ba0769` | The `check:pi-dependencies` and `check:pi-patches` scripts verify exact direct @@ -53,20 +53,24 @@ representative patched source and declaration contracts. ## Validation -Preliminary checks on the task working tree based on -`7f1d7c1ce5f21a533c8910249bb6d22320d4581e` passed below. They are not the -final candidate result: rerun the relevant checks after committing and -refreshing the branch against the newer `origin/main` before landing. - -- Frozen install, Pi pin/patch checks, and `pnpm audit --recursive --prod`. -- `pnpm build:js`, agent-runtime and desktop typechecks, and `pnpm lint`. -- Agent Runtime: 85 files / 1,163 tests. Desktop: 3,482 tests. -- `pnpm docs:check` and `pnpm check:agent-policy`. -- `node scripts/e2e-agent-live.mjs` using the configured `.env` model through - Pi Agent Runtime and candidate host-core: passed with the expected reply. -- `pnpm test:e2e` using the same `.env` model: 25/25 passed, including live - completion and streaming requests. -- `cargo build --locked -p host-core` using the shared host Cargo target. +| Candidate | `2c90e2ebb0598fb2e3d4b31c78265eec8a2e6d71` | +| --- | --- | +| Base main | `3b036cc7810e18b3ef7689a2b93385125a8d0a3f` | +| E2E suites | Pi Agent Runtime live request; protocol-level `pnpm test:e2e` | +| Result | Both passed; smoke suite 25/25 passed, 0 skipped | +| Environment | `.env`-configured model and host-core built from the candidate source; output redacted API key and endpoint. | + +- `pnpm install --frozen-lockfile --ignore-scripts`, `pnpm check:pi-dependencies`, + `pnpm check:pi-patches`, and `pnpm audit --recursive --prod` passed; audit + found no known vulnerabilities. +- `pnpm build:js`, agent-runtime and desktop typechecks, and `pnpm lint` passed. +- Agent Runtime: 93 files / 1,234 tests. Desktop: 3,484 tests; no failures or + skipped tests. +- `pnpm docs:check` and `pnpm check:agent-policy` passed. +- `node scripts/e2e-agent-live.mjs` returned the expected + `hello-from-pi-desktop` response. +- `pnpm test:e2e` passed 25/25, including live completion and streaming. +- `cargo build --locked -p host-core` passed using the shared host Cargo target. ## Rollback