Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-8-authoring

The composer's preset seat stays removed here — this layer moved it to the
hero chip and the session-header action — so only the model seat takes the
`modelSeatLocked` narrowing master introduced.

Conflicts:
	apps/web/tests/snapshots/*/*.expected.md
	packages/client/connection/README.md
	packages/client/connection/README.zh.md
	packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
	packages/host/apiproxy/src/fetch/client.ts
This commit is contained in:
Yichen Jiang
2026-08-08 15:09:37 +08:00
649 changed files with 21094 additions and 2851 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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 docs/api-gateway.md
api-gateway.md: e8aafc173dced3c4ead07421d92401411565ece6
api-gateway.zh.md: 92681b72ffc573cde834cece19568ec3f1d515ce

170
docs/api-gateway.md Normal file
View File

@@ -0,0 +1,170 @@
# API Gateway
English | [中文](api-gateway.zh.md)
This is the current-state reference for the TypeRT API Gateway. It describes how business services declare unary Remote methods, how the build generates Host and Client contracts, and how calls reuse the Connection RPC and `/api` route. Session events, incremental data, and other streaming protocols are outside this document's scope; they may use the same Connection but do not use Remote method descriptors.
## Programming model
Business services use `@Remote` or `@RemoteScope` to select the methods exposed to the Client. Unmarked methods do not enter the generated Client types or runtime contributions and cannot be called through `ctx.remote`.
`@Remote` denotes calling a Cordis service registered on the root Host Context. Complex Host objects cannot cross the wire directly; the business package must declare their association with a wire identity through `TypeRTLookupMap` and register a default resolution provider with `ctx.typert.lookups` at runtime. For example, an `Agent` parameter named `agent` in the Host signature produces an `agentId` wire field, and the Gateway resolves that id to a Host object before invoking the business method. Host composition can use `ctx.typert.lookups.configure()` to override the resolution policy for a lookup key without changing the parameter name, wire field, or canonical type symbol owned by the business package.
`@RemoteScope(key)` first resolves an identity to a scoped Context through `ctx.typert.contexts`, then obtains the service from that Context and invokes the method. It applies when the method itself depends on scoped composition and does not need to receive objects such as `Agent` explicitly.
Services normally extend `GatewayService` so the constructor explicitly binds the Cordis service key and default Remote namespace. A service that already has another base class can instead declare `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`; both forms leave an inspectable public binding and do not depend on the compiler injecting a symbol into the constructor.
```ts
import type { Agent } from '@deepseek-ai/dsh-agent'
import { GatewayService, Remote, RemoteScope } from '@deepseek-ai/dsh-type-meta'
import type { Context } from 'cordis'
export interface CreateGoalRequest {
objective: string
}
export interface CreateGoalResult {
accepted: boolean
}
export class GoalService extends GatewayService {
constructor(ctx: Context) {
super(ctx, 'goals')
}
@Remote('create')
createForClient(
agent: Agent,
request: CreateGoalRequest,
signal: AbortSignal,
): CreateGoalResult {
signal.throwIfAborted()
return this.create(agent, request)
}
@RemoteScope('agent', 'current')
currentForClient(): CreateGoalResult {
return { accepted: true }
}
private create(_agent: Agent, request: CreateGoalRequest): CreateGoalResult {
return { accepted: request.objective.length > 0 }
}
}
```
Remote methods may return a value synchronously or return a Promise. For cooperative cancellation, the final parameter in the Host signature must be `signal: AbortSignal` using the global type; it is recorded in the descriptor instead of entering `args`, while the generated Client method accepts an optional final `AbortSignal`.
The Client uses concrete functions on ordinary objects, not a JavaScript Proxy. Direct and scoped calls appear under `ctx.remote.<namespace>` and `agentCtx.remote.<namespace>`. Each namespace is a traced Cordis child Service registered as `remote.<namespace>`; the Client assembly mounts contributions through `ctx.remote.$mount()`, and the namespace unloads after its last method is withdrawn. Dependency declarations belong to the actual caller: only a business package that reads `ctx.remote.<namespace>` or `agentCtx.remote.<namespace>` declares both `remote` and `remote.<namespace>` in its own `inject`; assemblies that only mount contributions and higher-level runtimes that do not call that namespace do not declare the namespace dependency on the business package's behalf. When an `@Remote` method has exactly one lookup parameter and a same-named `TypeRTContextMap` uses the same wire identity, the generated scoped signature omits that identity parameter. `@RemoteScope` generates only the scoped invocation interface.
```ts ignore-check
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import type { AgentContext } from '@deepseek-ai/dsh-client-runtime/client'
import type { Context } from 'cordis'
import type {} from '@deepseek-ai/dsh-api-remotes/client'
export const inject = ['remote', 'remote.goals']
declare const ctx: Context
declare const agentCtx: AgentContext
declare const agentId: SessionId
await ctx.remote.goals.create(agentId, { objective: 'ship it' })
await agentCtx.remote.goals.create({ objective: 'ship it' })
```
Client applications assemble only `@deepseek-ai/dsh-api-remotes`. That package imports the `/remote` subpaths of selected business packages as runtime values, mounts their contributions through `ctx.remote.$mount()`, and re-exports the declaration merges from the same files. Adding a Host Remote package is an explicit choice by the Client composition owner; business components do not need to load the TypeRT Gateway or the business package's Remote JS separately.
A future TUI can assemble the same React-independent `api-remotes` and `ctx.remote` contract, so the Host methods visible to it are likewise limited to the Remote methods selected at generation time. This document does not define or implement the TUI composition.
## Component responsibilities
| Location | Package or entry | Responsibility |
|---|---|---|
| Shared | `@deepseek-ai/dsh-type-meta` | Declares decorators, Gateway bindings, merge-extensible protocol maps, invocation descriptors, and provider types; starts no TypeScript analysis and registers no Cordis services |
| Build | `@deepseek-ai/dsh-typert-generator` | Strictly analyzes Remote signatures, the type graph, lookups, Contexts, and source locations from the Host `ts.Program`, then generates Host and Host-for-Client artifacts |
| Host | `@deepseek-ai/dsh-typert-registry` and Loader | Places generated Host descriptors, schemas, and business-package registrations in `ctx.typert`, and holds lookup and Context providers |
| Host | `@deepseek-ai/dsh-api-remotes` | Owns the application Agent/Session identity policy and configures the corresponding TypeRT lookups |
| Host | `@deepseek-ai/dsh-api-gateway` | Provides `ctx.typertGateway`, claims Remote endpoints, resolves objects or Contexts, invokes live Cordis services, and validates boundaries |
| Client | `@deepseek-ai/dsh-api-gateway/client` | Provides `ctx.remote` and `remote.<namespace>` child Services, mounts generated descriptors as concrete methods, and initiates, validates, and cancels calls through the Connection |
| Client | `@deepseek-ai/dsh-api-remotes/client` | Explicitly selects and mounts the `/remote` contributions allowed by the application and brings the corresponding declaration merges into business code |
| Both | `@deepseek-ai/dsh-client-connection` | Provides the RPC carrier, request correlation, trust boundary, cancellation, response envelope, and current `/api` HTTP bridge |
The API Gateway package owns the Host dispatcher and Client Remote endpoint as peer entries, but the two builds never enter the same `ts.Program`. The Host entry does not import the Client Cordis `Context` merge, and the Client entry does not import the Host Gateway service.
## Strict generation pipeline
The root build runs `build:lib:host`, `build:lib:client`, and `build:web` in order. The Host lib phase first runs `tsc -b tsconfig.host.json`, then `tsdown --env.DSH_BUILD_FACE host`; the normal Host Project Reference graph compiles the TypeRT generator, which runs during this tsdown pass with the Host aggregate as its only `ts.Program` seed. The Client lib phase then runs `tsc -b tsconfig.client.json` and `tsdown --env.DSH_BUILD_FACE client`, consuming the newly generated Remote Client declarations and runtime contributions without starting TypeRT again.
Both tsdown passes receive the complete workspace and bundle only JavaScript emitted to `lib/types` by the corresponding tsc phase. The root config does not scan Client artifacts, classify package names, or pass a maintained filter to tsdown; package-local configs return entries for the current phase based on `DSH_BUILD_FACE`. An ordinary Client plugin produces both its Node loader entry and browser bundle during the Client phase.
`api-remotes` is the only package with split TypeScript faces. Its Host project owns the Agent/Session lookup policy, while its Client project depends on `/remote` declarations generated for business packages during Host tsdown; root aggregates and direct consumers must reference `api/remotes/tsconfig.host.json` or `api/remotes/tsconfig.client.json` respectively. The package's `clientBundle(..., { hostPhase: true })` produces its Host entry during Host tsdown and leaves only the browser entry for Client tsdown. Every other package remains registered in one aggregate.
Each contributing business package writes generated files to its own `lib/` directory, not to its source directory:
| File | Consumer | Contents |
|---|---|---|
| `typert.host.js` | Host Loader | Runtime reflection for the Host face, strict invocation descriptors, and schema registration values |
| `typert.host.d.ts` | Host type system | Generated declarations for the Host face |
| `typert.remote-client.js` | `api-remotes` | A mountable `TypeRTRemoteContribution` containing strict descriptors and runtime codecs |
| `typert.remote-client.d.ts` | Client type system | Declaration merges for `TypeRTRemoteNamespaceMap` and `TypeRTRemoteScopeMap`, plus Client-safe type references |
| `typert.remote-client.d.ts.map` | Editor | Maps generated method properties back to Remote method declarations in the Host package |
Business packages expose the Host Loader entry through `./typert` and the Host-for-Client entry through `./remote`. The generator also validates these package exports and published-file lists; it generates artifacts only for explicit contribution packages that provide the corresponding entry.
Parameter names in Remote Client declarations come from wire fields, while parameter and return types reference Client-safe types exported by the original business package. The declaration map resolves the generated property behind `ctx.remote.goals.create` back to the Host source method marked with `@Remote`, so editors that support declaration maps can navigate from a Client call to the real implementation instead of stopping at the generated `.d.ts`.
Strict analysis requires a Remote to be a public, non-static instance method with a concrete implementation. The method cannot be generic; parameters must be required, named simple identifiers and cannot use destructuring, default values, rest parameters, or optional parameters. TypeRT generates strict schemas for ordinary JSON-representable types; complex objects such as workspace classes must have a unique `TypeRTLookupMap` declaration. Lookup and Context packages are responsible for both static declaration merges and runtime provider registration; if either side is missing, the build or earliest resolvable runtime boundary fails.
## Runtime invocation
Remote and API Proxy currently share the Connection's `/api` route; there is no separate `/api2` server or second Connection. The Client Remote calls `connection.rpc.call('/api', '<namespace>/<method>', { args }, signal)`; the current HTTP carrier maps this to `POST /api/<namespace>/<method>`, with a payload containing only a named `args` object.
The Connection performs the unified trust check for `/api` before the HTTP bridge, then dispatches inside the shared FetchHandler in interceptor order. The TypeRT Gateway claims only two-segment endpoints that have a strict descriptor or active SRC marker; unclaimed requests fall back to the existing API Proxy. The Connection owns transport, RPC ids, response envelopes, and request cancellation, while the Gateway owns only the Remote data protocol and business dispatch. Replacing the Connection carrier in the future does not require changes to Remote descriptors or the Client programming interface.
For every call, the Gateway resolves the descriptor and live service from the current registries instead of caching business objects. It requires the fields in `args` to match the descriptor exactly, validates wire values with codecs, resolves objects or receivers through registered lookup or Context providers, invokes the service method targeted by the binding, and validates the return value. A missing provider, unknown identity, binding mismatch, missing or extra argument, schema failure, or missing method fails at the boundary before entering or after leaving business code.
The lookup provider's `register()` supplies both the stable declaration and the default resolver; `configure()` supplies a resolver owned by Host composition that may execute asynchronously and is scoped to an effect lifetime. Configuration may precede provider mounting; without a provider, invocation still fails with `lookup-unavailable`, and unloading the configuration restores the provider's default policy. API Remotes owns the standard `agentFor()` semantics for `agent` and `session`: it reuses a live Agent, automatically resumes ordinary cold sessions, deduplicates concurrent resumes, and rejects identities owned by subagent routing; the `session` lookup returns that Agent's Session. The Web API Proxy supplies its Agent defaults and scope setup, then consumes the same resolver for legacy methods. Resume failures and ownership fences pass through unchanged as existing RPC errors rather than being collapsed into the Gateway's `internal` error.
Unloading a Client contribution removes its descriptors and concrete methods together, aborts its in-flight calls, and makes stale method handles retained by external code reject further calls. A strict endpoint withdrawn on the Host also does not degrade to SRC inference, preventing a hot unload from silently weakening validation.
## SRC development fallback
When the Host starts from source through `node --import tsx/esm`, it does not execute the TypeRT compiler plugin. Standard decorator initializers still record the method name and invocation mode in a module-private `WeakMap`, while `GatewayService` or `bindTypeRTGateway()` supplies the explicit service binding; the Gateway can therefore construct a weaker temporary descriptor without starting a `ts.Program`.
The SRC fallback parses simple parameter names from the live function. When a parameter name matches the `parameter` of a registered lookup, such as `agent` or `session`, it uses the lookup's `agentId` or `sessionId` wire field and resolves the object on the Host; other parameters are checked only for cycle-free, JSON-safe data with no special prototype. `@RemoteScope` directly uses the wire field of a registered Host Context provider. SRC does not read TypeScript types, generate Zod schemas, infer optional parameters, or support destructuring, default values, rest parameters, or duplicate parameter names.
SRC solves only dispatch for a Host process running from source. The Client does not discover decorators from the running Host, and the Client Remote refuses to mount SRC descriptors that lack strict codecs; its types, codecs, and Remote registration values always come from the most recently generated `lib/typert.remote-client.*` artifacts.
## Development mode
A complete build generates Host contracts before compiling the Host, Client, and Web, so it is the deterministic entry for creating or refreshing all artifacts:
```sh
pnpm run build
```
Web development normally starts the source Host after one complete build and runs the Client plugin watcher in another terminal:
```sh
pnpm run dsh -- web --dev
pnpm run dev:web
```
`dsh` starts the Host source through tsx, so the Host can use the SRC fallback; `dev:web` watches only Client plugins with a `dshClient` declaration and rewrites their `lib/client.js`. It does not analyze Host decorators or generate Remote Client DTS.
Changing only a Remote method's implementation body without changing its contract does not require regenerating the TypeRT files. After adding or removing a decorator or changing an export name, namespace, parameter, return value, lookup, Context, or cancellation signature, rerun the ordered lib build so the Host generates the strict contract before the Client compiles and bundles the new contribution:
```sh
pnpm run build:lib
```
The running Client watcher consumes these generated files when it rebundles. If `pnpm run build:lib:host` has already refreshed the Host contract, `pnpm run build:lib:client` can complete the Client side; a clean worktree cannot skip the Host phase. Recompiling only the frontend source cannot infer new types from Host decorators. `pnpm run typecheck` runs the Host lib phase before Client tsc, and CI and release builds use the same order.
## Boundaries
Remote handles only unary method calls with one request and one result. Session event streams, pagination, incremental reduce, projection, and entity substreams require a separate data protocol and registration model; even when they reuse the Connection, they must not masquerade as Remote methods or enter invocation descriptors.
The API layers are organized as `remotes → gateway → connection → webserver`. The BFF and TypeRT RPC layers live under `packages/api`; Connection and WebServer remain at `packages/client/connection` and `packages/host/webserver`, with service contracts that permit a later package-only move to `packages/api`. The legacy API Proxy remains at `packages/host/apiproxy` as the fallback for endpoints not yet migrated to Remote.
Lookup policy is currently configured per key, so all `agent` or `session` parameters share the cold-resume behavior. If a Remote endpoint must accept live objects only, an explicit per-parameter or per-endpoint policy must be added later; the business method must not guess whether the object came from restoration.

170
docs/api-gateway.zh.md Normal file
View File

@@ -0,0 +1,170 @@
# API Gateway
[English](api-gateway.md) | 中文
本文是 TypeRT API Gateway 的当前状态参考。它描述业务 Service 如何声明一元 Remote 方法、构建如何生成 Host 与 Client 契约,以及调用如何复用 Connection 的 RPC 与 `/api` 路由。会话事件、增量数据和其他流协议不属于本文范围;它们可以使用同一个 Connection但不使用 Remote 方法描述符。
## 编程模型
业务 Service 通过 `@Remote``@RemoteScope` 选择对 Client 开放的方法。未标记的方法不会进入生成的 Client 类型或运行时贡献,也不能通过 `ctx.remote` 调用。
`@Remote` 表示调用根 Host Context 中注册的 Cordis Service。复杂的 Host 对象不能直接跨 wire 传输;业务包必须通过 `TypeRTLookupMap` 声明它与 wire identity 的关联,并在运行时向 `ctx.typert.lookups` 注册默认解析提供方。例如 `Agent` 参数在 Host 签名中名为 `agent`,生成的 wire 字段为 `agentId`Gateway 在调用业务方法前将 id 解析为 Host 对象。Host 组合可以用 `ctx.typert.lookups.configure()` 覆盖某个 lookup key 的解析策略而不改变业务包拥有的参数名、wire 字段或规范类型 symbol。
`@RemoteScope(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context再从该 Context 取得 Service 并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。
Service 通常继承 `GatewayService`,让 Cordis service key 与默认 Remote namespace 在构造器中显式绑定。已有其他基类的 Service 可以改为声明 `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`;两种方式都会留下可检查的公开 binding不依赖编译器向构造函数注入 symbol。
```ts
import type { Agent } from '@deepseek-ai/dsh-agent'
import { GatewayService, Remote, RemoteScope } from '@deepseek-ai/dsh-type-meta'
import type { Context } from 'cordis'
export interface CreateGoalRequest {
objective: string
}
export interface CreateGoalResult {
accepted: boolean
}
export class GoalService extends GatewayService {
constructor(ctx: Context) {
super(ctx, 'goals')
}
@Remote('create')
createForClient(
agent: Agent,
request: CreateGoalRequest,
signal: AbortSignal,
): CreateGoalResult {
signal.throwIfAborted()
return this.create(agent, request)
}
@RemoteScope('agent', 'current')
currentForClient(): CreateGoalResult {
return { accepted: true }
}
private create(_agent: Agent, request: CreateGoalRequest): CreateGoalResult {
return { accepted: request.objective.length > 0 }
}
}
```
Remote 方法可以同步返回或返回 Promise。若需要协作式取消Host 签名的最后一个参数必须是全局类型的 `signal: AbortSignal`;它记录在描述符中而不是进入 `args`Client 生成的方法则接受最后一个可选的 `AbortSignal`
Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直接调用与作用域调用分别出现在 `ctx.remote.<namespace>``agentCtx.remote.<namespace>`。每个 namespace 都是注册为 `remote.<namespace>` 的可追踪 Cordis 子 ServiceClient assembly 通过 `ctx.remote.$mount()` 挂载贡献,最后一个方法撤回后该 namespace 随即卸载。依赖声明归实际调用方所有:只有读取 `ctx.remote.<namespace>``agentCtx.remote.<namespace>` 的业务包才在自己的 `inject` 中同时声明 `remote``remote.<namespace>`;只负责挂载 contribution 的 assembly以及不调用该 namespace 的上层 runtime不代业务包声明 namespace 依赖。当一个 `@Remote` 方法恰好有一个 lookup 参数、且同名 `TypeRTContextMap` 使用相同 wire identity 时,生成的作用域签名会省略该 identity 参数。`@RemoteScope` 只生成作用域调用界面。
```ts ignore-check
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import type { AgentContext } from '@deepseek-ai/dsh-client-runtime/client'
import type { Context } from 'cordis'
import type {} from '@deepseek-ai/dsh-api-remotes/client'
export const inject = ['remote', 'remote.goals']
declare const ctx: Context
declare const agentCtx: AgentContext
declare const agentId: SessionId
await ctx.remote.goals.create(agentId, { objective: 'ship it' })
await agentCtx.remote.goals.create({ objective: 'ship it' })
```
Client 应用只装配 `@deepseek-ai/dsh-api-remotes`。该包以运行时值导入被选业务包的 `/remote` 子路径,通过 `ctx.remote.$mount()` 挂载贡献,同时重新导出相同文件中的声明合并。增加一个 Host Remote 包是 Client 组合所有者的显式选择;业务组件不需要分别加载 TypeRT Gateway 或业务包的 Remote JS。
未来的 TUI 可以装配同一个不依赖 React 的 `api-remotes` 与 `ctx.remote` 契约,因此它能看到的 Host 方法同样只限于生成时选择的 Remote 方法。本文不定义或实现 TUI 组合。
## 组件职责
| 位置 | 包或入口 | 职责 |
|---|---|---|
| 共享 | `@deepseek-ai/dsh-type-meta` | 声明 decorator、Gateway binding、可合并协议映射、调用描述符及提供方类型不启动 TypeScript 分析,也不注册 Cordis 服务 |
| 构建 | `@deepseek-ai/dsh-typert-generator` | 从 Host `ts.Program` 严格分析 Remote 签名、类型图、lookup、Context 与源码位置,并生成 Host 和 Host-for-Client 产物 |
| Host | `@deepseek-ai/dsh-typert-registry` 与 Loader | 把生成的 Host 描述符、schema 及业务包注册项放入 `ctx.typert`,并持有 lookup 与 Context 提供方 |
| Host | `@deepseek-ai/dsh-api-remotes` | 负责应用的 Agent/Session 身份策略,并配置对应的 TypeRT lookup |
| Host | `@deepseek-ai/dsh-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint解析对象或 Context调用实时 Cordis Service 并校验边界 |
| Client | `@deepseek-ai/dsh-api-gateway/client` | 提供 `ctx.remote` 与 `remote.<namespace>` 子 Service把生成的描述符挂成具体方法并通过 Connection 发起、校验和取消调用 |
| Client | `@deepseek-ai/dsh-api-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 |
| 双侧 | `@deepseek-ai/dsh-client-connection` | 提供 RPC carrier、请求关联、信任边界、取消、响应 envelope 与当前 `/api` HTTP bridge |
API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对等入口,但两侧构建不会进入同一个 `ts.Program`。Host 入口不导入 Client 的 Cordis `Context` 合并Client 入口也不导入 Host Gateway 服务。
## 严格生成链路
根构建依次执行 `build:lib:host`、`build:lib:client` 与 `build:web`。Host lib 阶段先运行 `tsc -b tsconfig.host.json`,再运行 `tsdown --env.DSH_BUILD_FACE host`TypeRT generator 由正常 Host Project Reference 图编译,并在这次 tsdown 中以 Host aggregate 为唯一 `ts.Program` 种子运行。Client lib 阶段随后运行 `tsc -b tsconfig.client.json` 与 `tsdown --env.DSH_BUILD_FACE client`,使用刚生成的 Remote Client 声明和运行时贡献,但不再次启动 TypeRT。
两次 tsdown 都接收完整 workspace且都只打包 `lib/types` 中由对应 tsc 阶段发射的 JavaScript。根配置不扫描 Client 产物、不按 package 名分类,也不向 tsdown 传维护式 filter各包的本地配置根据 `DSH_BUILD_FACE` 返回当前阶段的入口。普通 Client plugin 在 Client 阶段一起生成 Node loader 入口与 browser bundle。
`api-remotes` 是唯一拆分 TypeScript face 的 package 特例。它的 Host project 负责 Agent/Session lookup 策略Client project 则依赖业务包在 Host tsdown 中生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用 `api/remotes/tsconfig.host.json` 或 `api/remotes/tsconfig.client.json`。包内 `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。其他 package 仍只登记在一个 aggregate 中。
每个贡献业务包把生成文件写入自己的 `lib/`,而不是源码目录:
| 文件 | 消费方 | 内容 |
|---|---|---|
| `typert.host.js` | Host Loader | Host face 的运行时反射、严格调用描述符和 schema 注册值 |
| `typert.host.d.ts` | Host 类型系统 | Host face 的生成声明 |
| `typert.remote-client.js` | `api-remotes` | 可挂载的 `TypeRTRemoteContribution`,包含严格描述符与运行时 codec |
| `typert.remote-client.d.ts` | Client 类型系统 | `TypeRTRemoteNamespaceMap` 与 `TypeRTRemoteScopeMap` 的声明合并及 Client-safe 类型引用 |
| `typert.remote-client.d.ts.map` | 编辑器 | 将生成的方法属性映射回 Host 包中的 Remote 方法声明 |
业务包通过 `./typert` 暴露 Host Loader 入口,通过 `./remote` 暴露 Host-for-Client 入口。生成器同时校验这些 package export 及发布文件清单;只有具备相应入口的显式贡献包才会生成产物。
Remote Client 声明中的参数名来自 wire 字段,参数和返回类型则引用原业务包导出的 Client-safe 类型。声明 map 把 `ctx.remote.goals.create` 最终解析到的生成属性映射到带 `@Remote` 的 Host 源方法,因此支持 declaration-map 的编辑器可以从 Client 调用跳到真实实现,而不是停在生成的 `.d.ts`。
严格分析要求 Remote 是公开、非静态、有具体实现的实例方法。方法不能是泛型参数必须是具名且必填的简单标识符不能使用解构、默认值、rest 或可选参数。可 JSON 表示的普通类型由 TypeRT 生成严格 schema工作区 class 等复杂对象必须具有唯一的 `TypeRTLookupMap` 声明。lookup 与 Context 包同时负责静态声明合并和运行时提供方注册,缺少任一侧都会在构建或最早可解析的运行时边界报错。
## 运行时调用
当前 Remote 与 API Proxy 共用 Connection 的 `/api` 路由,不存在独立 `/api2` server 或第二套 Connection。Client Remote 调用 `connection.rpc.call('/api', '<namespace>/<method>', { args }, signal)`;当前 HTTP carrier 对应 `POST /api/<namespace>/<method>`payload 只包含一个具名 `args` 对象。
Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内按 interceptor 顺序分发。TypeRT Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint未认领的请求回退到既有 API Proxy。Connection 拥有传输、RPC id、响应 envelope 和 request cancellationGateway 只拥有 Remote 数据协议和业务分发。未来替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程界面。
Gateway 每次调用都从当前注册表解析描述符和实时 Service不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context provider 解析对象或接收者,最后调用 binding 指向的 Service 方法并校验返回值。缺少 provider、identity 未命中、binding 不一致、参数多缺、schema 失败和方法不存在都在进入或离开业务边界时失败。
lookup provider 的 `register()` 同时提供稳定声明和默认 resolver`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于 provider 挂载;没有 provider 时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复 provider 默认策略。API Remotes 负责 `agent` 与 `session` 的标准 `agentFor()` 语义:复用 live Agent自动恢复普通冷会话对并发恢复去重并拒绝由 subagent routing 拥有的 identity`session` lookup 返回该 Agent 的 Session。Web API Proxy 提供 Agent 默认值与 scope 设置,再让旧方法使用同一个 resolver。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。
Client 卸载一个贡献时会一起移除描述符和具体方法中止其进行中的调用并使外部仍持有的旧方法句柄拒绝继续调用。Host 上已经注册过的严格 endpoint 被撤回后也不会降级到 SRC 推断,以免热卸载悄然降低校验强度。
## SRC 开发回退
Host 通过 `node --import tsx/esm` 从源码启动时不会执行 TypeRT 编译插件。标准 decorator 初始化器仍会把方法名和调用模式记录到模块私有 `WeakMap``GatewayService` 或 `bindTypeRTGateway()` 则提供显式 service bindingGateway 因而可以在不启动 `ts.Program` 的情况下构造一个较弱的临时描述符。
SRC 回退从运行中函数解析简单参数名。参数名与某个已注册 lookup 的 `parameter` 相同,例如 `agent` 或 `session`,就使用其 `agentId` 或 `sessionId` wire 字段并在 Host 解析对象;其他参数只检查值是否为无循环、无特殊 prototype 的 JSON-safe 数据。`@RemoteScope` 直接使用已注册 Host Context provider 的 wire 字段。SRC 不读取 TypeScript 类型,不生成 Zod schema不推断可选参数也不支持解构、默认值、rest 或重复参数名。
SRC 只解决 Host 源码进程的分发问题。Client 不会从运行中的 Host 发现 decoratorClient Remote 也拒绝挂载缺少严格 codec 的 SRC 描述符其类型、codec 和 Remote 注册值始终来自最近一次生成的 `lib/typert.remote-client.*`。
## 开发模式
完整构建会先生成 Host 契约,再编译 Host、Client 与 Web因此是建立或刷新所有产物的确定性入口
```sh
pnpm run build
```
Web 开发通常在完成一次构建后启动源码 Host并在另一个终端运行 Client plugin watcher
```sh
pnpm run dsh -- web --dev
pnpm run dev:web
```
`dsh` 通过 tsx 启动 Host 源码,所以 Host 可以使用 SRC 回退;`dev:web` 只监听带 `dshClient` 声明的 Client plugin 并重写其 `lib/client.js`,它不会分析 Host decorator也不会生成 Remote Client DTS。
只修改 Remote 方法实现体而不改变契约时,无需重新生成 TypeRT 文件。新增或删除 decorator、修改导出名、namespace、参数、返回值、lookup、Context 或取消签名时,重新执行有序 lib 构建,让 Host 先生成严格契约,再让 Client 编译并打包新的贡献:
```sh
pnpm run build:lib
```
运行中的 Client watcher 会在重新打包时消费这些生成文件。若已单独运行 `pnpm run build:lib:host` 刷新 Host 契约,也可再运行 `pnpm run build:lib:client` 完成 Client 侧;干净工作树不能跳过 Host 阶段。仅重新编译前端源码不能从 Host decorator 推导新类型。`pnpm run typecheck` 会执行 Host lib 阶段后再运行 Client tscCI 与发布构建也使用同一顺序。
## 边界
Remote 只处理有单个请求与单个结果的一元方法调用。Session event stream、分页、增量 reduce、projection 和实体子流需要独立的数据协议与注册模型;即使它们复用 Connection也不应伪装成 Remote 方法或放入调用描述符。
API 各层按 `remotes → gateway → connection → webserver` 组织。BFF 与 TypeRT RPC 层位于 `packages/api`Connection 与 WebServer 仍位于 `packages/client/connection` 和 `packages/host/webserver`,其服务契约允许未来只移动包,将它们放到 `packages/api`。旧 API Proxy 仍位于 `packages/host/apiproxy`,作为尚未迁移到 Remote 的 endpoint 的回退路径。
当前 lookup 策略按 key 配置,因此所有 `agent` 或 `session` 参数共享冷恢复行为。某个 Remote endpoint 若必须只接受 live 对象,需要后续增加显式的逐参数或逐 endpoint 策略,不能通过业务方法内部猜测恢复来源。

View File

@@ -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 docs/architecture.md
architecture.md: bc6fd03947aed5d8f8315c282366abf34f0d7e3b
architecture.zh.md: 7342e6508194de191173ab8712f08f370fe6c55c
architecture.md: d4d95792f7896be56487d49ca5348167662060e6
architecture.zh.md: a16247fecc3f6f3fb9427fda6b806b6464a7ffd5

View File

@@ -48,6 +48,7 @@ Harnesses are [Cordis](cordis-primer.md) contexts; packages contribute services,
| `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | named secret references resolved per operation, never inlined in configuration |
| `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI-host directory picking (`native`/`browse` interactions) |
| `ctx.typert` | [`typert/registry`](../packages/typert/registry/README.md) | runtime registry for generated package reflection and live Zod schemas |
| `ctx.typertGateway` | [`api/gateway`](../packages/api/gateway/README.md) | dispatches TypeRT Remote unary calls through the [API Gateway](api-gateway.md) |
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | package-name-selected registry of package-owned runtime checks |
## Event

View File

@@ -48,6 +48,7 @@
| `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | 具名密钥引用,按操作解析,绝不内联进配置 |
| `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI 宿主目录选取(`native``browse` 交互) |
| `ctx.typert` | [`typert/registry`](../packages/typert/registry/README.md) | 生成的包反射和实时 Zod schema 的运行时注册表 |
| `ctx.typertGateway` | [`api/gateway`](../packages/api/gateway/README.md) | 通过 [API Gateway](api-gateway.md) 分发 TypeRT Remote 一元调用 |
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | 按包名筛选包自有运行时检查的注册表 |
## 事件

View File

@@ -32,6 +32,8 @@ flowchart LR
pkg_typert_registry["typert-registry"]
svc_typert["ctx.typert<br/>Runtime type registry"]
pkg_typert_loader["typert-loader"]
pkg_api_gateway["api-gateway"]
svc_typertGateway["ctx.typertGateway<br/>TypeRT Host invocation gateway"]
svc_sessionPersistence["ctx.sessionPersistence<br/>Durable session persistence seam"]
pkg_session_persistence_jsonl["session-persistence-jsonl"]
pkg_session_persistence_sqlite["session-persistence-sqlite"]
@@ -174,6 +176,7 @@ flowchart LR
pkg_agent --> svc_agents
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_approval --> svc_approval
pkg_bash --> svc_bash
pkg_bash_env --> svc_bashEnv
@@ -350,6 +353,7 @@ flowchart LR
svc_tools --> pkg_tool_subagent
svc_tools --> pkg_tool_todo
svc_tools --> pkg_tool_web
svc_typert --> pkg_api_gateway
svc_typert --> pkg_typert_loader
svc_userInteraction --> pkg_tool_ask_user
svc_web --> pkg_tool_web
@@ -366,7 +370,8 @@ flowchart LR
| `ctx.toolResultPrune` | `core` | [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | - | [`compact-basic`](../packages/compact/compact-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. |
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`cli-demo`](../packages/examples/cli-demo), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`invariants`](../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. |
| `ctx.invariants` | `core` | [`invariants`](../packages/support/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. |
| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader) | - | Plugins register live zod contributions directly or through dsh-typert-loader; runtime consumers query schemas and reflection metadata at their own edges. |
| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. |
| `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. |
| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. |
| `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-local`](../packages/settings/settings-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section; the web gateway serves redacted layered descriptors and writes the user layer. |
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the web gateway exposes value-free views and write-only storage. |

View File

@@ -344,7 +344,7 @@ Source: [`packages/examples/cli-demo/src/index.ts:26`](../packages/examples/cli-
## `@deepseek-ai/dsh-client-connection`
Requires: `httpServer` · `apiProxy`
Requires: `httpServer`
```ts config-catalog
/** Plugin config: the deployment's non-loopback serving authorities. */
@@ -361,7 +361,7 @@ export interface ConnectionConfig {
}
```
Source: [`packages/client/connection/src/index.ts:21`](../packages/client/connection/src/index.ts)
Source: [`packages/client/connection/src/index.ts:32`](../packages/client/connection/src/index.ts)
## `@deepseek-ai/dsh-client-hmr`
@@ -479,7 +479,7 @@ Source: [`packages/compact/compact-tool-result-prune/src/types.ts:4`](../package
```ts config-catalog
/** Plugin config: file location and hot-reload behavior. */
export interface Config {
/** Credentials document path; defaults to `.env` under the harness home. */
/** Credentials document path; defaults to `.credentials.yaml` under the harness home. */
path?: string
/** Harness home used when `path` is omitted; defaults to `$DSH_HOME` or `~/.dsh`. */
dshHome?: string
@@ -490,7 +490,7 @@ export interface Config {
}
```
Source: [`packages/credentials/credentials-local/src/index.ts:26`](../packages/credentials/credentials-local/src/index.ts)
Source: [`packages/credentials/credentials-local/src/index.ts:54`](../packages/credentials/credentials-local/src/index.ts)
## `@deepseek-ai/dsh-frontend-static`
@@ -548,7 +548,7 @@ export interface Config {
}
```
Source: [`packages/goal/goal/src/index.ts:114`](../packages/goal/goal/src/index.ts)
Source: [`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
## `@deepseek-ai/dsh-headless`
@@ -630,9 +630,20 @@ Source: [`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-c
Requires: `agents` · `directoryPicker` · `llm` · `sessions` · `subagents` · `sessionQuery` · `tools` · `userInteraction` · `workspace`
```ts config-catalog
/** Gateway plugin config: host-level agent routing and Workspace creation root. */
/**
* Gateway plugin config: host-level agent routing and Workspace creation root.
*
* `reasoningEffort` is deliberately absent, so the section carries one field
* the composition cannot. The seam resolves a section by MERGING the user
* layer over the composition entry per field, and an absent key cannot
* override a present one — so a composition-set effort would survive every
* later switch to a model that has none, and strand it for the next session
* to fail on. Effort is a per-model fact anyway: a deployment default belongs
* on the adapter profile (`llm-pi-ai`'s `reasoning`, `llm-deepseek`'s own),
* which resolves per model rather than per gateway.
*/
export interface Config {
/** Default provider route for created/resumed agents. */
/** Default provider route for created agents. */
provider: string
/** Default model id. */
model: string
@@ -641,7 +652,7 @@ export interface Config {
}
```
Source: [`packages/host/apiproxy/src/index.ts:33`](../packages/host/apiproxy/src/index.ts)
Source: [`packages/host/apiproxy/src/index.ts:67`](../packages/host/apiproxy/src/index.ts)
## `@deepseek-ai/dsh-host-directory-picker-browse`
@@ -721,17 +732,9 @@ Requires: `llm`
* reasoning effort resolves to `high`.
*/
export interface Config {
/**
* Trimmed literal API key; whitespace-only is absent, so it resolves through
* {@link apiKeyEnv} like an omitted one. Prefer {@link apiKeyEnv} to keep
* secrets out of configuration files. {@link resolveAdapterOptions} also
* format-checks what remains: a value no HTTP header can carry fails there
* rather than inside `fetch`.
*/
apiKey?: string
/** Credential reference (environment-variable name) resolved per request; defaults to `DEEPSEEK_API_KEY`. */
apiKeyEnv?: string
/** Endpoint base; falls back to $DEEPSEEK_BASE_URL, then the public API. */
/** Endpoint base; falls back to $DEEPSEEK_BASE_URL from a trusted environment layer, then the public API. */
baseURL?: string
/** Deployment thinking policy; `disabled` limits every conversation request to `off`. */
thinking?: 'enabled' | 'disabled'
@@ -766,7 +769,7 @@ export interface DeepSeekCatalogModel {
Depends on: [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
Source: [`packages/llm/llm-deepseek/src/index.ts:60`](../packages/llm/llm-deepseek/src/index.ts)
Source: [`packages/llm/llm-deepseek/src/index.ts:61`](../packages/llm/llm-deepseek/src/index.ts)
## `@deepseek-ai/dsh-llm-pi-ai`
@@ -785,12 +788,6 @@ export interface Config {
/** Configuration for one pi-ai provider route; the `providers` dict key IS the route. */
export interface PiAiProviderProfile {
/**
* Literal provider credential; prefer {@link apiKeyEnv}. With both absent pi-ai uses its
* provider-native ambient discovery. Trimmed and format-checked by {@link resolveProfiles}; a
* value no HTTP header can carry fails there rather than inside `fetch`.
*/
apiKey?: string
/** Credential reference (environment-variable name) resolved per request through `ctx.credentials`. */
apiKeyEnv?: string
/** Name shown by configuration surfaces; defaults to the route key. */
@@ -861,7 +858,7 @@ export interface PiAiModelProfile {
Depends on: `CacheRetention` (`@earendil-works/pi-ai`) · `ModelThinkingLevel` (`@earendil-works/pi-ai`) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · `ThinkingBudgets` (`@earendil-works/pi-ai`) · `Transport` (`@earendil-works/pi-ai`)
Source: [`packages/llm/llm-pi-ai/src/config.ts:126`](../packages/llm/llm-pi-ai/src/config.ts)
Source: [`packages/llm/llm-pi-ai/src/config.ts:120`](../packages/llm/llm-pi-ai/src/config.ts)
## `@deepseek-ai/dsh-llm-replay`
@@ -913,7 +910,7 @@ export interface ReplayModelConfig {
Depends on: [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
Source: [`packages/support/llm-replay/src/index.ts:710`](../packages/support/llm-replay/src/index.ts)
Source: [`packages/support/llm-replay/src/index.ts:731`](../packages/support/llm-replay/src/index.ts)
## `@deepseek-ai/dsh-llm-retry`
@@ -2478,7 +2475,7 @@ export interface Config {
}
```
Source: [`packages/web/web-search-deepseek/src/index.ts:43`](../packages/web/web-search-deepseek/src/index.ts)
Source: [`packages/web/web-search-deepseek/src/index.ts:44`](../packages/web/web-search-deepseek/src/index.ts)
## `@deepseek-ai/dsh-web-search-exa`
@@ -2500,7 +2497,7 @@ export interface Config {
}
```
Source: [`packages/web/web-search-exa/src/index.ts:37`](../packages/web/web-search-exa/src/index.ts)
Source: [`packages/web/web-search-exa/src/index.ts:38`](../packages/web/web-search-exa/src/index.ts)
## `@deepseek-ai/dsh-web-search-perplexity`
@@ -2522,7 +2519,7 @@ export interface Config {
}
```
Source: [`packages/web/web-search-perplexity/src/index.ts:31`](../packages/web/web-search-perplexity/src/index.ts)
Source: [`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts)
## `@deepseek-ai/dsh-workflow-workerthread`
@@ -2585,12 +2582,15 @@ Source: [`packages/context/workspace-context/src/config.ts:18`](../packages/cont
These load from a `cordis.yml` entry with no `config:` block; they declare no config surface.
- `@deepseek-ai/dsh-agent` ([`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts))
- `@deepseek-ai/dsh-api-gateway` — requires `typert` ([`packages/api/gateway/src/index.ts`](../packages/api/gateway/src/index.ts))
- `@deepseek-ai/dsh-api-remotes` ([`packages/api/remotes/src/index.ts`](../packages/api/remotes/src/index.ts))
- `@deepseek-ai/dsh-client-locale` ([`packages/client/locale/src/index.ts`](../packages/client/locale/src/index.ts))
- `@deepseek-ai/dsh-client-modules` — requires `httpServer` · `loader` ([`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts))
- `@deepseek-ai/dsh-client-runtime` ([`packages/client/runtime/src/index.ts`](../packages/client/runtime/src/index.ts))
- `@deepseek-ai/dsh-client-ui-agent-preset` ([`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts))
- `@deepseek-ai/dsh-client-ui-command` ([`packages/client/ui-command/src/index.ts`](../packages/client/ui-command/src/index.ts))
- `@deepseek-ai/dsh-client-ui-conversation` ([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts))
- `@deepseek-ai/dsh-client-ui-deliverables` ([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts))
- `@deepseek-ai/dsh-client-ui-goal` ([`packages/client/ui-goal/src/index.ts`](../packages/client/ui-goal/src/index.ts))
- `@deepseek-ai/dsh-client-ui-layout` ([`packages/client/ui-layout/src/index.ts`](../packages/client/ui-layout/src/index.ts))
- `@deepseek-ai/dsh-client-ui-model` ([`packages/client/ui-model/src/index.ts`](../packages/client/ui-model/src/index.ts))
@@ -2628,7 +2628,6 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-timeout-policy` — requires `tools` ([`packages/timeout/timeout-policy/src/index.ts`](../packages/timeout/timeout-policy/src/index.ts))
- `@deepseek-ai/dsh-tool-ask-user` — requires `tools` · `userInteraction` ([`packages/ui/tool-ask-user/src/index.ts`](../packages/ui/tool-ask-user/src/index.ts))
- `@deepseek-ai/dsh-tool-subagent-control` — requires `tools` · `subagents` ([`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts))
- `@deepseek-ai/dsh-typert-registry` ([`packages/typert/registry/src/index.ts`](../packages/typert/registry/src/index.ts))
- `@deepseek-ai/dsh-user-interaction` ([`packages/ui/user-interaction/src/index.ts`](../packages/ui/user-interaction/src/index.ts))
- `@deepseek-ai/dsh-workspace` — requires `storageDomain` · `sessionPersistence` ([`packages/workspace/workspace/src/index.ts`](../packages/workspace/workspace/src/index.ts))
@@ -2668,6 +2667,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
- `@deepseek-ai/dsh-client-ui-slots` ([`packages/client/ui-slots/src/index.ts`](../packages/client/ui-slots/src/index.ts))
- `@deepseek-ai/dsh-client-web` ([`packages/client/web/src/index.ts`](../packages/client/web/src/index.ts))
- `@deepseek-ai/dsh-client-web-react` ([`packages/client/web-react/src/index.ts`](../packages/client/web-react/src/index.ts))
- `@deepseek-ai/dsh-environment` ([`packages/util/environment/src/index.ts`](../packages/util/environment/src/index.ts))
- `@deepseek-ai/dsh-helper` ([`packages/sdk/helper/src/index.ts`](../packages/sdk/helper/src/index.ts))
- `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts))
- `@deepseek-ai/dsh-jsonrpc-demo` ([`packages/examples/jsonrpc-demo/src/index.ts`](../packages/examples/jsonrpc-demo/src/index.ts))
@@ -2685,4 +2685,6 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
- `@deepseek-ai/dsh-subagent-inprocess` ([`packages/subagent/subagent-inprocess/src/index.ts`](../packages/subagent/subagent-inprocess/src/index.ts))
- `@deepseek-ai/dsh-telemetry` ([`packages/sdk/telemetry/src/index.ts`](../packages/sdk/telemetry/src/index.ts))
- `@deepseek-ai/dsh-timeout` ([`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts))
- `@deepseek-ai/dsh-type-meta` ([`packages/typert/type-meta/src/index.ts`](../packages/typert/type-meta/src/index.ts))
- `@deepseek-ai/dsh-typert-generator` ([`packages/typert/generator/src/index.ts`](../packages/typert/generator/src/index.ts))
- `@deepseek-ai/dsh-typert-registry` ([`packages/typert/registry/src/index.ts`](../packages/typert/registry/src/index.ts))

View File

@@ -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 docs/cookbook/adding-a-package.md
adding-a-package.md: a45b222f6aed905a18ef9b480c989e6045029afe
adding-a-package.zh.md: af0e4d0779fa99ce43ebccba00c33eab16c4d362
adding-a-package.md: 8ab603ea7b235bd2a582c9232afaca281a969448
adding-a-package.zh.md: 8c0bc9dbd02b18a388f8e6ce91af10753c0210a2

View File

@@ -31,7 +31,7 @@ In-package relative imports use explicit `.ts` specifiers in source (for example
| File | Change |
|---|---|
| `tsconfig.base.json` | no edit for an existing group; for a new group, add a `./packages/<group>/*/src` candidate to the `@deepseek-ai/dsh-*` wildcard |
| `tsconfig.host.json` (host-side package) or `tsconfig.client.json` (client-side package) | add `{ "path": "./packages/<group>/<pkg>" }` to `references` — exactly one aggregate, never both ([layout](../development.md#typescript-project-layout)) |
| `tsconfig.host.json` (Host package) or `tsconfig.client.json` (Client package) | add `{ "path": "./packages/<group>/<pkg>" }` to `references` an ordinary package belongs to exactly one aggregate, never both. `api/remotes` uses a repository-specific split because the Host generates a contract that the Client consumes in a later phase; new packages must not copy it ([layout](../development.md#typescript-project-layout)) |
| `knip.json` | only if the package has entrypoints that repository discovery does not already cover |
A `packages/client/*` package additionally extends `tsconfig.base.client.json` instead of `tsconfig.base.json`, and a client plugin package declares `dshClient` in package.json, exports `./client`, and calls the shared tsdown preset (`packages/client/tsdown.client.ts`) — see [packages/client/AGENTS.md](../../packages/client/AGENTS.md) for the client-side contract.

View File

@@ -31,7 +31,7 @@ package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-c
| 文件 | 变更 |
|---|---|
| `tsconfig.base.json` | 已有分组无需编辑;新分组需为 `@deepseek-ai/dsh-*` 通配符添加 `./packages/<group>/*/src` 候选路径 |
| `tsconfig.host.json`host 包)或 `tsconfig.client.json`client 包) | 在 `references` 中添加 `{ "path": "./packages/<group>/<pkg>" }`——恰好一个聚合,绝不两个都加[布局](../development.md#typescript-project-layout) |
| `tsconfig.host.json`Host 包)或 `tsconfig.client.json`Client 包) | 在 `references` 中添加 `{ "path": "./packages/<group>/<pkg>" }`——普通包恰好属于一个 aggregate绝不两个都加。`api/remotes` 因 Host 生成契约与 Client 消费契约之间存在顺序依赖而使用仓库专属拆分,新增包不得仿照[布局](../development.md#typescript-project-layout) |
| `knip.json` | 仅当包有仓库发现机制尚未覆盖的入口时需要 |
`packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`client 插件包还需在 package.json 声明 `dshClient`、导出 `./client`、调用共享 tsdown preset`packages/client/tsdown.client.ts`——client 侧见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。

View File

@@ -472,7 +472,7 @@ Goal mutation accepted by one live agent. The matching `goal/change` session eve
Types: [Agent](../core-data-structures/core.md) · [GoalChanged](../core-data-structures/goal.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/goal/goal/src/domain.ts:141`](../../packages/goal/goal/src/domain.ts)
Source: [`packages/goal/goal/src/domain.ts:114`](../../packages/goal/goal/src/domain.ts)
## `llm/*`
@@ -542,7 +542,7 @@ Creation announcement during session publication. A synchronous throw vetoes and
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md)
Source: [`packages/core/session/src/index.ts:73`](../../packages/core/session/src/index.ts)
Source: [`packages/core/session/src/index.ts:74`](../../packages/core/session/src/index.ts)
### `session/disposed` — emit
@@ -563,7 +563,7 @@ Emitted once when an announced session leaves the store, including publication r
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md)
Source: [`packages/core/session/src/index.ts:83`](../../packages/core/session/src/index.ts)
Source: [`packages/core/session/src/index.ts:84`](../../packages/core/session/src/index.ts)
### `session/event` — emit
@@ -586,7 +586,7 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md)
Source: [`packages/core/session/src/index.ts:95`](../../packages/core/session/src/index.ts)
Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts)
### `session/flush` — parallel
@@ -606,7 +606,7 @@ Awaited parallel durability checkpoint: every listener runs and the caller await
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md)
Source: [`packages/core/session/src/index.ts:104`](../../packages/core/session/src/index.ts)
Source: [`packages/core/session/src/index.ts:105`](../../packages/core/session/src/index.ts)
## `settings/*`

View File

@@ -318,7 +318,7 @@ roots(): Agent[]
Types: [Agent](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md)
Source: [`packages/core/agent/src/index.ts:243`](../../packages/core/agent/src/index.ts)
Source: [`packages/core/agent/src/index.ts:254`](../../packages/core/agent/src/index.ts)
## `ctx.approval` — `ApprovalService`
@@ -820,7 +820,7 @@ create(agent: Agent, request: CreateGoalRequest): GoalView
* @param request - at least one replacement field.
* @returns the edited view.
*/
edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView
@Remote('edit') edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView
/**
* Pause an active goal and disarm automatic continuation.
@@ -828,7 +828,7 @@ edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView
* @param ref - expected current revision.
* @returns the paused view.
*/
pause(agent: Agent, ref: GoalRef): GoalView
@Remote('pause') pause(agent: Agent, ref: GoalRef): GoalView
/**
* Resume and arm a stopped goal, or rearm an active goal after a
@@ -837,7 +837,7 @@ pause(agent: Agent, ref: GoalRef): GoalView
* @param ref - expected current revision.
* @returns the active view.
*/
resume(agent: Agent, ref: GoalRef): GoalView
@Remote('resume') resume(agent: Agent, ref: GoalRef): GoalView
/**
* Mark a current non-complete goal complete and disarm it.
@@ -845,7 +845,7 @@ resume(agent: Agent, ref: GoalRef): GoalView
* @param ref - expected current revision.
* @returns the completed view.
*/
complete(agent: Agent, ref: GoalRef): GoalView
@Remote('complete') complete(agent: Agent, ref: GoalRef): GoalView
/**
* Mark an active goal blocked and disarm it.
@@ -862,12 +862,20 @@ block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView
* @param ref - expected current revision.
* @returns the tombstone ref whose revision is one past the cleared snapshot.
*/
clear(agent: Agent, ref: GoalRef): GoalRef
@Remote('clear') clear(agent: Agent, ref: GoalRef): GoalRef
/**
* Create one Goal through the remote boundary.
* @param agent - exact live Agent resolved from the wire identity.
* @param request - objective and optional round cap.
* @returns the created Goal identity.
*/
@Remote('create') remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult
```
Types: [Agent](../core-data-structures/core.md) · [CreateGoalRequest](../core-data-structures/goal.md) · [EditGoalRequest](../core-data-structures/goal.md) · [GoalBlockReason](../core-data-structures/goal.md) · [GoalRef](../core-data-structures/goal.md) · [GoalView](../core-data-structures/goal.md)
Types: [Agent](../core-data-structures/core.md) · [CreateGoalRequest](../core-data-structures/goal.md) · [CreateGoalResult](../core-data-structures/goal.md) · [EditGoalRequest](../core-data-structures/goal.md) · [GoalBlockReason](../core-data-structures/goal.md) · [GoalRef](../core-data-structures/goal.md) · [GoalView](../core-data-structures/goal.md)
Source: [`packages/goal/goal/src/index.ts:181`](../../packages/goal/goal/src/index.ts)
Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts)
## `ctx.httpServer` — `HttpServerService`
@@ -1850,7 +1858,7 @@ fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId):
Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [PrepareSessionOptions](../core-data-structures/persistence.md) · [Session](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md)
Source: [`packages/core/session/src/index.ts:803`](../../packages/core/session/src/index.ts)
Source: [`packages/core/session/src/index.ts:810`](../../packages/core/session/src/index.ts)
## `ctx.sessionTitle` — `SessionTitleService`
@@ -2640,16 +2648,17 @@ Source: [`packages/core/tools/src/index.ts:751`](../../packages/core/tools/src/i
## `ctx.typert` — `TypertRegistry`
Registry of generated schemas and package reflection.
Registry of generated schemas, package reflection, invocations, and Remote dependency providers.
```ts cordis-catalog
/**
* Register one generated contribution atomically for the calling fiber.
* Duplicate package-face identities or schema keys reject the whole batch.
* @param contribution - generated schemas and package metadata.
* Duplicate package-face identities, schemas, invocation ids, or endpoints
* reject the whole batch.
* @param contribution - generated schemas, reflection, and Host invocations.
* @returns the exact effect disposer that removes this contribution.
*/
register(contribution: TypertContribution): () => void
register(contribution: TypertContribution): TypeRTDisposer
/**
* Look up one schema by `<package>#<name>`.
@@ -2697,7 +2706,23 @@ listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[]
toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema
```
Source: [`packages/typert/registry/src/index.ts:67`](../../packages/typert/registry/src/index.ts)
Source: [`packages/typert/registry/src/service.ts:446`](../../packages/typert/registry/src/service.ts)
## `ctx.typertGateway` — `TypertGatewayService`
Resolve strict generated definitions or conservative SRC markers against current Cordis Services and TypeRT providers.
```ts cordis-catalog
/**
* Invoke one live Remote method through strict generated reflection or SRC markers.
* @param request - decoded endpoint and exact named wire arguments.
* @returns the validated business result.
* @throws {@link TypertGatewayError} for dispatch, provider, or boundary failures; lookup-policy and business errors retain identity.
*/
async invoke(request: InvokeRemoteRequest): Promise<unknown>
```
Source: [`packages/api/gateway/src/index.ts:78`](../../packages/api/gateway/src/index.ts)
## `ctx.userInteraction` — `UserInteractionService`

View File

@@ -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 docs/cordis-tutorial/05-config.md
05-config.md: fc19add239636fa9e7071d9c77e48595caec1f08
05-config.zh.md: 0c8170f518f0c87ab5c754606436496a4ff9d51e
05-config.md: 8d4043e33a58fc425d82d9846ff82473bcdef4c1
05-config.zh.md: e9463bd34e9c72dbae7b1ceb9907e35edf7b773b

View File

@@ -69,12 +69,12 @@ The plugin's fiber goes to FAILED, and this tutorial's launcher exits with statu
## Computed config values
The loader used in this repo supports a `!!js` tag for config values that must be computed at load time, such as reading an API key from the environment:
The loader used in this repo supports a `!!js` tag for config values that must be computed at load time:
```yaml
- name: '@deepseek-ai/dsh-llm-deepseek'
- name: './config-demo.ts'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
```
`!!js` works **only inside `config`**. Entry metadata (`name`, `id`, `disabled`, `inject`, ...) is static; `disabled: !!js ...` produces a truthy expression object that always disables the entry. See [loader configuration](../cordis-primer.md#loader-configuration).

View File

@@ -69,12 +69,12 @@ ValidationError: invalid config:
## 计算得到的配置值
本仓库使用的 loader 支持 `!!js` 标签,用于必须在加载时计算的配置值,例如从环境中读取 API key
本仓库使用的 loader 支持 `!!js` 标签,用于必须在加载时计算的配置值:
```yaml
- name: '@deepseek-ai/dsh-llm-deepseek'
- name: './config-demo.ts'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
```
`!!js` **仅在 `config` 内有效**。Cordis 配置项的元数据(`name``id``disabled``inject` 等)是静态的;`disabled: !!js ...` 会生成一个真值表达式对象,始终禁用该 Cordis 配置项。详见 [loader 配置](../cordis-primer.md#loader-configuration)。

View File

@@ -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 docs/core-data-structures/core.md
core.md: 52e77be89d939eefa2b42ef5586c5798e194a303
core.zh.md: 5f0134a4c8b3dead49830b41f62e8b7238327cfa
core.md: f7cf288715a3aec2f7037f12fc983e3172a77cef
core.zh.md: c17fd1335503c95e7f7f6f96cc286f567a8384e6

View File

@@ -20,6 +20,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
| [llm-streaming.md](llm-streaming.md) | the `StreamChunk` wire protocol + adapter contract, `BlockAssembler`, the `LlmAdapter` seam |
| [token-meter.md](token-meter.md) | immutable scalar and positional replay measurements with consumed-log revisions |
| [scope.md](scope.md) | scoped registration identity, dispatch carriers, and the owned `Scope` context |
| [typert.md](typert.md) | Remote invocation descriptors, lookup/Context declarations, TypeRT registries, and the Host Gateway/Client API seams |
| [goal.md](goal.md) | persisted goal identity, lifecycle snapshots, activation, change records, and round attribution |
| [commands.md](commands.md) | the human-command seam: definitions, adapter discovery, direct invocation, results, and parsing views |
| [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events |
@@ -313,6 +314,15 @@ interface LlmConfigurableProvider {
* object; empty when the whole section is the profile.
*/
settingsPath: readonly string[]
/**
* Whether the owning adapter knows this route only because configuration
* declared it — a gateway or self-hosted server it ships nothing about.
* Absent means the adapter draws no such distinction; false means it does
* and this route is one of its own. Only the adapter can answer: a stored
* profile is how a user-added route AND a corrected shipped one both look
* from outside.
*/
declared?: boolean
}
```

View File

@@ -20,6 +20,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数
| [llm-streaming.md](llm-streaming.md) | `StreamChunk` 协议格式wire format+ 适配器契约adapter contract`BlockAssembler``LlmAdapter` seam |
| [token-meter.md](token-meter.md) | 不可变的标量与位置回放度量,附带已消费日志修订号 |
| [scope.md](scope.md) | 作用域注册标识、dispatch 载体,以及拥有的 `Scope` 上下文 |
| [typert.md](typert.md) | Remote 调用 descriptor、lookup/Context 声明、TypeRT 注册表,以及 Host Gateway/Client API seam |
| [goal.md](goal.md) | 持久 goal 标识、生命周期快照、激活、变更记录与 Round 归属 |
| [commands.md](commands.md) | 人类命令 seam定义、适配器发现、直接调用、结果与解析视图 |
| [session.md](session.md) | 完整的 `SessionEventMap` 变体目录、`TurnTrigger`/`TurnEndReason``deriveMessages()`、执行封闭与独立事件 |
@@ -319,6 +320,15 @@ interface LlmConfigurableProvider {
* object; empty when the whole section is the profile.
*/
settingsPath: readonly string[]
/**
* Whether the owning adapter knows this route only because configuration
* declared it — a gateway or self-hosted server it ships nothing about.
* Absent means the adapter draws no such distinction; false means it does
* and this route is one of its own. Only the adapter can answer: a stored
* profile is how a user-added route AND a corrected shipped one both look
* from outside.
*/
declared?: boolean
}
```

View File

@@ -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 docs/core-data-structures/credentials.md
credentials.md: 3f6fcd127d01e2c49e17c70c002bebe9f363e951
credentials.zh.md: b5d2d9e164a85ce090790635c438b768cae4c9ca
credentials.md: ef74ddeb4346e18f8d5d33488657e5d50f1d754e
credentials.zh.md: 09cf374a2346fd93aa834e3372321e6eeece6ed8

View File

@@ -24,7 +24,7 @@ type CredentialRef = Branded<'CredentialRef'>
interface ResolvedCredential {
/** The non-empty secret value. */
value: string
/** Provider-defined source layer id (the local provider uses `env` and `file`). */
/** Provider-defined source layer id (the local provider uses `env`, `file`, `project-env`, and `user-env`). */
source: string
}
```

View File

@@ -24,7 +24,7 @@ type CredentialRef = Branded<'CredentialRef'>
interface ResolvedCredential {
/** The non-empty secret value. */
value: string
/** Provider-defined source layer id (the local provider uses `env` and `file`). */
/** Provider-defined source layer id (the local provider uses `env`, `file`, `project-env`, and `user-env`). */
source: string
}
```

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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 docs/core-data-structures/typert.md
typert.md: c70e50e2fea8455eb75dfdf8c309f659ab9cb2f9
typert.zh.md: 2cd1636d4cc8dbcfa009073b4a8e1dcc8d5897e4

View File

@@ -0,0 +1,203 @@
# TypeRT remote calls
English | [中文](typert.zh.md)
Types shared by generated Remote artifacts, the Host Gateway, and consumer API assemblies. The [TypeRT Gateway Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) owns the architecture and transport decisions; this page records the literal public contracts from [`dsh-type-meta`](../../packages/typert/type-meta/src/types.ts) and [`dsh-api-gateway`](../../packages/api/gateway/src/types.ts).
## Lookup and Context declarations
Business-object packages extend two empty maps through declaration merging. A lookup associates one Host object type with its wire identity; a Context declaration associates one scoped Context kind with its wire identity. Generated descriptors name these keys, while runtime providers supply the live resolution behavior.
```ts type-equiv
/** Merge-extensible Host object lookup declarations. */
interface TypeRTLookupMap {}
```
```ts type-equiv
/** Merge-extensible scoped Context declarations. */
interface TypeRTContextMap {}
```
The registry retains a lookup's wire declaration after its resolver unloads. SRC discovery therefore continues to classify the parameter as a lookup and fails unavailable instead of accepting the wire value as an ordinary business object.
```ts type-equiv
/** Stable wire declaration retained after a lookup provider unloads. */
interface TypeRTLookupDefinition {
/** Merge-declared lookup key. */
readonly key: string
/** Source parameter name recognized by the SRC weak parser. */
readonly parameter: string
/** Wire field replacing the Host object parameter. */
readonly wire: string
/** Canonical Host type symbol used by strict generation. */
readonly hostTypeSymbol: string
/** Canonical wire type symbol used by strict generation. */
readonly wireTypeSymbol: string
}
```
## Invocation descriptors
An `InvocationDescriptor` is local reflection, not a wire message. Host and consumer builds generate corresponding descriptors; the request sends only the endpoint and named `args`. Strict codecs carry generated schemas, while SRC codecs enforce JSON-safe values without structural type recovery. Cancellation is an out-of-band carrier signal injected after business parameters and never enters `args`.
```ts type-equiv
/** Codec attached to one invocation parameter or result. */
type TypeRTCodec =
| {
readonly mode: 'strict'
readonly typeSymbol: string
readonly schema: TypeRTSchema
}
| {
readonly mode: 'src-json'
}
```
```ts type-equiv
/** One ordered business parameter in a Remote invocation. */
interface InvocationParameterDescriptor {
/** Source-level parameter name. */
readonly name: string
/** Required key in the wire `args` object. */
readonly wire: string
/** Whether the value is JSON or requires a registered Host lookup. */
readonly source: 'json' | 'lookup'
/** Lookup key when `source` is `lookup`. */
readonly lookup?: string
/** Boundary codec for the wire representation. */
readonly codec: TypeRTCodec
}
```
```ts type-equiv
/** Carrier-independent description of one exported method invocation. */
interface InvocationDescriptor {
/** Globally stable generated identity. */
readonly id: string
/** Cordis service key owning the method. */
readonly service: string
/** Wire namespace, defaulting to the service key. */
readonly namespace: string
/** Public instance method name. */
readonly method: string
/** Service member invoked when the exported method name is an alias. */
readonly implementation?: string
/** Receiver selection mode. */
readonly invocation:
| { readonly kind: 'direct' }
| {
readonly kind: 'context'
readonly context: string
readonly wire: string
readonly codec: TypeRTCodec
}
/** Optional consuming-Context projection for one direct lookup parameter. */
readonly scope?: {
/** Context kind whose Client binder supplies the identity. */
readonly context: string
/** Lookup parameter wire field replaced by the Context identity. */
readonly wire: string
}
/** Ordered business parameters. */
readonly parameters: readonly InvocationParameterDescriptor[]
/** Transport cancellation injected after business parameters instead of entering wire args. */
readonly cancellation?: {
/** Reserved final Host method parameter. */
readonly parameter: 'signal'
}
/** Codec for the resolved method result. */
readonly result: TypeRTCodec
/** Source declaration used only for diagnostics. */
readonly sourceLocation?: InvocationSourceLocation
}
```
## TypeRT registry
`ctx.typert` separates current-environment descriptors, explicitly selected Remote contributions, lookup providers, and scoped Context providers. A lookup provider owns the stable wire declaration and default resolver; Host composition can configure an effect-scoped synchronous or asynchronous resolver for the same key, and unloading that configuration restores the default policy. Registrations are Cordis-owned effects and return awaitable disposers.
```ts type-equiv
/** Minimal TypeRT runtime consumed through dependency inversion. */
interface TypeRTService {
readonly local: TypeRTLocalRegistry
readonly remotes: TypeRTRemoteRegistry
readonly lookups: TypeRTLookupRegistry
readonly contexts: TypeRTContextRegistry
}
```
Generated consumer declarations merge direct namespaces into the map inherited by `TypeRTClientRemote`.
```ts type-equiv
/** Merge-extensible direct namespace surface generated for Client Remote services. */
interface TypeRTRemoteNamespaceMap {}
```
## Host Gateway
Connection decodes its carrier envelope before calling `ctx.typertGateway`. The request carries exact named wire fields and the carrier's cancellation signal separately; infrastructure and boundary failures use the Gateway's in-process error taxonomy, ordinary exceptions are folded by the RPC adapter into the transport's `internal` error code, and existing RPC errors carried by lookup policy through `TypeRTLookupFailure` are returned unchanged.
```ts type-equiv
/** One Remote method request after a carrier has decoded its envelope. */
interface InvokeRemoteRequest {
/** Remote namespace selected by the generated descriptor. */
readonly namespace: string
/** Exported Service method name. */
readonly method: string
/** Named wire values; fields must exactly match the descriptor. */
readonly args: Readonly<Record<string, unknown>>
/** Carrier or direct-caller cancellation injected only into cancellation-aware methods. */
readonly signal?: AbortSignal
}
```
```ts type-equiv
/** Stable infrastructure and boundary failures emitted before or after business execution. */
type TypertGatewayErrorCode =
| 'ambiguous-endpoint'
| 'arguments-invalid'
| 'binding-invalid'
| 'context-failed'
| 'context-not-found'
| 'context-unavailable'
| 'definition-unavailable'
| 'input-invalid'
| 'invocation-unavailable'
| 'lookup-failed'
| 'lookup-not-found'
| 'lookup-unavailable'
| 'method-unavailable'
| 'provider-mismatch'
| 'result-invalid'
| 'service-unavailable'
| 'signature-invalid'
```
```ts type-equiv
/** Host dispatcher consumed by Connection adapters. */
interface TypertGateway {
/**
* Invoke one live Remote method without assuming a carrier or response envelope.
* @param request - decoded endpoint and named wire arguments.
* @returns the validated business result.
* @throws {@link TypertGatewayError} for dispatch, provider, or boundary failures; lookup-policy and business errors retain identity.
*/
invoke(request: InvokeRemoteRequest): Promise<unknown>
}
```
## Consumer Remote
`ctx.remote` exposes only namespaces contributed by imported `/remote` artifacts. `$mount()` installs generated descriptors and concrete methods as one fiber-owned operation. Each namespace is a traced `remote.<namespace>` Cordis child Service whose lifetime spans its mounted methods; no JavaScript Proxy or Host business Service type enters the consumer.
```ts type-equiv
/** Client Remote capability implemented by the Gateway and consumed by Remote assemblies. */
interface TypeRTClientRemote extends TypeRTRemoteNamespaceMap {
/**
* Mount one generated Host-for-Client contribution in the caller's fiber.
* @param contribution - explicitly selected Remote package artifact.
* @returns disposer after namespace services and concrete methods are ready.
*/
$mount(contribution: TypeRTRemoteContribution): Promise<TypeRTDisposer>
}
```

View File

@@ -0,0 +1,203 @@
# TypeRT 远程调用
[English](typert.md) | 中文
以下类型由生成的 Remote 产物、Host Gateway 与消费方 API assembly 共用。[TypeRT Gateway Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) 负责架构与传输决策;本页记录 [`dsh-type-meta`](../../packages/typert/type-meta/src/types.ts) 和 [`dsh-api-gateway`](../../packages/api/gateway/src/types.ts) 中公共契约的字面定义。
## Lookup 与 Context 声明
业务对象包通过声明合并扩展两个空 map。lookup 将一种 Host 对象类型与其 wire identity 关联Context 声明将一种 scoped Context 类别与其 wire identity 关联。生成的 descriptor 引用这些 key运行时提供方则提供活对象解析行为。
```ts type-equiv
/** Merge-extensible Host object lookup declarations. */
interface TypeRTLookupMap {}
```
```ts type-equiv
/** Merge-extensible scoped Context declarations. */
interface TypeRTContextMap {}
```
lookup 的 resolver 卸载后,注册表仍会保留其 wire 声明。因此 SRC 发现过程会继续把该参数归类为 lookup并因不可用而失败而不会把 wire 值当作普通业务对象接受。
```ts type-equiv
/** Stable wire declaration retained after a lookup provider unloads. */
interface TypeRTLookupDefinition {
/** Merge-declared lookup key. */
readonly key: string
/** Source parameter name recognized by the SRC weak parser. */
readonly parameter: string
/** Wire field replacing the Host object parameter. */
readonly wire: string
/** Canonical Host type symbol used by strict generation. */
readonly hostTypeSymbol: string
/** Canonical wire type symbol used by strict generation. */
readonly wireTypeSymbol: string
}
```
## 调用 descriptor
`InvocationDescriptor` 是本地反射信息,不是 wire message。Host 与消费方构建会生成彼此对应的 descriptor请求只发送 endpoint 与具名 `args`。strict codec 携带生成的 schemaSRC codec 则在不恢复结构类型的前提下强制要求 JSON 安全值。取消通过带外 carrier signal 表达:它在业务参数之后注入,绝不进入 `args`。
```ts type-equiv
/** Codec attached to one invocation parameter or result. */
type TypeRTCodec =
| {
readonly mode: 'strict'
readonly typeSymbol: string
readonly schema: TypeRTSchema
}
| {
readonly mode: 'src-json'
}
```
```ts type-equiv
/** One ordered business parameter in a Remote invocation. */
interface InvocationParameterDescriptor {
/** Source-level parameter name. */
readonly name: string
/** Required key in the wire `args` object. */
readonly wire: string
/** Whether the value is JSON or requires a registered Host lookup. */
readonly source: 'json' | 'lookup'
/** Lookup key when `source` is `lookup`. */
readonly lookup?: string
/** Boundary codec for the wire representation. */
readonly codec: TypeRTCodec
}
```
```ts type-equiv
/** Carrier-independent description of one exported method invocation. */
interface InvocationDescriptor {
/** Globally stable generated identity. */
readonly id: string
/** Cordis service key owning the method. */
readonly service: string
/** Wire namespace, defaulting to the service key. */
readonly namespace: string
/** Public instance method name. */
readonly method: string
/** Service member invoked when the exported method name is an alias. */
readonly implementation?: string
/** Receiver selection mode. */
readonly invocation:
| { readonly kind: 'direct' }
| {
readonly kind: 'context'
readonly context: string
readonly wire: string
readonly codec: TypeRTCodec
}
/** Optional consuming-Context projection for one direct lookup parameter. */
readonly scope?: {
/** Context kind whose Client binder supplies the identity. */
readonly context: string
/** Lookup parameter wire field replaced by the Context identity. */
readonly wire: string
}
/** Ordered business parameters. */
readonly parameters: readonly InvocationParameterDescriptor[]
/** Transport cancellation injected after business parameters instead of entering wire args. */
readonly cancellation?: {
/** Reserved final Host method parameter. */
readonly parameter: 'signal'
}
/** Codec for the resolved method result. */
readonly result: TypeRTCodec
/** Source declaration used only for diagnostics. */
readonly sourceLocation?: InvocationSourceLocation
}
```
## TypeRT 注册表
`ctx.typert` 分开保存当前环境的 descriptor、显式选择的 Remote contribution、lookup 提供方与 scoped Context 提供方。lookup 提供方拥有稳定 wire 声明和默认 resolverHost 组合可以为同一个 key 配置 effect-scoped 同步或异步 resolver配置卸载后恢复默认策略。各项注册都是由 Cordis 持有的 effect并返回可等待的 disposer。
```ts type-equiv
/** Minimal TypeRT runtime consumed through dependency inversion. */
interface TypeRTService {
readonly local: TypeRTLocalRegistry
readonly remotes: TypeRTRemoteRegistry
readonly lookups: TypeRTLookupRegistry
readonly contexts: TypeRTContextRegistry
}
```
生成的消费方声明会把 direct namespace 合并到 `TypeRTClientRemote` 继承的 map 中。
```ts type-equiv
/** Merge-extensible direct namespace surface generated for Client Remote services. */
interface TypeRTRemoteNamespaceMap {}
```
## Host Gateway
Connection 会先解码 carrier envelope再调用 `ctx.typertGateway`。请求将精确的具名 wire 字段与 carrier 的取消 signal 分开携带;基础设施与边界失败使用 Gateway 的进程内错误分类体系,普通异常由 RPC 适配器折叠为传输层的 `internal` 错误码lookup 策略通过 `TypeRTLookupFailure` 携带的既有 RPC error 则原样返回。
```ts type-equiv
/** One Remote method request after a carrier has decoded its envelope. */
interface InvokeRemoteRequest {
/** Remote namespace selected by the generated descriptor. */
readonly namespace: string
/** Exported Service method name. */
readonly method: string
/** Named wire values; fields must exactly match the descriptor. */
readonly args: Readonly<Record<string, unknown>>
/** Carrier or direct-caller cancellation injected only into cancellation-aware methods. */
readonly signal?: AbortSignal
}
```
```ts type-equiv
/** Stable infrastructure and boundary failures emitted before or after business execution. */
type TypertGatewayErrorCode =
| 'ambiguous-endpoint'
| 'arguments-invalid'
| 'binding-invalid'
| 'context-failed'
| 'context-not-found'
| 'context-unavailable'
| 'definition-unavailable'
| 'input-invalid'
| 'invocation-unavailable'
| 'lookup-failed'
| 'lookup-not-found'
| 'lookup-unavailable'
| 'method-unavailable'
| 'provider-mismatch'
| 'result-invalid'
| 'service-unavailable'
| 'signature-invalid'
```
```ts type-equiv
/** Host dispatcher consumed by Connection adapters. */
interface TypertGateway {
/**
* Invoke one live Remote method without assuming a carrier or response envelope.
* @param request - decoded endpoint and named wire arguments.
* @returns the validated business result.
* @throws {@link TypertGatewayError} for dispatch, provider, or boundary failures; lookup-policy and business errors retain identity.
*/
invoke(request: InvokeRemoteRequest): Promise<unknown>
}
```
## 消费方 Remote
`ctx.remote` 只暴露由已导入 `/remote` 产物贡献的 namespace。`$mount()` 会把生成的 descriptor 与具体方法作为一项由 fiber 持有的操作统一注册。每个 namespace 都是可追踪的 `remote.<namespace>` Cordis 子 Service其生命周期覆盖已挂载的方法JavaScript Proxy 与 Host 业务 Service 类型都不会进入消费方。
```ts type-equiv
/** Client Remote capability implemented by the Gateway and consumed by Remote assemblies. */
interface TypeRTClientRemote extends TypeRTRemoteNamespaceMap {
/**
* Mount one generated Host-for-Client contribution in the caller's fiber.
* @param contribution - explicitly selected Remote package artifact.
* @returns disposer after namespace services and concrete methods are ready.
*/
$mount(contribution: TypeRTRemoteContribution): Promise<TypeRTDisposer>
}
```

View File

@@ -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 docs/development.md
development.md: 30a2bd0a2c97df8d3d75ec50f47b861b3a65590e
development.zh.md: 5582a85429c97c3e31517a495c69392b80885f7d
development.md: 60a7ccc87e2c33e66b3d966a2907d31bb0b1efd8
development.zh.md: 6607705be7e9f548b4f44555ad8c6cc8c2d34964

View File

@@ -43,24 +43,41 @@ Setup is complete when `pnpm run typecheck` exits successfully.
### TypeScript project layout
The repository typecheck runs the whole-repo `tsc -b` graph: it emits every package/vendor `lib/types` and checks examples, tests, and scripts through two no-emit aggregates.
The repository's TypeScript configuration has exactly three roles; every tsconfig file plays one of them.
The repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.
| File | Role | Forms a program? |
|---|---|---|
| `tsconfig.json` | Solution root: `extends` base, `files: []`, references to the two aggregates. The whole-repo `tsc -b tsconfig.json` graph, the tsserver discovery entry, and through the inherited `paths` the resolution config for tsx running `examples/` and `scripts/` (their nearest tsconfig is this file). | No |
| `tsconfig.host.json` | Host aggregate: host-side packages (via references), examples, tests, scripts, website. Excludes `packages/client`. | Yes |
| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`. | Yes |
| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |
| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |
| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |
| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |
| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the client aggregate and every `packages/client/*` package. | No |
| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |
Host and client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Two disciplines follow:
Host and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:
- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.
- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges. Program-backed generators and gates (`scripts/ts-project.ts` consumers, doc-typecheck standalone mode) are host-only by decision; the client side gains program-backed tooling only with a concrete need.
- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.
- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.
Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Decision record: [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md); the tsc-first emit pipeline is the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md).
`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf. Do not copy this structure to other packages; see the [`api-remotes` README](../packages/api/remotes/README.md) for the complete boundary.
The root build follows the generated dependency order:
```sh
tsc -b tsconfig.host.json
tsdown --env.DSH_BUILD_FACE host
tsc -b tsconfig.client.json
tsdown --env.DSH_BUILD_FACE client
pnpm run build:web
```
Both tsdown passes use the same complete workspace match. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase.
TypeRT runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start TypeRT. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. The [API Remotes generated-contract build note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md) records this ordering decision.
Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. See the [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md) for the two-aggregate topology and the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership.
Business services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order.
If a relevant local check consumes built package output, build once first:

View File

@@ -43,24 +43,41 @@ pnpm run typecheck
### TypeScript 项目布局
仓库类型检查会执行全仓 `tsc -b` 图:它会发射每个 package/vendor 的 `lib/types`,并通过两个 no-emit 聚合检查示例、测试和脚本
仓库的 TypeScript 配置只有三种角色;每个 tsconfig 文件恰好扮演其中一种。
仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregateHost 包进入 `tsconfig.host.json`Client 包进入 `tsconfig.client.json`
| 文件 | 角色 | 是否构成 program |
|---|---|---|
| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个聚合。全仓 `tsc -b tsconfig.json` 图、tsserver 发现入口,并经继承的 `paths` 充当 tsx 运行 `examples/``scripts/` 时的解析配置(它们最近的 tsconfig 就是此文件)。 | 否 |
| `tsconfig.host.json` | host 聚合host 侧各包(经 references、示例、测试、脚本website。排除 `packages/client`。 | 是 |
| `tsconfig.client.json` | client 聚合`packages/client/*` 各包及其测试、`apps/web`。 | 是 |
| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/``scripts/` 时的解析配置。 | 否 |
| `tsconfig.host.json` | Host aggregateHost package、示例、测试、脚本website,以及 `api/remotes` 的 Host 特例 project。 | 是 |
| `tsconfig.client.json` | Client aggregate`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |
| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |
| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 client 聚合和每个 `packages/client/*` extends。 | 否 |
| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |
host 与 client 保持两个聚合 program是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个聚合,一个 paths 门面也可以横跨两侧。由此推出条纪律:
Host 与 Client 保持两个 aggregate program是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出条纪律:
- `tsconfig.base.json` 永不添加 `include``files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。
- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json``tsconfig.client.json`——永不种子根 solution因为把两个聚合展平进一个 program 会撞上 `Context` 合并冲突。基于 program 的生成器与门禁(`scripts/ts-project.ts` 的消费者、doc-typecheck standalone 模式)按决策仅覆盖 host 侧client 侧只在出现真实需求时再获得基于 program 的工具。
- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json``tsconfig.client.json`——永不种子根 solution因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。
- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。
静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。决策记录:[solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md)tsc-first 发射管线见 [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md)。
`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution两个 aggregate 和直接消费方分别引用 `tsconfig.host.json``tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf不得引用 solution 根或另一侧 leaf。不要把该结构推广到其他包完整边界见 [`api-remotes` README](../packages/api/remotes/README.md)。
根构建按生成依赖排序:
```sh
tsc -b tsconfig.host.json
tsdown --env.DSH_BUILD_FACE host
tsc -b tsconfig.client.json
tsdown --env.DSH_BUILD_FACE client
pnpm run build:web
```
两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client package也不维护 Host/Client package 过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client plugin 在 Client 阶段同时生成 Node loader 与 browser bundle`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。
TypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影Client tsdown 不启动 TypeRT。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc`pnpm run build` 继续执行 Client tsdown 和 Web 构建。该顺序的决策记录见 [API Remotes 生成契约构建 Note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md)。
静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。双 aggregate 拓扑见 [solution-root Note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md)tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md)。
业务 Service 在 Host 使用 `@Remote``@RemoteScope` 声明可调用方法Host 构建生成 Host-for-Client 类型与运行时贡献Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。
如果相关的本地检查需要使用构建后的包产物,请先构建一次:

View File

@@ -27,13 +27,13 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:62`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:71`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-policy`](../packages/fs/fs-policy), [`skill-local`](../packages/skill/skill-local) |
| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:54`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
| `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:141`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-session`](../packages/goal/goal-session) |
| `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-session`](../packages/goal/goal-session) |
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/index.ts:73`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | `apiproxy`, [`llm`](../packages/llm/llm) |
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:62`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/support/llm-replay), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`session-title`](../packages/session-title/session-title) |
| `session/created` | `emit` | [`packages/core/session/src/index.ts:73`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:83`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:95`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`cli-demo`](../packages/examples/cli-demo), [`compact`](../packages/compact/compact), [`compact-basic`](../packages/compact/compact-basic), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection`](../packages/session-projection/session-projection), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-telemetry-otel`](../packages/telemetry/session-telemetry-otel), [`session-title`](../packages/session-title/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:104`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry) |
| `session/created` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:84`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:96`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`cli-demo`](../packages/examples/cli-demo), [`compact`](../packages/compact/compact), [`compact-basic`](../packages/compact/compact-basic), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection`](../packages/session-projection/session-projection), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-telemetry-otel`](../packages/telemetry/session-telemetry-otel), [`session-title`](../packages/session-title/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:105`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry) |
| `settings/document-updated` | `emit` | [`packages/settings/settings/src/index.ts:170`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `apiproxy` |
| `settings/updated` | `emit` | [`packages/settings/settings/src/index.ts:157`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) |
| `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:188`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - |
@@ -66,7 +66,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `credentials/changed` | `runtime` (`emit`) | `ui-models` |
| `internal/dispatch` | - | [`commands`](../packages/ui/commands), [`compact`](../packages/compact/compact), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`pty-local`](../packages/pty/pty-local), `runtime`, [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`workflow`](../packages/workflow/workflow) |
| `internal/plugin` | - | `hmr`, `loader`, `modules`, `webserver` |
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets) |
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
| `internal/status` | - | [`agent`](../packages/core/agent) |
| `locale/change` | `locale` (`emit`) | `locale` |
| `models/changed` | `runtime` (`emit`) | `ui-models` |

View File

@@ -10,6 +10,7 @@ flowchart TD
subgraph group_util["packages/util"]
pkg_atomic_write["atomic-write"]
pkg_brand["brand"]
pkg_environment["environment"]
pkg_native_command["native-command"]
pkg_paths["paths"]
pkg_retention["retention"]
@@ -25,7 +26,6 @@ flowchart TD
subgraph group_core["packages/core"]
pkg_agent["agent"]
pkg_agent_loop["agent-loop"]
pkg_agent_tool_mode["agent-tool-mode"]
pkg_scope["scope"]
pkg_session["session"]
pkg_system_prompt["system-prompt"]
@@ -147,6 +147,10 @@ flowchart TD
pkg_user_approval["user-approval"]
pkg_user_interaction["user-interaction"]
end
subgraph group_api["packages/api"]
pkg_api_gateway["api-gateway"]
pkg_api_remotes["api-remotes"]
end
subgraph group_bundle["packages/bundle"]
pkg_base["base"]
pkg_headless["headless"]
@@ -160,9 +164,9 @@ flowchart TD
pkg_client_runtime["client-runtime"]
pkg_client_schema_form["client-schema-form"]
pkg_client_test_runtime["client-test-runtime"]
pkg_client_ui_agent_preset["client-ui-agent-preset"]
pkg_client_ui_command["client-ui-command"]
pkg_client_ui_conversation["client-ui-conversation"]
pkg_client_ui_deliverables["client-ui-deliverables"]
pkg_client_ui_goal["client-ui-goal"]
pkg_client_ui_layout["client-ui-layout"]
pkg_client_ui_model["client-ui-model"]
@@ -229,7 +233,6 @@ flowchart TD
end
subgraph group_preset["packages/preset"]
pkg_agent_presets["agent-presets"]
pkg_persona["persona"]
end
subgraph group_pty["packages/pty"]
pkg_pty["pty"]
@@ -277,6 +280,7 @@ flowchart TD
pkg_session_telemetry_otel["session-telemetry-otel"]
end
subgraph group_typert["packages/typert"]
pkg_type_meta["type-meta"]
pkg_typert_generator["typert-generator"]
pkg_typert_loader["typert-loader"]
pkg_typert_registry["typert-registry"]
@@ -292,6 +296,7 @@ flowchart TD
end
pkg_atomic_write --> pkg_invariants
pkg_brand --> pkg_invariants
pkg_environment --> pkg_invariants
pkg_native_command --> pkg_invariants
pkg_paths --> pkg_invariants
pkg_retention --> pkg_invariants
@@ -303,7 +308,6 @@ flowchart TD
pkg_loader_smoke --> pkg_invariants
pkg_base --> pkg_invariants
pkg_client_modules --> pkg_invariants
pkg_client_runtime --> pkg_invariants
pkg_client_schema_form --> pkg_invariants
pkg_client_ui_primitives --> pkg_invariants
pkg_client_ui_slots --> pkg_invariants
@@ -311,10 +315,12 @@ flowchart TD
pkg_client_web_react --> pkg_invariants
pkg_code_runtime --> pkg_invariants
pkg_jsonrpc_demo --> pkg_invariants
pkg_host_apiproxy --> pkg_invariants
pkg_host_directory_picker --> pkg_invariants
pkg_host_webserver --> pkg_invariants
pkg_storage --> pkg_invariants
pkg_subprocess --> pkg_invariants
pkg_type_meta --> pkg_invariants
pkg_typert_generator --> pkg_invariants
pkg_typert_registry --> pkg_invariants
pkg_llm --> pkg_brand
@@ -325,21 +331,16 @@ flowchart TD
pkg_client_hmr --> pkg_client_modules
pkg_client_hmr --> pkg_host_webserver
pkg_client_hmr --> pkg_invariants
pkg_client_locale --> pkg_client_runtime
pkg_client_locale --> pkg_client_ui_primitives
pkg_client_locale --> pkg_client_ui_slots
pkg_client_locale --> pkg_invariants
pkg_client_ui_settings --> pkg_client_runtime
pkg_client_ui_settings --> pkg_client_ui_primitives
pkg_client_ui_settings --> pkg_client_ui_slots
pkg_client_ui_settings --> pkg_invariants
pkg_client_ui_trajectory --> pkg_client_runtime
pkg_client_ui_trajectory --> pkg_client_ui_primitives
pkg_client_ui_trajectory --> pkg_invariants
pkg_client_runtime --> pkg_invariants
pkg_client_runtime --> pkg_type_meta
pkg_client_runtime --> pkg_typert_registry
pkg_credentials --> pkg_brand
pkg_credentials --> pkg_invariants
pkg_frontend_static --> pkg_host_webserver
pkg_frontend_static --> pkg_invariants
pkg_agent_presets --> pkg_invariants
pkg_agent_presets --> pkg_paths
pkg_agent_presets --> pkg_scope
pkg_helper --> pkg_brand
pkg_helper --> pkg_invariants
pkg_helper --> pkg_subprocess
@@ -360,11 +361,13 @@ flowchart TD
pkg_typert_loader --> pkg_invariants
pkg_typert_loader --> pkg_typert_registry
pkg_llm_deepseek --> pkg_credentials
pkg_llm_deepseek --> pkg_environment
pkg_llm_deepseek --> pkg_invariants
pkg_llm_deepseek --> pkg_llm
pkg_llm_deepseek --> pkg_settings
pkg_llm_deepseek --> pkg_timeout
pkg_llm_pi_ai --> pkg_credentials
pkg_llm_pi_ai --> pkg_environment
pkg_llm_pi_ai --> pkg_invariants
pkg_llm_pi_ai --> pkg_llm
pkg_llm_pi_ai --> pkg_settings
@@ -373,11 +376,24 @@ flowchart TD
pkg_session --> pkg_invariants
pkg_session --> pkg_llm
pkg_session --> pkg_scope
pkg_session --> pkg_type_meta
pkg_system_prompt --> pkg_invariants
pkg_system_prompt --> pkg_llm
pkg_system_prompt --> pkg_scope
pkg_web --> pkg_invariants
pkg_web --> pkg_llm
pkg_api_gateway --> pkg_client_connection
pkg_api_gateway --> pkg_invariants
pkg_api_gateway --> pkg_typert_registry
pkg_client_locale --> pkg_client_runtime
pkg_client_locale --> pkg_client_ui_primitives
pkg_client_locale --> pkg_client_ui_slots
pkg_client_locale --> pkg_invariants
pkg_client_test_runtime --> pkg_client_runtime
pkg_client_test_runtime --> pkg_client_ui_slots
pkg_client_test_runtime --> pkg_client_web_react
pkg_client_test_runtime --> pkg_host_apiproxy
pkg_client_test_runtime --> pkg_invariants
pkg_client_ui_models --> pkg_client_connection
pkg_client_ui_models --> pkg_client_runtime
pkg_client_ui_models --> pkg_client_schema_form
@@ -385,6 +401,63 @@ flowchart TD
pkg_client_ui_models --> pkg_client_ui_slots
pkg_client_ui_models --> pkg_client_web_react
pkg_client_ui_models --> pkg_invariants
pkg_client_ui_settings --> pkg_client_runtime
pkg_client_ui_settings --> pkg_client_ui_primitives
pkg_client_ui_settings --> pkg_client_ui_slots
pkg_client_ui_settings --> pkg_invariants
pkg_client_ui_trajectory --> pkg_client_runtime
pkg_client_ui_trajectory --> pkg_client_ui_primitives
pkg_client_ui_trajectory --> pkg_invariants
pkg_credentials_local --> pkg_atomic_write
pkg_credentials_local --> pkg_credentials
pkg_credentials_local --> pkg_environment
pkg_credentials_local --> pkg_invariants
pkg_credentials_local --> pkg_paths
pkg_lsp --> pkg_brand
pkg_lsp --> pkg_invariants
pkg_lsp --> pkg_llm
pkg_sandbox --> pkg_invariants
pkg_sandbox --> pkg_llm
pkg_settings_local --> pkg_atomic_write
pkg_settings_local --> pkg_invariants
pkg_settings_local --> pkg_paths
pkg_settings_local --> pkg_settings
pkg_agent --> pkg_invariants
pkg_agent --> pkg_llm
pkg_agent --> pkg_scope
pkg_agent --> pkg_session
pkg_agent --> pkg_system_prompt
pkg_agent --> pkg_type_meta
pkg_bash --> pkg_invariants
pkg_bash --> pkg_sandbox
pkg_bash --> pkg_subprocess
pkg_fs --> pkg_brand
pkg_fs --> pkg_invariants
pkg_fs --> pkg_llm
pkg_fs --> pkg_sandbox
pkg_compact --> pkg_invariants
pkg_compact --> pkg_llm
pkg_compact --> pkg_session
pkg_web_fetch_local --> pkg_invariants
pkg_web_fetch_local --> pkg_timeout
pkg_web_fetch_local --> pkg_web
pkg_web_search_exa --> pkg_environment
pkg_web_search_exa --> pkg_invariants
pkg_web_search_exa --> pkg_web
pkg_web_search_perplexity --> pkg_environment
pkg_web_search_perplexity --> pkg_invariants
pkg_web_search_perplexity --> pkg_web
pkg_spill --> pkg_brand
pkg_spill --> pkg_invariants
pkg_spill --> pkg_llm
pkg_spill --> pkg_session
pkg_session_persistence --> pkg_brand
pkg_session_persistence --> pkg_invariants
pkg_session_persistence --> pkg_session
pkg_app_boot --> pkg_environment
pkg_app_boot --> pkg_invariants
pkg_app_boot --> pkg_paths
pkg_app_boot --> pkg_system_prompt
pkg_client_ui_question --> pkg_client_locale
pkg_client_ui_question --> pkg_invariants
pkg_client_ui_settings_general --> pkg_client_connection
@@ -415,86 +488,16 @@ flowchart TD
pkg_client_ui_workspace --> pkg_client_ui_primitives
pkg_client_ui_workspace --> pkg_client_ui_slots
pkg_client_ui_workspace --> pkg_invariants
pkg_credentials_local --> pkg_atomic_write
pkg_credentials_local --> pkg_credentials
pkg_credentials_local --> pkg_invariants
pkg_credentials_local --> pkg_paths
pkg_lsp --> pkg_brand
pkg_lsp --> pkg_invariants
pkg_lsp --> pkg_llm
pkg_sandbox --> pkg_invariants
pkg_sandbox --> pkg_llm
pkg_settings_local --> pkg_atomic_write
pkg_settings_local --> pkg_invariants
pkg_settings_local --> pkg_paths
pkg_settings_local --> pkg_settings
pkg_agent --> pkg_invariants
pkg_agent --> pkg_llm
pkg_agent --> pkg_scope
pkg_agent --> pkg_session
pkg_agent --> pkg_system_prompt
pkg_bash --> pkg_invariants
pkg_bash --> pkg_sandbox
pkg_bash --> pkg_subprocess
pkg_fs --> pkg_brand
pkg_fs --> pkg_invariants
pkg_fs --> pkg_llm
pkg_fs --> pkg_sandbox
pkg_compact --> pkg_invariants
pkg_compact --> pkg_llm
pkg_compact --> pkg_session
pkg_web_fetch_local --> pkg_invariants
pkg_web_fetch_local --> pkg_timeout
pkg_web_fetch_local --> pkg_web
pkg_web_search_exa --> pkg_invariants
pkg_web_search_exa --> pkg_web
pkg_web_search_perplexity --> pkg_invariants
pkg_web_search_perplexity --> pkg_web
pkg_spill --> pkg_brand
pkg_spill --> pkg_invariants
pkg_spill --> pkg_llm
pkg_spill --> pkg_session
pkg_session_persistence --> pkg_brand
pkg_session_persistence --> pkg_invariants
pkg_session_persistence --> pkg_session
pkg_llm_replay --> pkg_invariants
pkg_llm_replay --> pkg_llm
pkg_llm_replay --> pkg_session
pkg_app_boot --> pkg_invariants
pkg_app_boot --> pkg_paths
pkg_app_boot --> pkg_system_prompt
pkg_client_ui_layout --> pkg_client_runtime
pkg_client_ui_layout --> pkg_client_ui_slots
pkg_client_ui_layout --> pkg_client_ui_theme
pkg_client_ui_layout --> pkg_invariants
pkg_code_runtime_worker --> pkg_code_runtime
pkg_code_runtime_worker --> pkg_invariants
pkg_code_runtime_worker --> pkg_session
pkg_code_runtime_worker --> pkg_timeout
pkg_host_directory_picker_browse --> pkg_client_locale
pkg_host_directory_picker_browse --> pkg_client_runtime
pkg_host_directory_picker_browse --> pkg_client_ui_primitives
pkg_host_directory_picker_browse --> pkg_client_ui_slots
pkg_host_directory_picker_browse --> pkg_client_ui_workspace
pkg_host_directory_picker_browse --> pkg_invariants
pkg_host_directory_picker_native --> pkg_client_runtime
pkg_host_directory_picker_native --> pkg_client_ui_slots
pkg_host_directory_picker_native --> pkg_client_ui_workspace
pkg_host_directory_picker_native --> pkg_invariants
pkg_lsp_local --> pkg_brand
pkg_lsp_local --> pkg_invariants
pkg_lsp_local --> pkg_llm
pkg_lsp_local --> pkg_lsp
pkg_lsp_local --> pkg_subprocess
pkg_lsp_local --> pkg_timeout
pkg_agent_presets --> pkg_atomic_write
pkg_agent_presets --> pkg_invariants
pkg_agent_presets --> pkg_paths
pkg_agent_presets --> pkg_scope
pkg_agent_presets --> pkg_session
pkg_agent_presets --> pkg_settings
pkg_persona --> pkg_invariants
pkg_persona --> pkg_system_prompt
pkg_sandbox_local --> pkg_invariants
pkg_sandbox_local --> pkg_llm
pkg_sandbox_local --> pkg_sandbox
@@ -517,6 +520,7 @@ flowchart TD
pkg_goal --> pkg_scope
pkg_goal --> pkg_session
pkg_goal --> pkg_session_projection
pkg_goal --> pkg_type_meta
pkg_bash_local --> pkg_bash
pkg_bash_local --> pkg_invariants
pkg_bash_local --> pkg_subprocess
@@ -535,6 +539,7 @@ flowchart TD
pkg_skill_local --> pkg_skill
pkg_web_search_deepseek --> pkg_agent
pkg_web_search_deepseek --> pkg_credentials
pkg_web_search_deepseek --> pkg_environment
pkg_web_search_deepseek --> pkg_invariants
pkg_web_search_deepseek --> pkg_session
pkg_web_search_deepseek --> pkg_web
@@ -554,6 +559,10 @@ flowchart TD
pkg_session_title --> pkg_llm
pkg_session_title --> pkg_session
pkg_session_title --> pkg_session_projection
pkg_llm_replay --> pkg_compact
pkg_llm_replay --> pkg_invariants
pkg_llm_replay --> pkg_llm
pkg_llm_replay --> pkg_session
pkg_commands --> pkg_agent
pkg_commands --> pkg_brand
pkg_commands --> pkg_invariants
@@ -569,6 +578,15 @@ flowchart TD
pkg_user_interaction --> pkg_agent
pkg_user_interaction --> pkg_invariants
pkg_user_interaction --> pkg_llm
pkg_headless --> pkg_agent
pkg_headless --> pkg_host_apiproxy
pkg_headless --> pkg_host_webserver
pkg_headless --> pkg_invariants
pkg_headless --> pkg_session
pkg_client_ui_layout --> pkg_client_runtime
pkg_client_ui_layout --> pkg_client_ui_slots
pkg_client_ui_layout --> pkg_client_ui_theme
pkg_client_ui_layout --> pkg_invariants
pkg_time_context --> pkg_agent
pkg_time_context --> pkg_invariants
pkg_time_context --> pkg_session
@@ -576,12 +594,16 @@ flowchart TD
pkg_tmux_context --> pkg_bash
pkg_tmux_context --> pkg_invariants
pkg_tmux_context --> pkg_session
pkg_host_apiproxy --> pkg_agent_presets
pkg_host_apiproxy --> pkg_invariants
pkg_host_directory_picker_auto --> pkg_host_directory_picker_browse
pkg_host_directory_picker_auto --> pkg_host_directory_picker_native
pkg_host_directory_picker_auto --> pkg_host_webserver
pkg_host_directory_picker_auto --> pkg_invariants
pkg_host_directory_picker_browse --> pkg_client_locale
pkg_host_directory_picker_browse --> pkg_client_runtime
pkg_host_directory_picker_browse --> pkg_client_ui_primitives
pkg_host_directory_picker_browse --> pkg_client_ui_slots
pkg_host_directory_picker_browse --> pkg_client_ui_workspace
pkg_host_directory_picker_browse --> pkg_invariants
pkg_host_directory_picker_native --> pkg_client_runtime
pkg_host_directory_picker_native --> pkg_client_ui_slots
pkg_host_directory_picker_native --> pkg_client_ui_workspace
pkg_host_directory_picker_native --> pkg_invariants
pkg_pty --> pkg_agent
pkg_pty --> pkg_brand
pkg_pty --> pkg_invariants
@@ -673,16 +695,12 @@ flowchart TD
pkg_permission --> pkg_session_projection
pkg_permission --> pkg_settings
pkg_permission --> pkg_user_approval
pkg_headless --> pkg_agent
pkg_headless --> pkg_host_apiproxy
pkg_headless --> pkg_host_webserver
pkg_headless --> pkg_invariants
pkg_headless --> pkg_session
pkg_client_test_runtime --> pkg_client_runtime
pkg_client_test_runtime --> pkg_client_ui_slots
pkg_client_test_runtime --> pkg_client_web_react
pkg_client_test_runtime --> pkg_host_apiproxy
pkg_client_test_runtime --> pkg_invariants
pkg_api_remotes --> pkg_agent
pkg_api_remotes --> pkg_goal
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_typert_registry
pkg_client_ui_conversation --> pkg_client_locale
pkg_client_ui_conversation --> pkg_client_runtime
pkg_client_ui_conversation --> pkg_client_ui_primitives
@@ -693,6 +711,10 @@ flowchart TD
pkg_command_feedback --> pkg_commands
pkg_command_feedback --> pkg_invariants
pkg_command_feedback --> pkg_session
pkg_host_directory_picker_auto --> pkg_host_directory_picker_browse
pkg_host_directory_picker_auto --> pkg_host_directory_picker_native
pkg_host_directory_picker_auto --> pkg_host_webserver
pkg_host_directory_picker_auto --> pkg_invariants
pkg_pty_local --> pkg_agent
pkg_pty_local --> pkg_invariants
pkg_pty_local --> pkg_pty
@@ -712,8 +734,6 @@ flowchart TD
pkg_agent_loop --> pkg_session_persistence
pkg_agent_loop --> pkg_system_prompt
pkg_agent_loop --> pkg_tools
pkg_agent_tool_mode --> pkg_invariants
pkg_agent_tool_mode --> pkg_tools
pkg_tool_goal --> pkg_agent
pkg_tool_goal --> pkg_goal
pkg_tool_goal --> pkg_invariants
@@ -848,15 +868,6 @@ flowchart TD
pkg_tool_ask_user --> pkg_invariants
pkg_tool_ask_user --> pkg_tools
pkg_tool_ask_user --> pkg_user_interaction
pkg_client_ui_agent_preset --> pkg_client_connection
pkg_client_ui_agent_preset --> pkg_client_locale
pkg_client_ui_agent_preset --> pkg_client_runtime
pkg_client_ui_agent_preset --> pkg_client_ui_conversation
pkg_client_ui_agent_preset --> pkg_client_ui_primitives
pkg_client_ui_agent_preset --> pkg_client_ui_settings
pkg_client_ui_agent_preset --> pkg_client_ui_slots
pkg_client_ui_agent_preset --> pkg_client_web_react
pkg_client_ui_agent_preset --> pkg_invariants
pkg_client_ui_command --> pkg_client_connection
pkg_client_ui_command --> pkg_client_locale
pkg_client_ui_command --> pkg_client_runtime
@@ -865,7 +876,12 @@ flowchart TD
pkg_client_ui_command --> pkg_client_ui_slash
pkg_client_ui_command --> pkg_client_ui_slots
pkg_client_ui_command --> pkg_invariants
pkg_client_ui_goal --> pkg_client_connection
pkg_client_ui_deliverables --> pkg_client_locale
pkg_client_ui_deliverables --> pkg_client_runtime
pkg_client_ui_deliverables --> pkg_client_ui_conversation
pkg_client_ui_deliverables --> pkg_client_ui_slots
pkg_client_ui_deliverables --> pkg_invariants
pkg_client_ui_goal --> pkg_api_remotes
pkg_client_ui_goal --> pkg_client_locale
pkg_client_ui_goal --> pkg_client_runtime
pkg_client_ui_goal --> pkg_client_ui_conversation
@@ -1150,6 +1166,7 @@ flowchart TD
| [`invariants`](../packages/support/invariants) | `support` | — |
| [`atomic-write`](../packages/util/atomic-write) | `util` | [`invariants`](../packages/support/invariants) |
| [`brand`](../packages/util/brand) | `util` | [`invariants`](../packages/support/invariants) |
| [`environment`](../packages/util/environment) | `util` | [`invariants`](../packages/support/invariants) |
| [`native-command`](../packages/util/native-command) | `util` | [`invariants`](../packages/support/invariants) |
| [`paths`](../packages/util/paths) | `util` | [`invariants`](../packages/support/invariants) |
| [`retention`](../packages/util/retention) | `util` | [`invariants`](../packages/support/invariants) |
@@ -1161,7 +1178,6 @@ flowchart TD
| [`loader-smoke`](../packages/support/loader-smoke) | `support` | [`invariants`](../packages/support/invariants) |
| [`base`](../packages/bundle/base) | `bundle` | [`invariants`](../packages/support/invariants) |
| [`client-modules`](../packages/client/modules) | `client` | [`invariants`](../packages/support/invariants) |
| [`client-runtime`](../packages/client/runtime) | `client` | [`invariants`](../packages/support/invariants) |
| [`client-schema-form`](../packages/client/schema-form) | `client` | [`invariants`](../packages/support/invariants) |
| [`client-ui-primitives`](../packages/client/ui-primitives) | `client` | [`invariants`](../packages/support/invariants) |
| [`client-ui-slots`](../packages/client/ui-slots) | `client` | [`invariants`](../packages/support/invariants) |
@@ -1169,20 +1185,21 @@ flowchart TD
| [`client-web-react`](../packages/client/web-react) | `client` | [`invariants`](../packages/support/invariants) |
| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | [`invariants`](../packages/support/invariants) |
| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | [`invariants`](../packages/support/invariants) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`invariants`](../packages/support/invariants) |
| [`host-directory-picker`](../packages/host/directory-picker) | `host` | [`invariants`](../packages/support/invariants) |
| [`host-webserver`](../packages/host/webserver) | `host` | [`invariants`](../packages/support/invariants) |
| [`storage`](../packages/storage/storage) | `storage` | [`invariants`](../packages/support/invariants) |
| [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`invariants`](../packages/support/invariants) |
| [`type-meta`](../packages/typert/type-meta) | `typert` | [`invariants`](../packages/support/invariants) |
| [`typert-generator`](../packages/typert/generator) | `typert` | [`invariants`](../packages/support/invariants) |
| [`typert-registry`](../packages/typert/registry) | `typert` | [`invariants`](../packages/support/invariants) |
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout) |
| [`client-connection`](../packages/client/connection) | `client` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`client-hmr`](../packages/client/hmr) | `client` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`client-locale`](../packages/client/locale) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-settings`](../packages/client/ui-settings) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`invariants`](../packages/support/invariants) |
| [`client-runtime`](../packages/client/runtime) | `client` | [`invariants`](../packages/support/invariants), [`type-meta`](../packages/typert/type-meta), [`typert-registry`](../packages/typert/registry) |
| [`credentials`](../packages/credentials/credentials) | `credentials` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
| [`frontend-static`](../packages/host/frontend-static) | `host` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`scope`](../packages/core/scope) |
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess) |
| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) |
| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
@@ -1191,63 +1208,65 @@ flowchart TD
| [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`invariants`](../packages/support/invariants), [`storage`](../packages/storage/storage) |
| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`typert-loader`](../packages/typert/loader) | `typert` | [`invariants`](../packages/support/invariants), [`typert-registry`](../packages/typert/registry) |
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`credentials`](../packages/credentials/credentials), [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`credentials`](../packages/credentials/credentials), [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`type-meta`](../packages/typert/type-meta) |
| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
| [`web`](../packages/web/web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection), [`invariants`](../packages/support/invariants), [`typert-registry`](../packages/typert/registry) |
| [`client-locale`](../packages/client/locale) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-test-runtime`](../packages/client/test-runtime) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/support/invariants) |
| [`client-ui-models`](../packages/client/ui-models) | `client` | [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) |
| [`client-ui-settings`](../packages/client/ui-settings) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`invariants`](../packages/support/invariants) |
| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) |
| [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`settings-local`](../packages/settings/settings-local) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`settings`](../packages/settings/settings) |
| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`type-meta`](../packages/typert/type-meta) |
| [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) |
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
| [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`app-boot`](../packages/ui/app-boot) | `ui` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`system-prompt`](../packages/core/system-prompt) |
| [`client-ui-question`](../packages/client/ui-question) | `client` | [`client-locale`](../packages/client/locale), [`invariants`](../packages/support/invariants) |
| [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) |
| [`client-ui-sidebar`](../packages/client/ui-sidebar) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-slash`](../packages/client/ui-slash) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-theme`](../packages/client/ui-theme) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) |
| [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`settings-local`](../packages/settings/settings-local) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`settings`](../packages/settings/settings) |
| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
| [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) |
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
| [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`llm-replay`](../packages/support/llm-replay) | `support` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`app-boot`](../packages/ui/app-boot) | `ui` | [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`system-prompt`](../packages/core/system-prompt) |
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/support/invariants) |
| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | `host` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) |
| [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) |
| [`lsp-local`](../packages/lsp/lsp-local) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`atomic-write`](../packages/util/atomic-write), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`settings`](../packages/settings/settings) |
| [`persona`](../packages/preset/persona) | `preset` | [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt) |
| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
| [`session-projection`](../packages/session-projection/session-projection) | `session-projection` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection) |
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection) |
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`type-meta`](../packages/typert/type-meta) |
| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`pwsh-local`](../packages/bash/pwsh-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) |
| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) |
| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`skill`](../packages/skill/skill) |
| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`web`](../packages/web/web) |
| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`web`](../packages/web/web) |
| [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/support/invariants), [`spill`](../packages/spill/spill) |
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
| [`session-title`](../packages/session-title/session-title) | `session-title` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection) |
| [`llm-replay`](../packages/support/llm-replay) | `support` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`commands`](../packages/ui/commands) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session) |
| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`host-apiproxy`](../packages/host/apiproxy), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/support/invariants) |
| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`invariants`](../packages/support/invariants) |
| [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | `host` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) |
| [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) |
| [`pty`](../packages/pty/pty) | `pty` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants) |
@@ -1267,14 +1286,13 @@ flowchart TD
| [`session-title-llm`](../packages/session-title/session-title-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`timeout`](../packages/util/timeout) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) |
| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`settings`](../packages/settings/settings), [`user-approval`](../packages/ui/user-approval) |
| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`host-apiproxy`](../packages/host/apiproxy), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`client-test-runtime`](../packages/client/test-runtime) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/support/invariants) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`typert-registry`](../packages/typert/registry) |
| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`token-meter`](../packages/llm/token-meter) |
| [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
| [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`pty-local`](../packages/pty/pty-local) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess) |
| [`tasks-local`](../packages/tasks/tasks-local) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tasks`](../packages/tasks/tasks), [`timeout`](../packages/util/timeout) |
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`agent-tool-mode`](../packages/core/agent-tool-mode) | `core` | [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`bash-env`](../packages/bash/bash-env) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) |
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
@@ -1297,9 +1315,9 @@ flowchart TD
| [`session-title-first-message-llm`](../packages/session-title/session-title-first-message-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`session-title-llm`](../packages/session-title/session-title-llm) |
| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
| [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) |
| [`client-ui-command`](../packages/client/ui-command) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) |
| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) |
| [`client-ui-skill`](../packages/client/ui-skill) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) |
| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |

View File

@@ -356,7 +356,7 @@ Source: [`packages/feedback/command-feedback/src/index.ts:24`](../packages/feedb
'goal/change': GoalChangeMeta
```
Source: [`packages/goal/goal/src/domain.ts:81`](../packages/goal/goal/src/domain.ts)
Source: [`packages/goal/goal/src/domain.ts:66`](../packages/goal/goal/src/domain.ts)
### `hook/*`

View File

@@ -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 docs/user/guide/config.md
config.md: 0f1a99ed0afdab13d052ae2714fc2e1718d3d85e
config.zh.md: 74e14e4e6e1fbb38a7a8d5167a4747080a662128
config.md: 34ab38c60cc7a9b6026f5be2be47f440eb6cb08d
config.zh.md: ce965dfc67c759ffcbad7b044b74abb2025851c2

View File

@@ -18,10 +18,6 @@ A minimal configuration is a list of plugin entries:
```yaml
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
models:
- deepseek-v4-flash
- id: bash
name: '@deepseek-ai/dsh-bash-local'
@@ -53,15 +49,14 @@ Cordis starts sibling entries concurrently. A plugin declares required services
`dsh --profile <name>` composes the profile's bundle patch layers (its manifest's `dsh.profile.bundles` list, in order) over an empty root, then the profile's own `~/.dsh/profiles/<name>/cordis.patch.yml`, then each `--patch <path>` overlay, then CLI-flag patches. Later layers win per row.
A patch replaces a row's entire `config` value; it does not deep-merge keys. For example, patching `llm-deepseek` with only `config: { thinking: disabled }` also removes that row's configured `apiKey` and `baseURL`, so restate every key the row must retain.
A patch replaces a row's entire `config` value; it does not deep-merge keys. For example, patching `llm-deepseek` with only `config: { thinking: disabled }` also removes that row's configured `apiKeyEnv` and `baseURL`, so restate every key the row must retain.
## JavaScript values and environment variables
The Cordis loader evaluates runtime expressions tagged with `!!js`. Keep API keys and other secrets in the gitignored `.env` file at the repository root, never in committed configuration.
The Cordis loader evaluates runtime expressions tagged with `!!js` for non-secret runtime values. Bundled LLM adapters carry credential references such as `apiKeyEnv`; the value belongs in an environment layer or `$DSH_HOME/.credentials.yaml`, not Cordis configuration.
```yaml
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
cwd: !!js process.cwd()
```

View File

@@ -18,10 +18,6 @@ Harness 使用 `cordis.yml` 描述 agent智能体加载哪些插件以及
```yaml
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
models:
- deepseek-v4-flash
- id: bash
name: '@deepseek-ai/dsh-bash-local'
@@ -53,15 +49,14 @@ Cordis 会并发启动同级配置项。插件通过 `inject` 声明必需服务
`dsh --profile <name>` 按该 profile 的 manifest元数据清单`dsh.profile.bundles` 列表的顺序,在空根之上组合各组合包补丁层,随后依次应用该 profile 自己的 `~/.dsh/profiles/<name>/cordis.patch.yml`、每个 `--patch <path>` overlay最后是 CLI命令行界面标志补丁。同一行以较后的层为准。
补丁会替换目标行的整个 `config` 值,而不是深度合并各个键。例如,只用 `config: { thinking: disabled }` 修补 `llm-deepseek`,也会移除该行原有的 `apiKey``baseURL`;因此必须重新写出该行需要保留的全部键。
补丁会替换目标行的整个 `config` 值,而不是深度合并各个键。例如,只用 `config: { thinking: disabled }` 修补 `llm-deepseek`,也会移除该行原有的 `apiKeyEnv``baseURL`;因此必须重新写出该行需要保留的全部键。
## JavaScript 值和环境变量
Cordis loader 使用 `!!js`签读取运行时表达式。API key 等凭据应放在仓库根目录、已被 Git 忽略的 `.env` 中,不能提交到配置文件
Cordis loader 会求值以 `!!js`记的运行时表达式,用于非机密的运行时值。仓库内置的 LLM大语言模型适配器携带 `apiKeyEnv` 等凭据引用;对应的值应放在环境层或 `$DSH_HOME/.credentials.yaml`,而不是 Cordis 配置中
```yaml
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
cwd: !!js process.cwd()
```

View File

@@ -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 docs/user/guide/index.md
index.md: 4bb9f2e0056792a160877515f142eb36d4f680ac
index.zh.md: 2792547a146b5ca6186bcb69c1c026744e80b326
index.md: ede09506a996193fe5cf4ae6cd9b64d3529798a6
index.zh.md: 5f72a6d3099d2d4721eccebae92eacfe72d33bce

View File

@@ -11,8 +11,6 @@ Harness implements every capability an AI agent needs—including LLM calls, too
```yaml
# Select the LLM backend
- name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
# Select the one-shot application
- id: cli-agent

View File

@@ -11,8 +11,6 @@ Harness 将一个 AI Agent智能体 所需要的所有能力——LLM 调
```yaml
# Select the LLM backend
- name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
# Select the one-shot application
- id: cli-agent

View File

@@ -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 docs/user/guide/providers.md
providers.md: 66b6cf25c61a252fbd10a85f8c79c246eeae8abe
providers.zh.md: a2c33c90be971e09ab29e2355ca6a7ae6f947c39
providers.md: 5fd11f3753468f9eccd318045b1ac5f6b3a21622
providers.zh.md: 3e71e3fb2c64fb71fed196433175c860d3f6c817

View File

@@ -31,7 +31,7 @@ That holds for providers that authenticate with an API key. The catalog also car
**Let the endpoint report its models.** Expand **Model catalog** and choose **Fetch available models**: the interrogation asks the endpoint **the form currently shows** — including a base URL edited but not yet saved and a key typed but not yet stored — and offers what it reports as candidates to pick from. A route the installed catalog describes is answered from that catalog with no network call. Adopting a candidate only writes rows into the draft; nothing is stored until you save.
Keys are write-only: the page only ever holds a redacted descriptor, never the literal secret. A key you enter is stored in `$DSH_HOME/.env`, and the profile records only the variable name that references it.
Keys are write-only: the page only ever holds a redacted descriptor, never the literal secret. A key you enter is stored in `$DSH_HOME/.credentials.yaml`, and the profile records only the variable name that references it.
## settings.yaml for advanced configuration
@@ -89,24 +89,26 @@ Model ids are not lifecycle configuration. Requesting a model the route does not
## Credentials
Prefer `apiKeyEnv`: it is a *reference* resolved per request, so no secret enters the configuration file. A literal `apiKey` is the escape hatch. Omitting both is what leaves a route unauthenticated, which for a catalog route means pi-ai's own environment discovery. A reference that resolves to nothing fails the request with `MISSING_CREDENTIAL` rather than falling through to whatever unrelated key the environment happens to hold.
Use `apiKeyEnv`: it is a *reference* resolved per request, so no secret enters the configuration file. Omitting it leaves a route unauthenticated, which for a catalog route means pi-ai's own environment discovery. A reference that resolves to nothing fails the request with `MISSING_CREDENTIAL` rather than falling through to whatever unrelated key the environment happens to hold.
References resolve from `$DSH_HOME/.env` — what the Models page's key fields write — and from the matching environment variable when no credential service is mounted. One credential serves every model on its route.
Under `dsh`, references resolve from the inherited environment, the Models page's `$DSH_HOME/.credentials.yaml` store, the invoking directory's `.env`, then `$DSH_HOME/.env`. Without a credential service, a reference reads only the matching environment variable. One credential serves every model on its route.
## Point an agent at the new provider
A configured route appears in the web model picker and can be switched at any time, which is how most people use it.
A new session's default model comes from the `api-gateway` entry (`@deepseek-ai/dsh-host-apiproxy`) and its `provider` and `model`, which ship as `deepseek-official` and `deepseek-v4-flash`. To change that default, override the entry in `$DSH_HOME/config.yaml`:
Switching there also sets the default: the model you pick becomes the one the next new session starts on, recorded in `settings.yaml` under `api-gateway`. There is no separate gesture.
```yaml
- id: api-gateway
config:
provider: acme-gateway
model: acme-large
api-gateway:
provider: acme-gateway
model: acme-large
reasoningEffort: high # optional
```
A patch replaces that entry's whole `config`, so write out every key it needs to keep. A composition you assemble yourself — headless, for instance — sets `agent-loop`'s `agents` instead.
A session that has already run a turn is never retargeted by it — that session derives its route from its own log, so changing the default only reaches sessions that have not started. The shipped fallback under this section is the `api-gateway` composition entry (`deepseek-official` / `deepseek-v4-flash`), which a self-assembled `cordis.yml` may override; a composition you assemble yourself — headless, for instance — sets `agent-loop`'s `agents` instead.
If the provider a saved default names is later removed, the composer says **Select model** and refuses input until you pick one, rather than sending to a route nothing serves.
## Troubleshooting

View File

@@ -2,7 +2,7 @@
[English](providers.md) | 中文
Harness 出厂带 DeepSeek同时挂着一个通用的多提供方适配器,用来接入 Anthropic、OpenAI 这类内置目录里的提供方,或任何 OpenAI 兼容的网关与自建服务。你有两个入口Web 界面的**模型**页,以及 `$DSH_HOME/settings.yaml`。两者写的是同一份文档,改完下一次请求即生效,不用重启。
Harness 出厂带 DeepSeek同时预装了一个通用的多提供方适配器,用来接入 Anthropic、OpenAI 这类内置目录里的提供方,或任何 OpenAI 兼容的网关与自建服务。你有两个入口Web 界面的**模型**页,以及 `$DSH_HOME/settings.yaml`。两者写的是同一份文档,改完下一次请求即生效,不用重启。
## 提供方从哪里来
@@ -31,7 +31,7 @@ Harness 出厂就带 DeepSeek同时挂着一个通用的多提供方适配器
**让端点自己报模型。** 展开**模型目录**后点**获取可用模型**,会按你**当前表单里**的地址与密钥去问端点(地址改了但没保存、密钥刚输入还没存下,都算数),把它报告的模型列成候选让你勾选。内置目录里的路由直接由目录作答,不联网。采纳只是把行写进草稿,最终还是你点保存才落盘。
密钥是只写的:页面拿到的永远是脱敏描述符,不是明文。写入的密钥存进 `$DSH_HOME/.env`profile 里只记录引用它的变量名。
密钥是只写的:页面拿到的永远是脱敏描述符,不是明文。写入的密钥存进 `$DSH_HOME/.credentials.yaml`profile 里只记录引用它的变量名。
## settings.yaml进阶配置
@@ -89,24 +89,26 @@ settings 段落**逐个提供方**地盖在 `cordis.yml` 的同名配置之上
## 凭据
优先`apiKeyEnv`——它是一个**引用**,每次请求时解析,密钥本身不进配置文件`apiKey` 字面量是应急出口。两者都不给,才表示这个路由不带认证,对内置目录路由意味着交给 pi-ai 自己的环境发现。给了引用却解析不到,请求会以 `MISSING_CREDENTIAL` 失败,而不是退回去用环境里碰巧存在的某个不相干的 key 计费。
使`apiKeyEnv`——它是一个**引用**,每次请求时解析,密钥本身不进配置文件。省略它会让路由不带认证,对内置目录路由意味着交给 pi-ai 自己的环境发现。给了引用却解析不到,请求会以 `MISSING_CREDENTIAL` 失败,而不是退回去用环境里碰巧存在的某个不相干的 key 计费。
引用解析自 `$DSH_HOME/.env`(模型页的密钥输入框写的就是它),没有挂载凭据服务时则直接读同名环境变量。一份凭据供该路由上的所有模型使用。
`dsh` 下,引用依次从继承环境、模型页的 `$DSH_HOME/.credentials.yaml` 存储、调用目录的 `.env``$DSH_HOME/.env` 解析。未挂载凭据服务时,引用只读取同名环境变量。一份凭据供该路由上的所有模型使用。
## 让 agent 用上新提供方
配好的路由会出现在 Web 的模型选择器里,随时可切,这也是最常用的方式。
新会话的默认模型来自 `api-gateway` 那条(`@deepseek-ai/dsh-host-apiproxy`)的 `provider``model`,出厂值是 `deepseek-official``deepseek-v4-flash`。要改默认值,就在 `$DSH_HOME/config.yaml` 里覆盖该条:
在那里切换同时也就选定了默认值:你选的模型会成为下一个新会话的起点,记录在 `settings.yaml``api-gateway` 段里。没有另一个单独的手势。
```yaml
- id: api-gateway
config:
provider: acme-gateway
model: acme-large
api-gateway:
provider: acme-gateway
model: acme-large
reasoningEffort: high # optional
```
补丁会整体替换该条的 `config`,所以要把这条需要保留的键一并写出。自行组装的 `cordis.yml`(例如 headless改的则是 `agent-loop``agents`
已经跑过一轮的会话不会被它重定向——那个会话从自己的日志推导路由,因此改默认值只影响还没开始的会话。这个段落之下的出厂兜底是 `api-gateway` 组合条目(`deepseek-official` / `deepseek-v4-flash`自行组装的 `cordis.yml` 可以覆盖它;自行组装的组合(例如 headless改的则是 `agent-loop``agents`
如果某个已存默认值指向的提供方后来被删掉了,输入框会显示**选择模型**并拒绝输入,而不是把消息发给一个没人服务的路由。
## 排错