`, minting stays in the carrier layer ([layering and RPC protocol note](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)).
+- **rpcId is strictly bidirectional**: the initiator mints, the responder echoes, and minting stays in Connection ([unary Remote migration](../../.agents/notes/implemented/architecture/2026-08-10-unary-apiproxy-remote-migration.md)).
- **Notifier publication discipline**: `notifyNow` is only the direct echo of a user gesture; structural updates use microtask-batched `markDirty`, while visible streaming chunks use cumulative `markFrameDirty`. See `../api/session-controller/src/client/sessions/notifier.ts`.
- **The web layer is pure presentation.** Nothing that is only "how to draw" enters the session log. Tool cards derive in the Client from raw call/result events and persisted result metadata; process-local control state uses its own snapshots and frames. Unknown or malformed tool data falls back to the generic form. A new *model-visible* input still requires a session event (repo-wide rule).
diff --git a/packages/client/connection/README.i18n.yaml b/packages/client/connection/README.i18n.yaml
index 75ab67d3de..6be2c4aa21 100644
--- a/packages/client/connection/README.i18n.yaml
+++ b/packages/client/connection/README.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/connection/README.md
-README.md: 72f8ee150c1879f920f189ad3d0221fce449c451
-README.zh.md: 3ef1e22554f568e5d4fe24556fafbc7ce0413209
+README.md: af757608aa7face6854aaf9d103654f974b51ed4
+README.zh.md: fef7248abe1e19595b90c43a6a1b9abf0aafa88a
diff --git a/packages/client/connection/README.md b/packages/client/connection/README.md
index 72f8ee150c..af757608aa 100644
--- a/packages/client/connection/README.md
+++ b/packages/client/connection/README.md
@@ -1,5 +1,5 @@
---
-description: "Browser-host wire layer for the web GUI: the shared API client, event-stream delivery with reconnect, the /api HTTP bridge, and the browser-trust fence, for users and maintainers composing or debugging the connection."
+description: "Browser-host wire layer for the web GUI: Remote RPC, event-stream delivery with reconnect, exact Fetch routes, the /api HTTP bridge, and the browser-trust fence."
kind: "package-reference"
---
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-Protocol and connection-generation layer. The Client plugin mounts `ctx.connection`, containing the shared API client, current-page loopback state, generation-scoped observable `hostDescription`, a generic RPC carrier, and the registration point for one generation source and the connection loop. A generation publishes `hostDescription` and calls `onConnected` only after its source is ready and `host.describe` succeeds; source completion, failure, withdrawal, or an explicit stop clears that value before `ConnectionController` reconnects with backoff.
+The package carries browser-to-Host Remote calls, exact Fetch responses, and connection generations. The Client plugin mounts `ctx.connection` with current-page loopback state, a generic RPC carrier, the active generation and its Host facts, and the registration point for one generation source. A generation becomes visible when its source reports ready; source completion, failure, withdrawal, or an explicit stop clears it before `ConnectionController` reconnects with backoff.
## Table of Contents
@@ -25,7 +25,7 @@ Protocol and connection-generation layer. The Client plugin mounts `ctx.connecti
## Use this package
-The browser uses HTTP POST for API Proxy and generic Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; in-process compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half owns the sole `/api` route, Fetch bridge, browser authentication, and Host/Origin checks. Typert Gateway claims its Remote endpoints first, and unclaimed requests fall through to API Proxy. Loopback hostname classification remains package-internal to the browser-facing Client state.
+The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; in-process compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half owns the sole `/api` route, Fetch bridge, browser authentication, Host/Origin checks, and exact `GET`/`HEAD` route registry. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state.
-----
@@ -41,9 +41,9 @@ Before authentication, every request still passes `src/api-request-trust.ts`. It
## Connection generation
-API Gateway Client registers the internal `$events` logical stream as the sole generation source, independently of whether any `$on` listener exists. The Host attaches all incremental listeners in the API Remotes source factory, then sends one `{ type: 'ready' }` item before events. `ConnectionController` waits for that item and `host.describe` in parallel; `onConnected` cannot start baseline reads until both succeed, so baseline acquisition cannot race ahead of incremental observation.
+API Gateway Client registers the internal `$events` logical stream as the sole generation source, independently of whether any `$on` listener exists. The Host attaches all incremental listeners in the API Remotes source factory, then sends one `{ type: 'ready', clientId, host: { home } }` item before events. `ConnectionController` publishes that generation and calls `onConnected` only after the ready item arrives, so baseline acquisition cannot race ahead of incremental observation.
-An ended `$events` stream, a Remote stream error, a non-ready opening item, or a malformed event item invalidates the current generation. The controller immediately withdraws `hostDescription`, publishes `reconnecting`, and rebuilds the `$events` plus `host.describe` handshake after backoff. Gateway mux reconnects the physical WebSocket; Connection generation reopens the logical stream and establishes the next baseline starting point.
+An ended `$events` stream, a Remote stream error, a non-ready opening item, or a malformed event item invalidates the current generation. The controller immediately withdraws the generation, publishes `reconnecting`, and reopens `$events` after backoff. Gateway mux reconnects the physical WebSocket; Connection generation reopens the logical stream and establishes the next baseline starting point.
## Model Experience
diff --git a/packages/client/connection/README.zh.md b/packages/client/connection/README.zh.md
index 3ef1e22554..fef7248abe 100644
--- a/packages/client/connection/README.zh.md
+++ b/packages/client/connection/README.zh.md
@@ -1,5 +1,5 @@
---
-description: "面向用户与维护者的浏览器-宿主线层说明:共享 API 客户端、带重连的事件流投递、/api HTTP 桥与浏览器信任栅栏,用于组合或排查连接。"
+description: "Web GUI 的浏览器-Host 线层:Remote RPC、带重连的事件流投递、精确 Fetch 路由、/api HTTP 桥与浏览器信任栅栏。"
kind: "package-reference"
---
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
-协议与连接世代层:Client 插件挂载 `ctx.connection`,包含共享 API 客户端、当前页面的 loopback 状态、按 generation 生效的可观察 `hostDescription`、通用 RPC carrier,以及单一 generation source 与连接循环的注册面。每个 generation 只在 source 已就绪且 `host.describe` 成功后发布 `hostDescription` 并调用 `onConnected`;source 结束、失败、被撤回或显式 stop 都会清空该值,再由 `ConnectionController` 退避重连。
+本包承载浏览器到 Host 的 Remote 调用、精确 Fetch 响应与 connection generation。Client 插件挂载 `ctx.connection`,其中包含当前页面的 loopback 状态、通用 RPC carrier、当前 generation 及其 Host 信息,以及单一 generation source 的注册点。source 报告 ready 后 generation 才可见;source 结束、失败、被撤回或显式 stop 都会清空它,再由 `ConnectionController` 退避重连。
## 目录
@@ -25,7 +25,7 @@ kind: "package-reference"
## 使用本包
-浏览器通过 HTTP POST 执行 API Proxy 一元调用与通用 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。进程内组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 拥有唯一 `/api` route、Fetch bridge、浏览器认证与 Host/Origin 校验;Typert Gateway 先认领自己的 Remote endpoint,未认领的请求再回退 API Proxy。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。
+浏览器通过 HTTP POST 执行 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。进程内组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 拥有唯一 `/api` route、Fetch bridge、浏览器认证、Host/Origin 校验与精确 `GET`/`HEAD` 路由注册表。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。
-----
@@ -41,9 +41,9 @@ cookie 签名密钥是 `ctx.credentials` 中由 `client-connection/browser-sessi
## Connection generation
-API Gateway Client 把内部 `$events` logical stream 注册为唯一 generation source,与有无 `$on` 订阅无关。Host 在 API Remotes source factory 同步挂好所有增量 listener 后,先发送唯一 `{ type: 'ready' }` 项,再发送事件。`ConnectionController` 并行等待该 ready 与 `host.describe`;只有两者都成功才允许 `onConnected` 启动 baseline 读取,因此 baseline 不会跑在增量 listener 前面。
+API Gateway Client 把内部 `$events` logical stream 注册为唯一 generation source,与有无 `$on` 订阅无关。Host 在 API Remotes source factory 同步挂好所有增量 listener 后,先发送唯一 `{ type: 'ready', clientId, host: { home } }` 项,再发送事件。`ConnectionController` 仅在收到该 ready 项后发布 generation 并调用 `onConnected`,因此 baseline 不会跑在增量 listener 前面。
-`$events` 结束、返回 Remote stream error、收到非 ready 首项或畸形事件项,都会使当前 generation 失效。Controller 立即撤回 `hostDescription`、发布 `reconnecting`,并在退避后重建 `$events` 与 `host.describe` 握手。Gateway mux 自己负责重建底层 WebSocket;Connection 世代负责重建 logical stream 与 baseline 起点。
+`$events` 结束、返回 Remote stream error、收到非 ready 首项或畸形事件项,都会使当前 generation 失效。Controller 立即撤回 generation、发布 `reconnecting`,并在退避后重开 `$events`。Gateway mux 自己负责重建底层 WebSocket;Connection generation 负责重开 logical stream 并建立下一次 baseline 起点。
## 模型体验
diff --git a/packages/client/connection/package.json b/packages/client/connection/package.json
index 2be6924acf..f19f30e40d 100644
--- a/packages/client/connection/package.json
+++ b/packages/client/connection/package.json
@@ -1,7 +1,7 @@
{
"name": "@deepseek-ai/dsh-client-connection",
- "description": "Wire consumer layer: HTTP client, generation lifecycle, and fixture API",
- "version": "0.1.1-rc.2",
+ "description": "Authenticated RPC transport, generation lifecycle, and browser fixture",
+ "version": "0.1.2-alpha.1",
"publishConfig": {
"access": "public"
},
@@ -38,7 +38,8 @@
},
"license": "MIT",
"dependencies": {
- "@deepseek-ai/schemastery": "workspace:^"
+ "@deepseek-ai/schemastery": "workspace:^",
+ "zod": "^4.4.3"
},
"files": [
"lib/index.js",
@@ -51,7 +52,7 @@
"@deepseek-ai/dsh-attachment": "workspace:^",
"@deepseek-ai/dsh-commands": "workspace:^",
"@deepseek-ai/dsh-credentials": "workspace:^",
- "@deepseek-ai/dsh-host-apiproxy": "workspace:^",
+ "@deepseek-ai/dsh-brand": "workspace:^",
"@deepseek-ai/dsh-host-directory-picker": "workspace:^",
"@deepseek-ai/dsh-host-webserver": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
@@ -65,7 +66,7 @@
"@deepseek-ai/dsh-attachment": "workspace:^",
"@deepseek-ai/dsh-commands": "workspace:^",
"@deepseek-ai/dsh-credentials": "workspace:^",
- "@deepseek-ai/dsh-host-apiproxy": "workspace:^",
+ "@deepseek-ai/dsh-brand": "workspace:^",
"@deepseek-ai/dsh-host-directory-picker": "workspace:^",
"@deepseek-ai/dsh-host-webserver": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
diff --git a/packages/client/connection/src/client/api.ts b/packages/client/connection/src/client/api.ts
index 4d4826da79..44e7595f34 100644
--- a/packages/client/connection/src/client/api.ts
+++ b/packages/client/connection/src/client/api.ts
@@ -1,46 +1,26 @@
-// Central contract re-export point: every legacy API contract import inside
-// the Connection package goes through this browser-safe file.
-// Types and runtime protocol helpers/bounds come from the apiproxy api/ layer
-// (zero Node deps, browser-safe); AbstractApiClient is the client boundary.
-// NEVER import the package root: it drags bootHost/cordis into the browser bundle.
-// The ./api and ./client subpath exports are the browser-safe channels.
+/** Browser-safe Connection protocol and shared application value exports. */
export type {
- ApiProxy, HostApi,
- ResponseValue,
- SkillsApi, SkillEntry,
- ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
- ModelReasoningEffort, ModelSelection,
- SettingsApi,
- ConfigurableProviderView, DiscoveredModelView, LlmApi,
-} from '@deepseek-ai/dsh-host-apiproxy/api'
-export type {
- RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
- ClientRequest, ServerResponse, RpcMessage,
-} from '@deepseek-ai/dsh-host-apiproxy/api'
-// transportError lives in the apiproxy api layer (beside RpcResult, its
-// subject); re-exported here so connection consumers keep one contract
-// entry point.
-export {
- RpcId,
- transportError,
-} from '@deepseek-ai/dsh-host-apiproxy/api'
-export { AbstractApiClient } from '@deepseek-ai/dsh-host-apiproxy/client'
-export type { IApiClient } from '@deepseek-ai/dsh-host-apiproxy/client'
+ ClientRequest,
+ RpcError,
+ RpcErrorCode,
+ RpcMessage,
+ RpcRequest,
+ RpcResponse,
+ RpcResult,
+ ServerResponse,
+} from '../rpc.ts'
+export { RpcId, transportError } from '../rpc.ts'
export type { SessionId, SessionEvent } from '@deepseek-ai/dsh-session/types'
export type { MessageId } from '@deepseek-ai/dsh-llm/brand'
export type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm/types'
-/** Successful value returned by the connection-generation host handshake. */
-export type HostDescription = import('@deepseek-ai/dsh-host-apiproxy/api').ResponseValue<'host.describe'>
-
-import type { RpcResponse, RpcResult } from '@deepseek-ai/dsh-host-apiproxy/api'
+import type { RpcResponse, RpcResult } from '../rpc.ts'
/**
- * Unwrap a unary response: RpcResponse