docs: sync subsystem docs and regenerate catalogs

This commit is contained in:
imccyu committed 2026-10-13 23:03:15 +08:00
1 parent b967475c66
commit a18488955d
40 files changed
+1101 -240

No files matched your search

+3
View File
@@ -30,6 +30,8 @@ flowchart LR
cfg --> plugin_dsh_base_typert_loader
plugin_dsh_base_typert_gateway["typert-gateway<br/>@deepseek-ai/dsh-api-gateway"]
cfg --> plugin_dsh_base_typert_gateway
plugin_dsh_base_remote_access["remote-access<br/>@deepseek-ai/dsh-api-remote-access"]
cfg --> plugin_dsh_base_remote_access
plugin_dsh_base_session_title["session-title<br/>@deepseek-ai/dsh-session-title"]
cfg --> plugin_dsh_base_session_title
plugin_dsh_base_session_title_llm["session-title-llm<br/>@deepseek-ai/dsh-session-title-first-prompt-llm"]
@@ -211,6 +213,7 @@ flowchart LR
| `typert` | `@deepseek-ai/dsh-typert-registry` |
| `typert-loader` | `@deepseek-ai/dsh-typert-loader` |
| `typert-gateway` | `@deepseek-ai/dsh-api-gateway` |
| `remote-access` | `@deepseek-ai/dsh-api-remote-access` |
| `session-title` | `@deepseek-ai/dsh-session-title` |
| `session-title-llm` | `@deepseek-ai/dsh-session-title-first-prompt-llm` |
| `user-questions` | `@deepseek-ai/dsh-user-questions` |
+6 -6
View File
@@ -6,17 +6,17 @@
en: 735eb4f22d5f6f71
zh: 697eef269b133039
/api-gateway/programming-model:
en: ea12b64211f0d377
zh: 032e5c0d799c7529
en: 78eb70d6c2ce6608
zh: 826f0ccc206b7299
/api-gateway/component-responsibilities:
en: e29f1d7026291075
zh: 15e361e797d44fce
en: fe382a9bcc768cc2
zh: 78e3d50d59780c3d
/api-gateway/strict-generation-pipeline:
en: 9c4a98eb0539ec7f
zh: ad2a145abd805fdd
/api-gateway/runtime-invocation:
en: 2a2fed340aaa926c
zh: 46bb1f500c870a76
en: 37b4858fa48a61b6
zh: 9a296a2171f1ab7d
/api-gateway/src-development-fallback:
en: 9570ffe7a348e5ac
zh: 2b70b61b1ff1fbb0
+4 -1
View File
@@ -12,6 +12,8 @@ Business services use `@Remote` or `@RemoteScope` to select the methods exposed
`@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.
`@Remote` accepts an export name or `{ name?, mode?, resources? }`, and `@RemoteScope(key, option?)` accepts an export name, as in `@RemoteScope('agent', 'current')` below, or `{ name?, resources? }` as its second argument. `resources` holds per-kind readers that return the identities of the resources one call touches for access control; each reader receives the method's wire arguments as its own parameters, with types inferred from the decorated method. Most methods declare no readers, because the Gateway also reads identities from lookup parameters and scoped Contexts that declare a `resource`, and from the default fields in `typert.resources`; the [Typert subsystem](subsystems/typert.md) owns that order.
Services normally extend `TypertRemoteService` 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 typertRemote = bindTypertRemote(this, serviceKey)`; both forms leave an inspectable public binding and do not depend on the compiler injecting a symbol into the constructor.
```ts
@@ -88,6 +90,7 @@ The `api-remotes` assembly and the `ctx.remote` contract are React-independent;
| 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-session-controller` | 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, validates request values, resolves objects or Contexts, and invokes live Cordis services |
| Host | `@deepseek-ai/dsh-api-remote-access` | Decides which Remote methods a non-owner Peer may call through business-registered role-based policies, filters list results, and streams each Peer's access snapshot; the Gateway requires it |
| Client | `@deepseek-ai/dsh-api-gateway/client` | Provides `ctx.remote` and `remote.<namespace>` child Services, mounts generated descriptors as concrete methods, and initiates 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 the `/api` HTTP bridge |
@@ -124,7 +127,7 @@ Remote calls use the Connection's `/api` route. The Client Remote calls `connect
The Connection performs the unified trust check for `/api` before the HTTP bridge, then dispatches inside the shared FetchHandler. The Typert Gateway claims only two-segment endpoints that have a strict descriptor or active SRC marker; it projects binary Remote fields into JSON-compatible metadata and result-relative byte attachments, which Connection frames as multipart responses. Feature-owned exact Fetch routes handle responses outside the RPC envelope, and other requests return 404. Connection owns transport, RPC ids, response envelopes, and request cancellation, while Gateway owns the Remote data protocol and business dispatch. Replacing the Connection carrier 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, and invokes the service method targeted by the binding. The Client Gateway validates decoded binary results with the generated result codec; ordinary JSON results retain their existing handling. A missing provider, unknown identity, binding mismatch, missing or extra argument, schema failure, or missing method fails before entering or after leaving business code.
For every call, the Gateway resolves the descriptor and live service from the current registries instead of caching business objects. Every call names the Peer it speaks for, and a disposed Peer is refused with `gateway/peer-disposed`. The Gateway requires the fields in `args` to match the descriptor exactly; for a call whose Peer principal is not the owner it then resolves the resources the call touches and asks `ctx.remoteAccess.decide()`, so a refused call resolves no lookup and activates no Agent. It validates wire values with codecs, resolves objects or receivers through registered lookup or Context providers, and invokes the service method targeted by the binding. The Client Gateway validates decoded binary results with the generated result codec; ordinary JSON results retain their existing handling. A missing provider, unknown identity, binding mismatch, missing or extra argument, schema failure, or missing method fails 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 `gateway/lookup-unavailable`, and unloading the configuration restores the provider's default policy. The Session Controller owns the standard resolver 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. A resume failure and an ownership fence raise a `RemoteError` carrying their own code, `session/not-found` or `session/agent-busy`, which the Gateway encodes onto the wire unchanged; only an unclassified throw folds into `gateway/internal`.
+4 -1
View File
@@ -14,6 +14,8 @@
`@RemoteScope(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context,再从该 Context 取得服务并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。
`@Remote` 接受导出名或 `{ name?, mode?, resources? }`;`@RemoteScope(key, option?)` 的第二个参数接受导出名(如下文的 `@RemoteScope('agent', 'current')`)或 `{ name?, resources? }`。`resources` 按资源种类给出读取函数,返回一次调用所访问资源的身份,供访问控制使用;每个读取函数以方法的 wire 参数作为自身参数,类型从被装饰方法推断。多数方法不声明读取函数,因为 Gateway 还会从声明了 `resource` 的 lookup 参数与作用域 Context 以及 `typert.resources` 中的默认字段读取身份;[Typert 子系统](subsystems/typert.zh.md)负责该顺序。
服务通常继承 `TypertRemoteService`,让 Cordis 服务 key 与默认 Remote namespace 在构造器中显式绑定。已有其他基类的服务可以改为声明 `readonly typertRemote = bindTypertRemote(this, serviceKey)`;两种方式都会留下可检查的公开 binding,不依赖编译器向构造函数注入 symbol。
```ts
@@ -90,6 +92,7 @@ Client 应用只装配 `@deepseek-ai/dsh-api-remotes`。该包以运行时值导
| Host | `@deepseek-ai/dsh-typert-registry` 与 Loader | 把生成的 Host 描述符、schema 及业务包注册项放入 `ctx.typert`,并持有 lookup 与 Context 提供方 |
| Host | `@deepseek-ai/dsh-api-session-controller` | 负责应用的 Agent/Session 身份策略,并配置对应的 Typert lookup |
| Host | `@deepseek-ai/dsh-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,校验请求值,解析对象或 Context,并调用实时 Cordis 服务 |
| Host | `@deepseek-ai/dsh-api-remote-access` | 按业务方注册的基于 role 的 policy 判定非 owner Peer 可调用哪些 Remote 方法,过滤列表结果,并为每个 Peer 推送访问快照;Gateway 依赖它 |
| Client | `@deepseek-ai/dsh-api-gateway/client` | 提供 `ctx.remote` 与 `remote.<namespace>` 子服务,把生成的描述符挂成具体方法,并通过 Connection 发起和取消调用 |
| Client | `@deepseek-ai/dsh-api-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 |
| 双侧 | `@deepseek-ai/dsh-client-connection` | 提供 RPC carrier、请求关联、信任边界、取消、响应 envelope 与 `/api` HTTP bridge |
@@ -126,7 +129,7 @@ Remote 调用使用 Connection 的 `/api` 路由。Client Remote 调用 `connect
Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内分发。Typert Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint;它把二进制 Remote 字段投影为 JSON 兼容元数据及相对于结果的字节附件,再由 Connection 封装为 multipart 响应。功能自有的精确 Fetch 路由处理 RPC 信封之外的响应,其他请求返回 404。Connection 拥有传输、RPC id、响应 envelope 和请求取消,Gateway 拥有 Remote 数据协议和业务分发。替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程接口。
Gateway 每次调用都从当前注册表解析描述符和实时服务,不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context 提供方解析对象或接收者,最后调用 binding 指向的服务方法。Client Gateway 使用生成的结果 codec 校验解码后的二进制结果;普通 JSON 结果保持原有处理方式。缺少提供方、identity 未命中、binding 不一致、参数缺失或多余、schema 失败和方法不存在都会在进入业务代码前或离开业务代码后失败。
Gateway 每次调用都从当前注册表解析描述符和实时服务,不缓存业务对象。每次调用都给出其代表的 Peer,已 dispose 的 Peer 以 `gateway/peer-disposed` 被拒。Gateway 要求 `args` 的字段集合与描述符完全一致;对 Peer principal 不是 owner 的调用,随后解析调用访问的资源并询问 `ctx.remoteAccess.decide()`,被拒的调用不解析任何 lookup,也不激活 Agent。之后它先用 codec 校验 wire 值,再通过注册的 lookup 或 Context 提供方解析对象或接收者,最后调用 binding 指向的服务方法。Client Gateway 使用生成的结果 codec 校验解码后的二进制结果;普通 JSON 结果保持原有处理方式。缺少提供方、identity 未命中、binding 不一致、参数缺失或多余、schema 失败和方法不存在都会在进入业务代码前或离开业务代码后失败。
lookup 提供方的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于提供方挂载;没有提供方时调用仍以 `gateway/lookup-unavailable` 失败,配置卸载后则恢复提供方默认策略。Session Controller 负责 `agent` 与 `session` 的标准 resolver 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity;`session` lookup 返回该 Agent 的 Session。恢复失败与 ownership fence 抛出携带自有码的 `RemoteError`(`session/not-found` 或 `session/agent-busy`),Gateway 原样编码上 wire;只有未归类的 throw 才折成 `gateway/internal`。
+2 -2
View File
@@ -6,5 +6,5 @@
en: 41d772192075e133
zh: 5ef3da69b4de1d8a
/capability-seams-and-core-services:
en: 2e9d4cb22f8f2a6b
zh: 81f523914e8531f7
en: d7b1a50795bcac28
zh: 68386bdbf1869342
+7
View File
@@ -101,6 +101,8 @@ flowchart LR
svc_typert["ctx.typert<br/>Runtime type registry"]
pkg_typert_loader["typert-loader"]
svc_typertGateway["ctx.typertGateway<br/>Typert Host invocation gateway"]
pkg_api_remote_access["api-remote-access"]
svc_remoteAccess["ctx.remoteAccess<br/>Remote method access control"]
svc_sessionPersistence["ctx.sessionPersistence<br/>Durable session persistence seam"]
pkg_session_persistence_jsonl["session-persistence-jsonl"]
pkg_hooks_claude_code["hooks-claude-code"]
@@ -294,6 +296,7 @@ flowchart LR
pkg_agent_preset_registry --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_api_job_controller --> svc_jobController
pkg_api_remote_access --> svc_remoteAccess
pkg_api_session_controller --> svc_sessionController
pkg_api_session_controller --> svc_sessionFileReferences
pkg_api_session_controller --> svc_sessionSkillCatalog
@@ -500,6 +503,9 @@ flowchart LR
svc_profileContext --> pkg_plugin_manager
svc_ptcRuntime --> pkg_tools
svc_ptcRuntime --> pkg_workflow_ptc
svc_remoteAccess --> pkg_api_gateway
svc_remoteAccess --> pkg_api_session_controller
svc_remoteAccess --> pkg_api_workspace_controller
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_terminal_bash
svc_sandboxPolicy --> pkg_bash_sandbox
@@ -631,6 +637,7 @@ flowchart LR
| `ctx.directoryPickerController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | Carries the picking seam onto the wire: capability gating, cancellation, and the seam-coded failures a browser directory flow discriminates on. |
| `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.remoteAccess` | `core` | [`api-remote-access`](../packages/api/remote-access) | - | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller) | - | Decides which Remote methods a non-owner Peer may call through business-registered role-based policies, filters list results, and streams each Peer access snapshot. |
| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | The JSONL backend persists the SessionEvent vocabulary as one artifact per Session. |
| `ctx.configEditor` | `core` | [`config-editor`](../packages/boot/config-editor) | - | [`settings`](../packages/settings/settings), [`agent-default-model`](../packages/core/agent-default-model) | - | Persists profile config patches under the application file lock and HMR queue, then reconciles Loader entries. |
| `ctx.settings` | `core` | [`settings`](../packages/settings/settings) | - | [`api-settings-controller`](../packages/api/settings-controller) | - | Forms project volatile Config fields from active profile entries and delegate validated edits to config-editor. Plugins consume their own Config references. |
+7
View File
@@ -103,6 +103,8 @@ flowchart LR
svc_typert["ctx.typert<br/>Runtime type registry"]
pkg_typert_loader["typert-loader"]
svc_typertGateway["ctx.typertGateway<br/>Typert Host invocation gateway"]
pkg_api_remote_access["api-remote-access"]
svc_remoteAccess["ctx.remoteAccess<br/>Remote method access control"]
svc_sessionPersistence["ctx.sessionPersistence<br/>Durable session persistence seam"]
pkg_session_persistence_jsonl["session-persistence-jsonl"]
pkg_hooks_claude_code["hooks-claude-code"]
@@ -296,6 +298,7 @@ flowchart LR
pkg_agent_preset_registry --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_api_job_controller --> svc_jobController
pkg_api_remote_access --> svc_remoteAccess
pkg_api_session_controller --> svc_sessionController
pkg_api_session_controller --> svc_sessionFileReferences
pkg_api_session_controller --> svc_sessionSkillCatalog
@@ -502,6 +505,9 @@ flowchart LR
svc_profileContext --> pkg_plugin_manager
svc_ptcRuntime --> pkg_tools
svc_ptcRuntime --> pkg_workflow_ptc
svc_remoteAccess --> pkg_api_gateway
svc_remoteAccess --> pkg_api_session_controller
svc_remoteAccess --> pkg_api_workspace_controller
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_terminal_bash
svc_sandboxPolicy --> pkg_bash_sandbox
@@ -633,6 +639,7 @@ flowchart LR
| `ctx.directoryPickerController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | 把选目录 seam 送上线:能力门禁、取消传播,以及浏览器目录流程用于分支判断的 seam 错误码。 |
| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | 插件直接或通过 dsh-typert-loader 注册实时 zod 贡献;API 网关消费调用描述符和提供方,其他运行时消费方则在各自边界查询 schema 与反射元数据。 |
| `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | 将生成的 Remote 描述符与实时 Cordis 服务关联,解析已注册的身份,并通过共享的 Connection RPC 载体提供一元调用。 |
| `ctx.remoteAccess` | `core` | [`api-remote-access`](../packages/api/remote-access) | - | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller) | - | 通过业务方注册的基于 role 的 policy 判定非 owner Peer 可调用哪些 Remote 方法,过滤列表结果,并为每个 Peer 推送访问快照。 |
| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | JSONL backend 把 SessionEvent 词汇持久化为每个 Session 一份产物。 |
| `ctx.configEditor` | `core` | [`config-editor`](../packages/boot/config-editor) | - | [`settings`](../packages/settings/settings), [`agent-default-model`](../packages/core/agent-default-model) | - | Persists profile config patches under the application file lock and HMR queue, then reconciles Loader entries. |
| `ctx.settings` | `core` | [`settings`](../packages/settings/settings) | - | [`api-settings-controller`](../packages/api/settings-controller) | - | Forms project volatile Config fields from active profile entries and delegate validated edits to config-editor. Plugins consume their own Config references. |
+9 -8
View File
@@ -181,8 +181,8 @@ export interface Config {
## `@deepseek-ai/dsh-api-gateway`
- `inject`: `typert`
- `source`: [`packages/api/gateway/src/index.ts:147`](../packages/api/gateway/src/index.ts)
- `inject`: `typert` · `remoteAccess`
- `source`: [`packages/api/gateway/src/index.ts:155`](../packages/api/gateway/src/index.ts)
```ts config-catalog
/** Gateway transport configuration. */
@@ -219,8 +219,8 @@ export interface Config {
## `@deepseek-ai/dsh-api-session-controller`
- `inject`: `agentDefaultModel` · `agents` · `attachments` · `fileUploads` · `fs` · `llm` · `sessions` · `sessionProjections` · `sessionQuery` · `typert` · `workspaceRegistry`
- `source`: [`packages/api/session-controller/src/index.ts:80`](../packages/api/session-controller/src/index.ts)
- `inject`: `agentDefaultModel` · `agents` · `attachments` · `fileUploads` · `fs` · `llm` · `remoteAccess` · `sessions` · `sessionProjections` · `sessionQuery` · `typert` · `workspaceRegistry`
- `source`: [`packages/api/session-controller/src/index.ts:94`](../packages/api/session-controller/src/index.ts)
```ts config-catalog
/** Session Controller deployment policy. */
@@ -300,8 +300,8 @@ export interface Config {
## `@deepseek-ai/dsh-api-workspace-controller`
- `inject`: `typert` · `workspaceRegistry`
- `source`: [`packages/api/workspace-controller/src/index.ts:33`](../packages/api/workspace-controller/src/index.ts)
- `inject`: `remoteAccess` · `typert` · `workspaceRegistry`
- `source`: [`packages/api/workspace-controller/src/index.ts:37`](../packages/api/workspace-controller/src/index.ts)
```ts config-catalog
/** First-use directory policy for the Host account. */
@@ -435,7 +435,7 @@ export type Config = LocalConfig
## `@deepseek-ai/dsh-client-connection`
- `inject`: `credentials`
- `source`: [`packages/client/connection/src/index.ts:92`](../packages/client/connection/src/index.ts)
- `source`: [`packages/client/connection/src/index.ts:93`](../packages/client/connection/src/index.ts)
```ts config-catalog
/** Browser authentication, request limits, and connection recovery configuration. */
@@ -3276,7 +3276,7 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
- `inject`: `workingDirectory`
- `refs`: `Volatile` (`@deepseek-ai/cordis`)
- `source`: [`packages/subagent/subagent/src/index.ts:184`](../packages/subagent/subagent/src/index.ts)
- `source`: [`packages/subagent/subagent/src/index.ts:186`](../packages/subagent/subagent/src/index.ts)
```ts config-catalog
/** Host configuration for continuable subagent capacity. */
@@ -4515,6 +4515,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
| `@deepseek-ai/dsh-acp-app` | `cmdlineArgs` | [`packages/bundle/acp-app/src/index.ts`](../packages/bundle/acp-app/src/index.ts) |
| `@deepseek-ai/dsh-agent` | — | [`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts) |
| `@deepseek-ai/dsh-api-account-controller` | `deepseekAccount` · `agents` | [`packages/api/account-controller/src/index.ts`](../packages/api/account-controller/src/index.ts) |
| `@deepseek-ai/dsh-api-remote-access` | — | [`packages/api/remote-access/src/index.ts`](../packages/api/remote-access/src/index.ts) |
| `@deepseek-ai/dsh-api-remotes` | `typertGateway` | [`packages/api/remotes/src/index.ts`](../packages/api/remotes/src/index.ts) |
| `@deepseek-ai/dsh-authorization` | `credentials` | [`packages/credentials/authorization/src/index.ts`](../packages/credentials/authorization/src/index.ts) |
| `@deepseek-ai/dsh-browser-use` | — | [`packages/browser-use/browser-use/src/index.ts`](../packages/browser-use/browser-use/src/index.ts) |
+9 -8
View File
@@ -183,8 +183,8 @@ export interface Config {
## `@deepseek-ai/dsh-api-gateway`
- `inject`: `typert`
- `source`: [`packages/api/gateway/src/index.ts:147`](../packages/api/gateway/src/index.ts)
- `inject`: `typert` · `remoteAccess`
- `source`: [`packages/api/gateway/src/index.ts:155`](../packages/api/gateway/src/index.ts)
```ts config-catalog
/** Gateway transport configuration. */
@@ -221,8 +221,8 @@ export interface Config {
## `@deepseek-ai/dsh-api-session-controller`
- `inject`: `agentDefaultModel` · `agents` · `attachments` · `fileUploads` · `fs` · `llm` · `sessions` · `sessionProjections` · `sessionQuery` · `typert` · `workspaceRegistry`
- `source`: [`packages/api/session-controller/src/index.ts:80`](../packages/api/session-controller/src/index.ts)
- `inject`: `agentDefaultModel` · `agents` · `attachments` · `fileUploads` · `fs` · `llm` · `remoteAccess` · `sessions` · `sessionProjections` · `sessionQuery` · `typert` · `workspaceRegistry`
- `source`: [`packages/api/session-controller/src/index.ts:94`](../packages/api/session-controller/src/index.ts)
```ts config-catalog
/** Session Controller deployment policy. */
@@ -302,8 +302,8 @@ export interface Config {
## `@deepseek-ai/dsh-api-workspace-controller`
- `inject`: `typert` · `workspaceRegistry`
- `source`: [`packages/api/workspace-controller/src/index.ts:33`](../packages/api/workspace-controller/src/index.ts)
- `inject`: `remoteAccess` · `typert` · `workspaceRegistry`
- `source`: [`packages/api/workspace-controller/src/index.ts:37`](../packages/api/workspace-controller/src/index.ts)
```ts config-catalog
/** First-use directory policy for the Host account. */
@@ -437,7 +437,7 @@ export type Config = LocalConfig
## `@deepseek-ai/dsh-client-connection`
- `inject`: `credentials`
- `source`: [`packages/client/connection/src/index.ts:92`](../packages/client/connection/src/index.ts)
- `source`: [`packages/client/connection/src/index.ts:93`](../packages/client/connection/src/index.ts)
```ts config-catalog
/** Browser authentication, request limits, and connection recovery configuration. */
@@ -3278,7 +3278,7 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
- `inject`: `workingDirectory`
- `refs`: `Volatile` (`@deepseek-ai/cordis`)
- `source`: [`packages/subagent/subagent/src/index.ts:184`](../packages/subagent/subagent/src/index.ts)
- `source`: [`packages/subagent/subagent/src/index.ts:186`](../packages/subagent/subagent/src/index.ts)
```ts config-catalog
/** Host configuration for continuable subagent capacity. */
@@ -4517,6 +4517,7 @@ export interface Config {
| `@deepseek-ai/dsh-acp-app` | `cmdlineArgs` | [`packages/bundle/acp-app/src/index.ts`](../packages/bundle/acp-app/src/index.ts) |
| `@deepseek-ai/dsh-agent` | — | [`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts) |
| `@deepseek-ai/dsh-api-account-controller` | `deepseekAccount` · `agents` | [`packages/api/account-controller/src/index.ts`](../packages/api/account-controller/src/index.ts) |
| `@deepseek-ai/dsh-api-remote-access` | — | [`packages/api/remote-access/src/index.ts`](../packages/api/remote-access/src/index.ts) |
| `@deepseek-ai/dsh-api-remotes` | `typertGateway` | [`packages/api/remotes/src/index.ts`](../packages/api/remotes/src/index.ts) |
| `@deepseek-ai/dsh-authorization` | `credentials` | [`packages/credentials/authorization/src/index.ts`](../packages/credentials/authorization/src/index.ts) |
| `@deepseek-ai/dsh-browser-use` | — | [`packages/browser-use/browser-use/src/index.ts`](../packages/browser-use/browser-use/src/index.ts) |
@@ -9,8 +9,8 @@
en: 5759a7b0f96ea4bc
zh: 62465fbe91a4d2af
/remote-and-api-rules/remote-marks-capability-methods:
en: e5613ec07023429d
zh: 9272c9ae5718060a
en: 86422ee69c63f339
zh: 4a5e8d6f5d9a6ae3
/remote-and-api-rules/packages-api-carries-generic-orchestration-only:
en: 12a964b9c04fccf9
zh: cf804f8044535727
+3 -2
View File
@@ -44,13 +44,14 @@ One object then answers for both domains, so a change to either capability's act
## `@Remote` marks capability methods
`@Remote` carries four disciplines that a reviewer checks separately: it marks the concrete method that performs a capability; client-facing types come from the existing definitions through the generator instead of a hand-written copy; the payload is JSON and the remote method performs no output-side decode; and existing method and field names are additive. Each discipline removes a different failure — a wrapper hides which operation is exposed, a second hand-written type set drifts from the generated one, an output-side decode adds a second result path beside the codec the Gateway already runs, and a rename breaks every caller in the same change.
`@Remote` carries five disciplines that a reviewer checks separately: it marks the concrete method that performs a capability; client-facing types come from the existing definitions through the generator instead of a hand-written copy; the payload is JSON and the remote method performs no output-side decode; existing method and field names are additive; and a method names every resource it touches and reads its caller only from the invocation. Each discipline removes a different failure — a wrapper hides which operation is exposed, a second hand-written type set drifts from the generated one, an output-side decode adds a second result path beside the codec the Gateway already runs, a rename breaks every caller in the same change, and an unnamed resource, an unfiltered aggregate, or an argument-supplied caller lets a non-owner reach what no binding grants it.
- **Capability methods.** The annotation goes on the concrete method that performs the operation (`list`, `prompt`, `interruptByParent`), never on a wrapper that forwards to the real implementation. Judged by the decorated method's body: a body that only delegates means the annotation is on the wrong method.
- **Types from inference.** Client-facing types are aliased from what the generator produces — `type SessionRemote = TypertClientRemote['session']` — instead of being written a second time. Judged by a hand-written declaration whose fields repeat a generated Client type; [typert.md](../subsystems/typert.md) owns the generated maps and their declaration merges.
- **JSON on the wire, no output-side decode.** The **wire payload** of a Remote method is JSON, with binary content expressed field-level. The Host signature keeps what the Gateway already supports — lookup arguments resolved to Host objects, a trailing `AbortSignal`, a stream declared with `@Remote({ mode: 'stream' })` — and `@Remote` performs no decode on the way out; the generated codec and the Gateway validate the result. Judged by a decode, a stream reassembly, or a whole non-JSON value in the payload of a method carrying the decorator; [programming model](../api-gateway.md#programming-model) owns what may cross a Remote method.
- **Existing names are additive.** A migration does not rename a remote method or field that a Client already calls; a new semantic gets a new entry, and a rename is its own change with its own review. Judged by a diff that renames or removes a name already exposed through `ctx.remote.<namespace>`.
- **Not covered.** A method without the decorator stays an ordinary service method with in-process callers; the four disciplines govern what becomes a Remote method, not what a service may implement.
- **Caller identity and access.** Every Remote method is under access control: the `@Remote({ resources })` / `@RemoteScope(key, { resources })` readers and automatic resolution must name every resource the method touches, because access control never checks a resource they omit, and a method returning several resources filters each item through `this.ctx.remoteAccess.allows(resource)`. The caller is read only from `this.ctx.invocation.peer.principal`, never from arguments; a Remote method that builds a user-message source records it through `principalOf(this.ctx.invocation)` and passes the built source down instead of passing the principal as a function argument. Every call path names its Peer explicitly. Judged by a method whose resolved resources omit one it touches, an unfiltered aggregate, a principal read from wire data or threaded through helper parameters, or a user-message source built in a Remote method without `principalOf`; the [Peer principals Agent Note](../../.agents/notes/implemented/architecture/2026-10-12-peer-principal-and-remote-access.md) owns the design.
- **Not covered.** A method without the decorator stays an ordinary service method with in-process callers; the five disciplines govern what becomes a Remote method, not what a service may implement.
- **Exception.** A rename or a new decode path states its benefit and the impact on existing callers first and needs the domain owner's confirmation; the author never self-approves.
The generated map is the only source for Client-facing types, the decorator sits on the concrete method, and the payload stays JSON:
+3 -2
View File
@@ -44,13 +44,14 @@ class ApiProxy {
## `@Remote` 标在能力方法上
`@Remote` 有四项纪律,审查者分别核对:标注执行能力的具体方法;面向 Client 的类型经生成器来自既有定义,而不是手写第二份;载荷只承载 JSON,远程方法不做输出侧 decode;既有的方法与字段名只增不改。每项纪律消除一类不同的缺陷——壳方法藏起真正暴露的是哪个操作,手写的第二份类型会与生成结果漂移,输出侧 decode 在 Gateway 已有的 codec 之外再造一条结果路径,改名则让所有调用方在同一次变更里一起断掉。
`@Remote` 有五项纪律,审查者分别核对:标注执行能力的具体方法;面向 Client 的类型经生成器来自既有定义,而不是手写第二份;载荷只承载 JSON,远程方法不做输出侧 decode;既有的方法与字段名只增不改;方法指明其访问的全部资源,且只从 invocation 读取调用方。每项纪律消除一类不同的缺陷——壳方法藏起真正暴露的是哪个操作,手写的第二份类型会与生成结果漂移,输出侧 decode 在 Gateway 已有的 codec 之外再造一条结果路径,改名让所有调用方在同一次变更里一起断掉,遗漏资源、未过滤的聚合结果或按参数判定调用方则让非 owner 访问没有 binding 授予它的内容。
- **标能力方法。** 标注落在执行该操作的具体方法上(`list`、`prompt`、`interruptByParent`),绝不落在转发给真正实现的壳方法上。判定看被标注方法的函数体:函数体只做转发,标注就标错了方法。
- **类型靠推断。** 面向 Client 的类型从生成结果取别名——`type SessionRemote = TypertClientRemote['session']`——而不是重写一份。判定看是否出现字段与生成的 Client 类型重复的手写声明;生成映射及其声明合并的正家是 [typert.zh.md](../subsystems/typert.zh.md)。
- **线上载荷只承载 JSON、不做输出侧 decode。** Remote 方法的**线上载荷**是 JSON,二进制在字段级表达。Host 签名保留 Gateway 已支持的形式——经解析变成 Host 对象的 lookup 参数、末尾的 `AbortSignal`、以 `@Remote({ mode: 'stream' })` 声明的流式返回;`@Remote` 不做输出侧 decode,结果由生成的 codec 与 Gateway 校验。判定看带该标注的方法上是否出现 decode、流重组或整体非 JSON 值;可跨越 Remote 方法的内容正家是 [编程模型](../api-gateway.zh.md#programming-model)。
- **存量名只增不改。** 迁移不改 Client 已在调用的远程方法名或字段名;新语义新增条目,改名是独立的一刀、单独评审。判定看 diff 是否改名或删除了已经经 `ctx.remote.<namespace>` 暴露的名字。
- **不覆盖。** 不带该标注的方法仍是有进程内调用者的普通 service 方法;这四项纪律约束的是什么成为 Remote 方法,而不是 service 可以实现什么。
- **调用方身份与访问。** 每个 Remote 方法都受访问控制:`@Remote({ resources })` / `@RemoteScope(key, { resources })` 的读取函数与自动解析必须指明方法访问的全部资源,因为访问控制从不检查它们遗漏的资源;返回多个资源的方法对每一项经 `this.ctx.remoteAccess.allows(resource)` 过滤。调用方只从 `this.ctx.invocation.peer.principal` 读取,绝不从参数读取;在 Remote 方法内构造用户消息 source 时经 `principalOf(this.ctx.invocation)` 记录调用方,并向下传递已构造的 source,而不是把 principal 当函数参数传递。每条调用路径都显式给出 Peer。判定看方法解析出的资源是否遗漏其访问的资源、聚合结果是否未过滤、principal 是否从 wire 数据读取或经辅助函数参数逐层传递、Remote 方法内构造的用户消息 source 是否未经 `principalOf`;设计的正家是 [Peer 身份属性 Agent Note](../../.agents/notes/implemented/architecture/2026-10-12-peer-principal-and-remote-access.zh.md)。
- **不覆盖。** 不带该标注的方法仍是有进程内调用者的普通 service 方法;这五项纪律约束的是什么成为 Remote 方法,而不是 service 可以实现什么。
- **例外。** 确需改名或新增 decode 路径,须先说明收益与对既有调用方的影响,并取得对应领域专家的确认;作者不得自行批准。
生成的映射是面向 Client 类型的唯一来源,标注落在具体方法上,载荷保持 JSON:
+7 -7
View File
@@ -22,17 +22,17 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:624`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:631`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:610`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:617`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:629`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:609`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:636`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:615`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:622`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:54`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
| `compaction/summary-error` | `waterfall` | [`packages/compaction/compaction/src/index.ts:106`](../packages/compaction/compaction/src/index.ts) | [`compaction-basic`](../packages/compaction/compaction-basic) (`waterfall`) | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) |
| `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:67`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - |
| `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:68`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - |
| `cordis/dynamic-package` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:383`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
| `cordis/dynamic-retract` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:389`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
| `cordis/inspect-query` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:395`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
@@ -80,7 +80,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) |
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) |
| `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` |
| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:39`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` |
| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:39`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` |
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
+7 -7
View File
@@ -24,17 +24,17 @@
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:624`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:631`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:610`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:617`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:629`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:609`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:636`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:615`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:622`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:54`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
| `compaction/summary-error` | `waterfall` | [`packages/compaction/compaction/src/index.ts:106`](../packages/compaction/compaction/src/index.ts) | [`compaction-basic`](../packages/compaction/compaction-basic) (`waterfall`) | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) |
| `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:67`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - |
| `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:68`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - |
| `cordis/dynamic-package` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:383`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
| `cordis/dynamic-retract` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:389`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
| `cordis/inspect-query` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:395`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
@@ -82,7 +82,7 @@
| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) |
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) |
| `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` |
| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:39`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` |
| `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:39`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` |
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) |
| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
+3 -2
View File
@@ -111,6 +111,7 @@ flowchart TD
pkg_api_account_controller["api-account-controller"]
pkg_api_gateway["api-gateway"]
pkg_api_job_controller["api-job-controller"]
pkg_api_remote_access["api-remote-access"]
pkg_api_remotes["api-remotes"]
pkg_api_session_controller["api-session-controller"]
pkg_api_settings_controller["api-settings-controller"]
@@ -484,7 +485,6 @@ flowchart TD
pkg_spill --> pkg_brand
pkg_spill --> pkg_llm
pkg_spill --> pkg_session
pkg_api_gateway --> pkg_client_connection
pkg_api_terminal_controller --> pkg_subprocess
pkg_attachment_local --> pkg_attachment
pkg_attachment_local --> pkg_home_paths
@@ -1461,7 +1461,9 @@ flowchart TD
| [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — |
| [`llm`](../packages/llm/llm) | `llm` | — |
| [`scope`](../packages/core/scope) | `core` | — |
| [`api-gateway`](../packages/api/gateway) | `api` | — |
| [`api-job-controller`](../packages/api/job-controller) | `api` | — |
| [`api-remote-access`](../packages/api/remote-access) | `api` | — |
| [`api-workspace-files`](../packages/api/workspace-files) | `api` | — |
| [`cmdline`](../packages/boot/cmdline) | `boot` | — |
| [`acp-app`](../packages/bundle/acp-app) | `bundle` | — |
@@ -1588,7 +1590,6 @@ flowchart TD
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection) |
| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) |
| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) |
| [`hmr`](../packages/boot/hmr) | `boot` | [`app-boot`](../packages/boot/app-boot), [`cmdline`](../packages/boot/cmdline) |
+3 -2
View File
@@ -113,6 +113,7 @@ flowchart TD
pkg_api_account_controller["api-account-controller"]
pkg_api_gateway["api-gateway"]
pkg_api_job_controller["api-job-controller"]
pkg_api_remote_access["api-remote-access"]
pkg_api_remotes["api-remotes"]
pkg_api_session_controller["api-session-controller"]
pkg_api_settings_controller["api-settings-controller"]
@@ -486,7 +487,6 @@ flowchart TD
pkg_spill --> pkg_brand
pkg_spill --> pkg_llm
pkg_spill --> pkg_session
pkg_api_gateway --> pkg_client_connection
pkg_api_terminal_controller --> pkg_subprocess
pkg_attachment_local --> pkg_attachment
pkg_attachment_local --> pkg_home_paths
@@ -1463,7 +1463,9 @@ flowchart TD
| [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — |
| [`llm`](../packages/llm/llm) | `llm` | — |
| [`scope`](../packages/core/scope) | `core` | — |
| [`api-gateway`](../packages/api/gateway) | `api` | — |
| [`api-job-controller`](../packages/api/job-controller) | `api` | — |
| [`api-remote-access`](../packages/api/remote-access) | `api` | — |
| [`api-workspace-files`](../packages/api/workspace-files) | `api` | — |
| [`cmdline`](../packages/boot/cmdline) | `boot` | — |
| [`acp-app`](../packages/bundle/acp-app) | `bundle` | — |
@@ -1590,7 +1592,6 @@ flowchart TD
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection) |
| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) |
| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) |
| [`hmr`](../packages/boot/hmr) | `boot` | [`app-boot`](../packages/boot/app-boot), [`cmdline`](../packages/boot/cmdline) |
+4 -4
View File
@@ -933,7 +933,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @param signal - caller cancellation before prompt admission begins.
* @returns acknowledgement that the Agent accepted the prompt.
*/
@Remote('prompt') prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
@Remote('prompt') async prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
/**
* Read one image proven reachable from the addressed Session log.
@@ -962,7 +962,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @param signal - cancellation for persistence reads.
* @returns one chronological page.
*/
@Remote('page') page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
@Remote({ name: 'page', resources: { session: addressedSession } }) page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
/**
* Follow one Session log from its opening or resume cursor.
@@ -971,7 +971,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @returns a complete opening snapshot followed by gap-free durable event
* frames and optional cursorless assistant-stream frames.
*/
@Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
@Remote({ mode: 'stream', resources: { session: addressedSession } }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
/**
* Read exact projections, returning cached hints only when migration is required.
@@ -986,7 +986,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @param signal - cancellation owned by the Remote stream carrier.
* @returns one complete baseline followed by live replacement frames.
*/
@Remote({ mode: 'stream' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
@Remote({ mode: 'stream' }) async *control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
```
Types: [SessionId](core.md) · [SessionInspection](persistence.md) · [SessionSearchRequest](session-query.md)
+4 -4
View File
@@ -937,7 +937,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @param signal - caller cancellation before prompt admission begins.
* @returns acknowledgement that the Agent accepted the prompt.
*/
@Remote('prompt') prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
@Remote('prompt') async prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
/**
* Read one image proven reachable from the addressed Session log.
@@ -966,7 +966,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @param signal - cancellation for persistence reads.
* @returns one chronological page.
*/
@Remote('page') page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
@Remote({ name: 'page', resources: { session: addressedSession } }) page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
/**
* Follow one Session log from its opening or resume cursor.
@@ -975,7 +975,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @returns a complete opening snapshot followed by gap-free durable event
* frames and optional cursorless assistant-stream frames.
*/
@Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
@Remote({ mode: 'stream', resources: { session: addressedSession } }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
/**
* Read exact projections, returning cached hints only when migration is required.
@@ -990,7 +990,7 @@ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' |
* @param signal - cancellation owned by the Remote stream carrier.
* @returns one complete baseline followed by live replacement frames.
*/
@Remote({ mode: 'stream' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
@Remote({ mode: 'stream' }) async *control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
```
Types: [SessionId](core.zh.md) · [SessionInspection](persistence.zh.md) · [SessionSearchRequest](session-query.zh.md)
+2 -2
View File
@@ -15,8 +15,8 @@
en: 9cb70abb2e39b958
zh: cd4b6f1705d90e17
/web-client-slots/framework-provided-hooks:
en: 2fcfc55fbac80f72
zh: ea3016f394c56b07
en: c9f8d836214623ae
zh: 76dec55006396535
/web-client-slots/developer-provided-injection:
en: 3481dfa6cd3097da
zh: acbd6f06a7da1752
+5 -4
View File
@@ -84,11 +84,12 @@ The shipped adapters add these standard props. They are available according to t
| Availability | Props | Owner |
|---|---|---|
| every scope | `useSessions`, `useSessionStatus`, `useSessionRetainInfo` | `ui-session` |
| every scope | `useWorkspaces` | `ui-workspace` |
| every scope | `useSessions`, `useSessionStatus`, `useSessionRetainInfo`, `useSessionAccess` | `ui-session` |
| every scope | `useWorkspaces`, `useWorkspaceAccess` | `ui-workspace` |
| every scope | `useRemoteAccess` (the `global` resource) | `api-remote-access` |
| every scope | `usePanelInfo` | `ui-layout` |
| `session` | `sessionId`, `useSession`, `useProjection` | `ui-session` |
| `session-maybe` | optional `sessionId`, `useSession`, `useProjection` results | `ui-session` |
| `session` | `sessionId`, `useSession`, `useProjection`, `useAccess` (the bound Session's answer) | `ui-session` |
| `session-maybe` | optional `sessionId`, `useSession`, `useProjection`, `useAccess` results | `ui-session` |
| `session` | `useConversation`, `useInput`, `inputActions` | `ui-conversation` |
| `session-maybe` | optional `useConversation`, `useInput`, `inputActions` results | `ui-conversation` |
| `session` | `useChat` | `ui-chat` |
+5 -4
View File
@@ -86,11 +86,12 @@ Slot 声明固定两个相互独立的维度。
| 可用范围 | Props | Owner |
|---|---|---|
| 所有 scope | `useSessions`、`useSessionStatus`、`useSessionRetainInfo` | `ui-session` |
| 所有 scope | `useWorkspaces` | `ui-workspace` |
| 所有 scope | `useSessions`、`useSessionStatus`、`useSessionRetainInfo`、`useSessionAccess` | `ui-session` |
| 所有 scope | `useWorkspaces`、`useWorkspaceAccess` | `ui-workspace` |
| 所有 scope | `useRemoteAccess`(`global` 资源) | `api-remote-access` |
| 所有作用域 | `usePanelInfo` | `ui-layout` |
| `session` | `sessionId`、`useSession`、`useProjection` | `ui-session` |
| `session-maybe` | 结果可选的 `sessionId`、`useSession`、`useProjection` | `ui-session` |
| `session` | `sessionId`、`useSession`、`useProjection`、`useAccess`(所绑定 Session 的答案) | `ui-session` |
| `session-maybe` | 结果可选的 `sessionId`、`useSession`、`useProjection`、`useAccess` | `ui-session` |
| `session` | `useConversation`、`useInput`、`inputActions` | `ui-conversation` |
| `session-maybe` | 结果可选的 `useConversation`、`useInput`、`inputActions` | `ui-conversation` |
| `session` | `useChat` | `ui-chat` |
+7 -4
View File
@@ -11,12 +11,15 @@
/typert-remote-calls/invocation-descriptors:
en: 21e88d68154c2c92
zh: 8c9e05c65ef0f34d
/typert-remote-calls/remote-access-resources:
en: fa95f70437c07395
zh: d4870b678b569a89
/typert-remote-calls/typert-registry:
en: 29f578ba733cfd5a
zh: c486ff4ac2d1e3b4
en: 5827ef50011811c9
zh: 21a9e7b368ba211a
/typert-remote-calls/host-gateway:
en: 90fab88fe51d17bb
zh: 2d4c3849cf2dc7d3
en: 58b923de12e48f18
zh: d3c2372d8becf65e
/typert-remote-calls/consumer-remote:
en: 324386c600a5fdf5
zh: 7963501eb663e228
+189 -11
View File
@@ -200,13 +200,35 @@ interface RemoteStreamHandle<Out, In> extends AsyncIterable<Out> {
```ts type-equiv
/**
* One Peer's session on this Host. Connection owns it: `ctx` is the Cordis
* Merge-extensible caller identities. Each driver declares its own entry.
* Every entry is a JSON value with a unique literal `kind`, carries no
* credential, and is safe to persist in a Session log.
*/
interface PrincipalMap {
/** The local user who owns this Host. */
owner: { readonly kind: 'owner' }
}
```
```ts type-equiv
/** One caller identity; the authorization layer reads only this. */
type Principal = PrincipalMap[keyof PrincipalMap]
```
```ts type-equiv
/**
* One Peer's session on this Host. Its creator owns it: `ctx` is the Cordis
* scope that owns connection-lifetime registrations and is disposed with the
* Peer. Who the Peer is and what it may do are not recorded here.
* Peer, and `principal` is the caller identity fixed when the Peer was created.
*/
interface PeerScope {
readonly id: PeerId
/** Connection-lifetime registrations; disposed with the Peer. */
readonly ctx: Context
/** Caller identity fixed at creation by in-process Host code; no wire field can set it. */
readonly principal: Principal
/** Aborted once the Peer is disposed; Remote streams that speak for the Peer end with it. */
readonly signal: AbortSignal
/**
* Tear down every registration made through `ctx`.
* @returns settles once the scope has quiesced; racing calls share one completion.
@@ -239,7 +261,7 @@ interface RemoteInvocation {
}
/** Cordis service key of the receiving Service. */
readonly service: string
/** Peer the call speaks for; an in-process carrier speaks for the operator. */
/** Peer the call speaks for; every carrier names it explicitly. */
readonly peer: PeerScope
/** Carrier cancellation: Client cancel, socket close, or an uplink failure. */
readonly signal: AbortSignal
@@ -260,9 +282,81 @@ interface RemoteInvocation {
}
```
## Remote access resources
Every Remote method is under the access control of [`dsh-api-remote-access`](../../packages/api/remote-access/README.md): a non-owner call is allowed only when the policy for its principal kind binds, on every resource the call touches, a role that lists the endpoint action. The package that owns an identity type merge-declares its resource kind in `TypertResourceMap`; `global`, the built-in identity-less resource, is never a kind there. The Gateway reads a kind's identities from the `resources` readers of `@Remote({ resources })` or `@RemoteScope(key, { resources })`, else from a lookup parameter or scoped Context that declares the kind as its `resource`, else from the default field registered in `typert.resources`; a call that names no resource touches `global`. The [Peer principals Agent Note](../../.agents/notes/implemented/architecture/2026-10-12-peer-principal-and-remote-access.md) owns the decision.
```ts type-equiv
/**
* Merge-extensible resource kinds whose identities a Remote call or forwarded
* event may carry. The package that owns an identity type declares its kind;
* the value is that kind's identity type, a string type such as a branded id.
* `global` names the built-in identity-less resource and is never a kind here.
*/
interface TypertResourceMap {}
```
```ts type-equiv
/** One resource kind: the built-in `global` kind or a declared kind. */
type RemoteAccessResourceKind = 'global' | TypertResourceKind
```
```ts type-equiv
/**
* One resource a call or forwarded event touches: `global`, or one identity
* of a declared kind. Identities are wire values, read before any lookup
* resolves or validates them.
*/
type RemoteAccessResource =
| { readonly kind: 'global' }
| {
[Kind in TypertResourceKind]: { readonly kind: Kind; readonly id: TypertResourceId<TypertResourceMap[Kind]> }
}[TypertResourceKind]
```
```ts type-equiv
/**
* Explicit endpoint and resource choices for one Remote Scope method.
* @template WireArgs - the decorated method's positional parameters as the
* wire carries them; the default `never` admits readers of any tuple.
*/
interface RemoteScopeOptions<WireArgs extends readonly unknown[] = never> {
/** Exported endpoint method; omission retains the implementation member name. */
readonly name?: string
/**
* Per-kind readers of the resources one call touches. Each receives the
* method's arguments as its own parameters in undecoded wire JSON form, a
* lookup parameter as its wire identity and a cancellation parameter as the
* call's signal, so it performs plain reads only, and returns one identity,
* several, or `undefined` for none. A throw, an empty list, or an identity
* other than a non-empty string refuses the call. For a kind without a
* reader, the Gateway reads the identity of a scoped Context whose Host
* adapter declares that `resource` and of lookup parameters declaring it,
* else the field registered in `typert.resources`: a top-level wire
* argument or, failing that, the first object argument. A call that carries
* no identity touches `global`. The readers and these defaults together
* must name every resource the method accesses: access control never checks
* a resource they omit.
*/
readonly resources?: TypertResourceReaders<WireArgs>
}
```
```ts type-equiv
/**
* Explicit endpoint, stream, and resource choices for one direct Remote method.
* @template WireArgs - the decorated method's positional parameters as the
* wire carries them; the default `never` admits readers of any tuple.
*/
interface RemoteMethodOptions<WireArgs extends readonly unknown[] = never> extends RemoteScopeOptions<WireArgs> {
/** `stream`: deliver each Iterable item over the shared logical-stream carrier. */
readonly mode?: 'stream'
}
```
## 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.
`ctx.typert` separates current-environment descriptors, explicitly selected Remote contributions, lookup providers, scoped Context providers, and the default fields of resource kinds. 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. */
@@ -271,6 +365,7 @@ interface TypertRegistryContract {
readonly remotes: TypertRemoteRegistry
readonly lookups: TypertLookupRegistry
readonly contexts: TypertContextRegistry
readonly resources: TypertResourceRegistry
}
```
@@ -283,7 +378,7 @@ 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 ride `TypertGatewayError`, whose `gateway/*` codes are ordinary `RemoteError` codes, so the RPC adapter passes every structurally identified `RemoteError` through with its code and details intact and folds only unrecognized exceptions into `gateway/internal`.
Connection decodes its carrier envelope before calling `ctx.typertGateway`. The request carries exact named wire fields, the carrier's cancellation signal, and the Peer the call speaks for separately; every carrier and in-process caller names that Peer, and the Gateway refuses a disposed one with `gateway/peer-disposed`. For a non-owner call the Gateway resolves its resources and asks `ctx.remoteAccess.decide()` after argument validation and before any lookup or Context resolution; infrastructure and boundary failures ride `TypertGatewayError`, whose `gateway/*` codes are ordinary `RemoteError` codes, so the RPC adapter passes every structurally identified `RemoteError` through with its code and details intact and folds only unrecognized exceptions into `gateway/internal`.
```ts type-equiv
/** One Remote method request after a carrier has decoded its envelope. */
@@ -301,8 +396,8 @@ interface InvokeRemoteRequest {
readonly uplink?: AsyncIterable<unknown>
/** Carrier-provided frame capacity; absent for carriers without an encoded-frame inbox limit. */
readonly uplinkWindow?: RemoteUplinkWindow | undefined
/** Peer the call speaks for; absent means an in-process carrier, answered as the operator. */
readonly peer?: PeerScope
/** Peer the call speaks for; every carrier and in-process caller names it explicitly. */
readonly peer: PeerScope
/** Carrier or direct-caller cancellation injected only into cancellation-aware methods. */
readonly signal?: AbortSignal
}
@@ -324,6 +419,7 @@ type TypertGatewayErrorCode =
| 'gateway/lookup-not-found'
| 'gateway/lookup-unavailable'
| 'gateway/method-unavailable'
| 'gateway/peer-disposed'
| 'gateway/protocol'
| 'gateway/provider-mismatch'
| 'gateway/result-invalid'
@@ -338,8 +434,8 @@ interface TypertGateway {
/** Carrier adapter shared by WebSocket and in-process transports. */
readonly wireStream: TypertGatewayWireStream
/**
* Check for an active Client event stream.
* @returns whether a stream is open and has not been cancelled.
* Check for an active owner Client event stream, the only kind that can answer forwarded waterfalls.
* @returns whether an owner stream is open and has not been cancelled.
*/
hasLiveClient(): boolean
/**
@@ -404,6 +500,88 @@ interface TypertClientRemote extends TypertRemoteNamespaceMap {
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxremoteaccess--remoteaccessservice"></a>
### `ctx.remoteAccess` — `RemoteAccessService`
Registry of access policies, the Gateway's access decisions, list filtering, and the per-Peer access snapshot. Every composition that mounts the Gateway mounts this service too.
```ts cordis-catalog
/**
* Register the policy of one non-owner principal kind in the caller's
* fiber; open snapshots are recomputed on registration and removal.
* @param kind - the principal kind the policy serves; never `owner`.
* @param policy - business-owned bindings and refusal reasons for principals of `kind`.
* @returns disposer removing this policy.
* @throws Error when `kind` already has a policy.
*/
registerPolicy<K extends Exclude<keyof PrincipalMap, 'owner'>>(kind: K, policy: RemoteAccessPolicy<PrincipalMap[K]>): () => void
/**
* Report that the bindings or condition answers for `principal` may have
* changed. Business packages call this on every grant, revocation, mode
* change, and expiry; open snapshots re-push, and the next failure of each
* policy is logged again.
* @param principal - affected principal; omitted for every principal.
*/
invalidate(principal?: Principal): void
/**
* Gateway-only: decide one request whose resources the Gateway resolved.
* The owner is allowed, and so is `remoteAccess/follow`: every Peer reads
* its own snapshot. Otherwise the action is allowed only when, on every
* listed resource, some binding of the principal's policy names that
* resource, has a role listing the action, and has no condition or a
* condition that allows. `bindings()` is called once per decision.
* @param request - the caller or recipient, the action, the resources it
* touches, and a call's arguments.
* @returns `allow`; or `deny` with `no-allow` when the principal kind has
* no policy, the first refusing condition's reason on the first resource
* no binding allows, else `explain()`'s reason, else `no-allow`;
* `policy-failed` when `bindings()`, a condition, or `explain()` throws,
* logged once per policy until the next `invalidate()`. The Gateway
* throws `remote-access/denied` for a denied call and drops a denied event.
*/
decide(request: RemoteAccessRequest<Principal>): RemoteAccessDecision
/**
* Filter items inside a Remote method. The invocation is read from the
* Context that accessed this service, so call it as
* `this.ctx.remoteAccess.allows(resource)` from the Remote method. Each
* check reads the policy's bindings again, so a long-running stream sees
* binding changes from the next check on.
* @param resource - resource of one result item.
* @returns true for the owner and outside a Remote call; otherwise whether
* the invocation's endpoint action is allowed on `resource` without call
* arguments, false when the policy is absent or fails.
*/
allows(resource: RemoteAccessResource): boolean
/**
* The baseline a page opens with before its snapshot stream connects. A
* business package rendering a non-owner page assigns this value to
* {@link REMOTE_ACCESS_SNAPSHOT_KEY} after every index injection row,
* replacing the owner baseline the webServer index carries.
* @param principal - the page's caller identity.
* @returns the owner baseline for the owner; otherwise the principal's current grants.
*/
snapshotFor(principal: Principal): RemoteAccessBaseline
/**
* Stream the calling Peer's access snapshot: one baseline, then `upsert`
* and `remove` frames after each `invalidate()` and each policy
* registration or removal that changes a grant. The owner receives only
* `{ owner: true }`. The generation ends when `signal` aborts or this
* service unloads, so a reloaded service starts every reader from a fresh
* baseline.
* @param signal - generation cancellation.
* @returns the access frames of one generation.
*/
@Remote({ mode: 'stream' }) async *follow(signal: AbortSignal): AsyncIterable<RemoteAccessFrame>
```
Source: [`packages/api/remote-access/src/index.ts`](../../packages/api/remote-access/src/index.ts)
<a id="ctxtypert--typertregistry"></a>
### `ctx.typert` — `TypertRegistry`
@@ -476,8 +654,8 @@ Resolve strict generated definitions or conservative SRC markers against current
```ts cordis-catalog
/**
* Check for an active Client event stream.
* @returns whether a stream is open and has not been cancelled.
* Check for an active owner Client event stream, the only kind that can answer forwarded waterfalls.
* @returns whether an owner stream is open and has not been cancelled.
*/
hasLiveClient(): boolean
+189 -11
View File
@@ -200,13 +200,35 @@ interface RemoteStreamHandle<Out, In> extends AsyncIterable<Out> {
```ts type-equiv
/**
* One Peer's session on this Host. Connection owns it: `ctx` is the Cordis
* Merge-extensible caller identities. Each driver declares its own entry.
* Every entry is a JSON value with a unique literal `kind`, carries no
* credential, and is safe to persist in a Session log.
*/
interface PrincipalMap {
/** The local user who owns this Host. */
owner: { readonly kind: 'owner' }
}
```
```ts type-equiv
/** One caller identity; the authorization layer reads only this. */
type Principal = PrincipalMap[keyof PrincipalMap]
```
```ts type-equiv
/**
* One Peer's session on this Host. Its creator owns it: `ctx` is the Cordis
* scope that owns connection-lifetime registrations and is disposed with the
* Peer. Who the Peer is and what it may do are not recorded here.
* Peer, and `principal` is the caller identity fixed when the Peer was created.
*/
interface PeerScope {
readonly id: PeerId
/** Connection-lifetime registrations; disposed with the Peer. */
readonly ctx: Context
/** Caller identity fixed at creation by in-process Host code; no wire field can set it. */
readonly principal: Principal
/** Aborted once the Peer is disposed; Remote streams that speak for the Peer end with it. */
readonly signal: AbortSignal
/**
* Tear down every registration made through `ctx`.
* @returns settles once the scope has quiesced; racing calls share one completion.
@@ -239,7 +261,7 @@ interface RemoteInvocation {
}
/** Cordis service key of the receiving Service. */
readonly service: string
/** Peer the call speaks for; an in-process carrier speaks for the operator. */
/** Peer the call speaks for; every carrier names it explicitly. */
readonly peer: PeerScope
/** Carrier cancellation: Client cancel, socket close, or an uplink failure. */
readonly signal: AbortSignal
@@ -260,9 +282,81 @@ interface RemoteInvocation {
}
```
## Remote 访问资源
每个 Remote 方法都受 [`dsh-api-remote-access`](../../packages/api/remote-access/README.zh.md) 的访问控制:只有当该 principal 种类的 policy 在调用访问的每个资源上都绑定了列出该端点 action 的 role 时,非 owner 调用才被放行。拥有某个身份类型的包把其资源种类声明合并进 `TypertResourceMap`;内置的无身份资源 `global` 从不是其中的种类。Gateway 从 `@Remote({ resources })` 或 `@RemoteScope(key, { resources })` 的 `resources` 读取函数读取某个种类的身份,其次是把该种类声明为 `resource` 的 lookup 参数或作用域 Context,再次是在 `typert.resources` 中登记的默认字段;不指向任何资源的调用访问 `global`。决策由 [Peer 身份属性 Agent Note](../../.agents/notes/implemented/architecture/2026-10-12-peer-principal-and-remote-access.zh.md) 负责。
```ts type-equiv
/**
* Merge-extensible resource kinds whose identities a Remote call or forwarded
* event may carry. The package that owns an identity type declares its kind;
* the value is that kind's identity type, a string type such as a branded id.
* `global` names the built-in identity-less resource and is never a kind here.
*/
interface TypertResourceMap {}
```
```ts type-equiv
/** One resource kind: the built-in `global` kind or a declared kind. */
type RemoteAccessResourceKind = 'global' | TypertResourceKind
```
```ts type-equiv
/**
* One resource a call or forwarded event touches: `global`, or one identity
* of a declared kind. Identities are wire values, read before any lookup
* resolves or validates them.
*/
type RemoteAccessResource =
| { readonly kind: 'global' }
| {
[Kind in TypertResourceKind]: { readonly kind: Kind; readonly id: TypertResourceId<TypertResourceMap[Kind]> }
}[TypertResourceKind]
```
```ts type-equiv
/**
* Explicit endpoint and resource choices for one Remote Scope method.
* @template WireArgs - the decorated method's positional parameters as the
* wire carries them; the default `never` admits readers of any tuple.
*/
interface RemoteScopeOptions<WireArgs extends readonly unknown[] = never> {
/** Exported endpoint method; omission retains the implementation member name. */
readonly name?: string
/**
* Per-kind readers of the resources one call touches. Each receives the
* method's arguments as its own parameters in undecoded wire JSON form, a
* lookup parameter as its wire identity and a cancellation parameter as the
* call's signal, so it performs plain reads only, and returns one identity,
* several, or `undefined` for none. A throw, an empty list, or an identity
* other than a non-empty string refuses the call. For a kind without a
* reader, the Gateway reads the identity of a scoped Context whose Host
* adapter declares that `resource` and of lookup parameters declaring it,
* else the field registered in `typert.resources`: a top-level wire
* argument or, failing that, the first object argument. A call that carries
* no identity touches `global`. The readers and these defaults together
* must name every resource the method accesses: access control never checks
* a resource they omit.
*/
readonly resources?: TypertResourceReaders<WireArgs>
}
```
```ts type-equiv
/**
* Explicit endpoint, stream, and resource choices for one direct Remote method.
* @template WireArgs - the decorated method's positional parameters as the
* wire carries them; the default `never` admits readers of any tuple.
*/
interface RemoteMethodOptions<WireArgs extends readonly unknown[] = never> extends RemoteScopeOptions<WireArgs> {
/** `stream`: deliver each Iterable item over the shared logical-stream carrier. */
readonly mode?: 'stream'
}
```
## Typert 注册表
`ctx.typert` 分开保存当前环境的 descriptor、显式选择的 Remote contribution、lookup 提供方与作用域上下文提供方。lookup 提供方拥有稳定 wire 声明和默认 resolver;Host 组合可以为同一个 key 配置 effect-scoped 同步或异步 resolver,配置卸载后恢复默认策略。各项注册都是由 Cordis 持有的 effect,并返回可等待的 disposer。
`ctx.typert` 分开保存当前环境的 descriptor、显式选择的 Remote contribution、lookup 提供方、作用域上下文提供方与资源种类的默认字段。lookup 提供方拥有稳定 wire 声明和默认 resolver;Host 组合可以为同一个 key 配置 effect-scoped 同步或异步 resolver,配置卸载后恢复默认策略。各项注册都是由 Cordis 持有的 effect,并返回可等待的 disposer。
```ts type-equiv
/** Minimal Typert runtime consumed through dependency inversion. */
@@ -271,6 +365,7 @@ interface TypertRegistryContract {
readonly remotes: TypertRemoteRegistry
readonly lookups: TypertLookupRegistry
readonly contexts: TypertContextRegistry
readonly resources: TypertResourceRegistry
}
```
@@ -283,7 +378,7 @@ interface TypertRemoteNamespaceMap {}
## Host Gateway
Connection 会先解码 carrier envelope,再调用 `ctx.typertGateway`。请求将精确的具名 wire 字段与 carrier 的取消 signal 分开携带;基础设施与边界失败由 `TypertGatewayError` 承载,其 `gateway/*` 码就是普通的 `RemoteError` 码,因此 RPC 适配器会把每个经结构识别的 `RemoteError` 连同其 code 与 details 原样放行,只把无法识别的异常归并为 `gateway/internal`。
Connection 会先解码 carrier envelope,再调用 `ctx.typertGateway`。请求将精确的具名 wire 字段、carrier 的取消 signal 与本次调用代表的 Peer 分开携带;每个载体与进程内调用方都显式给出该 Peer,Gateway 以 `gateway/peer-disposed` 拒绝已 dispose 的 Peer。对非 owner 调用,Gateway 在参数校验之后、任何 lookup 或 Context 解析之前解析其资源并询问 `ctx.remoteAccess.decide()`;基础设施与边界失败由 `TypertGatewayError` 承载,其 `gateway/*` 码就是普通的 `RemoteError` 码,因此 RPC 适配器会把每个经结构识别的 `RemoteError` 连同其 code 与 details 原样放行,只把无法识别的异常归并为 `gateway/internal`。
```ts type-equiv
/** One Remote method request after a carrier has decoded its envelope. */
@@ -301,8 +396,8 @@ interface InvokeRemoteRequest {
readonly uplink?: AsyncIterable<unknown>
/** Carrier-provided frame capacity; absent for carriers without an encoded-frame inbox limit. */
readonly uplinkWindow?: RemoteUplinkWindow | undefined
/** Peer the call speaks for; absent means an in-process carrier, answered as the operator. */
readonly peer?: PeerScope
/** Peer the call speaks for; every carrier and in-process caller names it explicitly. */
readonly peer: PeerScope
/** Carrier or direct-caller cancellation injected only into cancellation-aware methods. */
readonly signal?: AbortSignal
}
@@ -324,6 +419,7 @@ type TypertGatewayErrorCode =
| 'gateway/lookup-not-found'
| 'gateway/lookup-unavailable'
| 'gateway/method-unavailable'
| 'gateway/peer-disposed'
| 'gateway/protocol'
| 'gateway/provider-mismatch'
| 'gateway/result-invalid'
@@ -338,8 +434,8 @@ interface TypertGateway {
/** Carrier adapter shared by WebSocket and in-process transports. */
readonly wireStream: TypertGatewayWireStream
/**
* Check for an active Client event stream.
* @returns whether a stream is open and has not been cancelled.
* Check for an active owner Client event stream, the only kind that can answer forwarded waterfalls.
* @returns whether an owner stream is open and has not been cancelled.
*/
hasLiveClient(): boolean
/**
@@ -404,6 +500,88 @@ interface TypertClientRemote extends TypertRemoteNamespaceMap {
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxremoteaccess--remoteaccessservice"></a>
### `ctx.remoteAccess` — `RemoteAccessService`
Registry of access policies, the Gateway's access decisions, list filtering, and the per-Peer access snapshot. Every composition that mounts the Gateway mounts this service too.
```ts cordis-catalog
/**
* Register the policy of one non-owner principal kind in the caller's
* fiber; open snapshots are recomputed on registration and removal.
* @param kind - the principal kind the policy serves; never `owner`.
* @param policy - business-owned bindings and refusal reasons for principals of `kind`.
* @returns disposer removing this policy.
* @throws Error when `kind` already has a policy.
*/
registerPolicy<K extends Exclude<keyof PrincipalMap, 'owner'>>(kind: K, policy: RemoteAccessPolicy<PrincipalMap[K]>): () => void
/**
* Report that the bindings or condition answers for `principal` may have
* changed. Business packages call this on every grant, revocation, mode
* change, and expiry; open snapshots re-push, and the next failure of each
* policy is logged again.
* @param principal - affected principal; omitted for every principal.
*/
invalidate(principal?: Principal): void
/**
* Gateway-only: decide one request whose resources the Gateway resolved.
* The owner is allowed, and so is `remoteAccess/follow`: every Peer reads
* its own snapshot. Otherwise the action is allowed only when, on every
* listed resource, some binding of the principal's policy names that
* resource, has a role listing the action, and has no condition or a
* condition that allows. `bindings()` is called once per decision.
* @param request - the caller or recipient, the action, the resources it
* touches, and a call's arguments.
* @returns `allow`; or `deny` with `no-allow` when the principal kind has
* no policy, the first refusing condition's reason on the first resource
* no binding allows, else `explain()`'s reason, else `no-allow`;
* `policy-failed` when `bindings()`, a condition, or `explain()` throws,
* logged once per policy until the next `invalidate()`. The Gateway
* throws `remote-access/denied` for a denied call and drops a denied event.
*/
decide(request: RemoteAccessRequest<Principal>): RemoteAccessDecision
/**
* Filter items inside a Remote method. The invocation is read from the
* Context that accessed this service, so call it as
* `this.ctx.remoteAccess.allows(resource)` from the Remote method. Each
* check reads the policy's bindings again, so a long-running stream sees
* binding changes from the next check on.
* @param resource - resource of one result item.
* @returns true for the owner and outside a Remote call; otherwise whether
* the invocation's endpoint action is allowed on `resource` without call
* arguments, false when the policy is absent or fails.
*/
allows(resource: RemoteAccessResource): boolean
/**
* The baseline a page opens with before its snapshot stream connects. A
* business package rendering a non-owner page assigns this value to
* {@link REMOTE_ACCESS_SNAPSHOT_KEY} after every index injection row,
* replacing the owner baseline the webServer index carries.
* @param principal - the page's caller identity.
* @returns the owner baseline for the owner; otherwise the principal's current grants.
*/
snapshotFor(principal: Principal): RemoteAccessBaseline
/**
* Stream the calling Peer's access snapshot: one baseline, then `upsert`
* and `remove` frames after each `invalidate()` and each policy
* registration or removal that changes a grant. The owner receives only
* `{ owner: true }`. The generation ends when `signal` aborts or this
* service unloads, so a reloaded service starts every reader from a fresh
* baseline.
* @param signal - generation cancellation.
* @returns the access frames of one generation.
*/
@Remote({ mode: 'stream' }) async *follow(signal: AbortSignal): AsyncIterable<RemoteAccessFrame>
```
Source: [`packages/api/remote-access/src/index.ts`](../../packages/api/remote-access/src/index.ts)
<a id="ctxtypert--typertregistry"></a>
### `ctx.typert` — `TypertRegistry`
@@ -476,8 +654,8 @@ Resolve strict generated definitions or conservative SRC markers against current
```ts cordis-catalog
/**
* Check for an active Client event stream.
* @returns whether a stream is open and has not been cancelled.
* Check for an active owner Client event stream, the only kind that can answer forwarded waterfalls.
* @returns whether an owner stream is open and has not been cancelled.
*/
hasLiveClient(): boolean
+2 -2
View File
@@ -24,8 +24,8 @@
en: c063711f8247c240
zh: 5e54758bbaf1670c
/web-client-architecture/conversation-and-presentation:
en: 71362f4ac9490bcb
zh: 025507ca615f5e2a
en: 9a22492f0d4359c6
zh: 55d449e383e88f62
/web-client-architecture/data-paths:
en: a254278432416473
zh: 567c2d3c6a20b304
+1 -1
View File
@@ -57,7 +57,7 @@ This pairing is not a second source of business truth. Host controllers decide d
Web and desktop share the [Coding Tools preference](../../packages/client/ui-settings/README.md#use-this-package). It controls diagnostic Views, [available preset choices](../../packages/client/ui-agent-preset/README.md), changed-file cards, and the builtin HTML preview policy without changing Session records.
`ui-session` installs the Session scope adapter and publishes `useSessions`, `useSessionStatus`, `useSessionRetainInfo`, `useSession`, `sessionId`, and `useProjection`. `SessionProvider` inherits an outer binding or binds an explicit `SessionReference`, so concurrent subtrees can target different Sessions. Domain adapters add further standard sources without putting React hooks on the model objects.
`ui-session` installs the Session scope adapter and publishes `useSessions`, `useSessionStatus`, `useSessionRetainInfo`, `useSessionAccess`, `useSession`, `sessionId`, `useProjection`, and the Session-scope `useAccess` over the [remote-access](../../packages/api/remote-access/README.md) Client service. `SessionProvider` inherits an outer binding or binds an explicit `SessionReference`, so concurrent subtrees can target different Sessions. Domain adapters add further standard sources without putting React hooks on the model objects.
`ui-conversation` binds once to each `SessionBinding.eventSource`. Its event registry correlates durable Session events and Client-only `assistant/live-chunk` updates into stable business Contexts, and its view registry materializes target snapshots. Chat Assistant, Trajectory Assistant, and Turn Tail interpret both live chunks and the compact streams embedded in durable settlements, so reconnect and paged history reproduce the same Assistant state without durable token rows. `ui-chat` and `ui-trajectory` register separate Definitions and builders: they may interpret the same event family, but they do not import or share each other's final display model. The shell selects a registered view and passes its snapshot through standard hooks and Slots. [Conversation](conversation.md) defines Context identity, replay, Location data, target builders, and keyed renderers.
+1 -1
View File
@@ -57,7 +57,7 @@ Connection 拥有请求 URL 解析、request correlation、`/api` carrier、trus
Web 和桌面端共享[代码工作工具偏好](../../packages/client/ui-settings/README.zh.md#use-this-package)。它控制诊断 View、[可用的预设选项](../../packages/client/ui-agent-preset/README.zh.md)、改动文件卡片和内置 HTML 预览策略,不改变 Session 记录。
`ui-session` 安装 Session scope adapter,并提供 `useSessions`、`useSessionStatus`、`useSessionRetainInfo`、`useSession`、`sessionId` 和 `useProjection`。`SessionProvider` 可以继承外围 binding,也可以绑定显式 `SessionReference`,因此并存子树可以指向不同 Session。领域 adapter 可以继续添加标准 source,但不会把 React hook 放进 model object。
`ui-session` 安装 Session scope adapter,并在 [remote-access](../../packages/api/remote-access/README.zh.md) Client 服务之上提供 `useSessions`、`useSessionStatus`、`useSessionRetainInfo`、`useSessionAccess`、`useSession`、`sessionId`、`useProjection` 和会话作用域的 `useAccess`。`SessionProvider` 可以继承外围 binding,也可以绑定显式 `SessionReference`,因此并存子树可以指向不同 Session。领域 adapter 可以继续添加标准 source,但不会把 React hook 放进 model object。
`ui-conversation` 对每个 `SessionBinding.eventSource` 只绑定一次。它的 event registry 把持久 Session event 与 Client-only `assistant/live-chunk` update 关联成稳定的业务 Context,view registry 则 materialize target snapshot。Chat Assistant、Trajectory Assistant 与 Turn Tail 同时解释 live chunk 和持久 settlement 中嵌入的紧凑 stream,因此重连与分页历史无需持久 token 行即可复现相同 Assistant 状态。`ui-chat` 与 `ui-trajectory` 分别注册自己的 Definition 和 builder:它们可以解释同一 event family,但不会导入或共享彼此的最终 display model。Shell 选择一个已注册 view,再通过标准 hook 与 Slot 交付其 snapshot。[Conversation](conversation.zh.md)定义 Context identity、replay、Location data、target builder 与 keyed renderer。
+15 -3
View File
@@ -116,13 +116,23 @@ createSharedFetchHandler(channel: '/api'): ConnectionFetchHandler
requestRejection(request: ConnectionTrustRequest): ConnectionRequestRejection
/**
* Admit one request: it passes {@link requestRejection} and speaks for the
* operator, or it is refused with that status.
* Admit one network request: it passes {@link requestRejection} and speaks
* for {@link browserPeer}, or it is refused with that status.
* @param request - request headers from the HTTP or upgrade request.
* @returns the operator Peer, or the rejection status.
* @returns the admitted Peer, or the rejection status.
*/
admit(request: ConnectionTrustRequest): PeerAdmission
/**
* Create an independent Peer. In-process only: no route, header, query, or
* cookie reaches it. Disposed with the Context that read `ctx.connection`,
* or earlier by its own `dispose()`.
* @param options - caller identity the Peer speaks for.
* @param options.principal - principal fixed for the Peer's lifetime.
* @returns a new Peer distinct from every other Peer.
*/
createPeer(options: { readonly principal: Principal }): PeerScope
/**
* Authenticate one frontend index request, owning a token redirect or 401.
* The cookie this mints is `Secure` when the mounted Web carrier serves TLS,
@@ -141,6 +151,8 @@ authorizeIndex(request: ConnectionIndexRequest, response: ConnectionIndexRespons
authenticatedUrl(baseUrl: string): string
```
Types: [Principal](typert.md)
Source: [`packages/client/connection/src/rpc.ts`](../../packages/client/connection/src/rpc.ts)
<a id="ctxwebapp--webapp"></a>
+15 -3
View File
@@ -116,13 +116,23 @@ createSharedFetchHandler(channel: '/api'): ConnectionFetchHandler
requestRejection(request: ConnectionTrustRequest): ConnectionRequestRejection
/**
* Admit one request: it passes {@link requestRejection} and speaks for the
* operator, or it is refused with that status.
* Admit one network request: it passes {@link requestRejection} and speaks
* for {@link browserPeer}, or it is refused with that status.
* @param request - request headers from the HTTP or upgrade request.
* @returns the operator Peer, or the rejection status.
* @returns the admitted Peer, or the rejection status.
*/
admit(request: ConnectionTrustRequest): PeerAdmission
/**
* Create an independent Peer. In-process only: no route, header, query, or
* cookie reaches it. Disposed with the Context that read `ctx.connection`,
* or earlier by its own `dispose()`.
* @param options - caller identity the Peer speaks for.
* @param options.principal - principal fixed for the Peer's lifetime.
* @returns a new Peer distinct from every other Peer.
*/
createPeer(options: { readonly principal: Principal }): PeerScope
/**
* Authenticate one frontend index request, owning a token redirect or 401.
* The cookie this mints is `Secure` when the mounted Web carrier serves TLS,
@@ -141,6 +151,8 @@ authorizeIndex(request: ConnectionIndexRequest, response: ConnectionIndexRespons
authenticatedUrl(baseUrl: string): string
```
Types: [Principal](typert.zh.md)
Source: [`packages/client/connection/src/rpc.ts`](../../packages/client/connection/src/rpc.ts)
<a id="ctxwebapp--webapp"></a>
+8 -2
View File
@@ -387,11 +387,17 @@ Host service backing the generated `ctx.remote.workspace` namespace.
@Remote('unpinSession') unpinSession(request: WorkspaceUnpinSessionRequest): Promise<WorkspacePinValue>
/**
* Stream a complete Workspace baseline followed by ordered increments.
* Stream a complete Workspace baseline followed by ordered increments. A
* non-owner receives only the Workspaces and Sessions `allows()` admits.
* When a delivered Workspace is no longer admitted at its next change or
* the next order change, the stream sends a `replace` frame with the
* complete filtered state instead of a `remove`, because a Client never
* resurrects a removed Workspace; a `remove` frame reports a deletion and
* reaches only a Client that received that Workspace.
* @param signal - generation cancellation.
* @returns baseline followed by ordered Workspace increments.
*/
@Remote({ mode: 'stream' }) follow(signal: AbortSignal): AsyncIterable<WorkspaceFollowFrame>
@Remote({ mode: 'stream' }) async *follow(signal: AbortSignal): AsyncIterable<WorkspaceFollowFrame>
```
Source: [`packages/api/workspace-controller/src/index.ts`](../../packages/api/workspace-controller/src/index.ts)
+8 -2
View File
@@ -387,11 +387,17 @@ Host service backing the generated `ctx.remote.workspace` namespace.
@Remote('unpinSession') unpinSession(request: WorkspaceUnpinSessionRequest): Promise<WorkspacePinValue>
/**
* Stream a complete Workspace baseline followed by ordered increments.
* Stream a complete Workspace baseline followed by ordered increments. A
* non-owner receives only the Workspaces and Sessions `allows()` admits.
* When a delivered Workspace is no longer admitted at its next change or
* the next order change, the stream sends a `replace` frame with the
* complete filtered state instead of a `remove`, because a Client never
* resurrects a removed Workspace; a `remove` frame reports a deletion and
* reaches only a Client that received that Workspace.
* @param signal - generation cancellation.
* @returns baseline followed by ordered Workspace increments.
*/
@Remote({ mode: 'stream' }) follow(signal: AbortSignal): AsyncIterable<WorkspaceFollowFrame>
@Remote({ mode: 'stream' }) async *follow(signal: AbortSignal): AsyncIterable<WorkspaceFollowFrame>
```
Source: [`packages/api/workspace-controller/src/index.ts`](../../packages/api/workspace-controller/src/index.ts)
@@ -661,7 +661,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'KeyedStandardSource',
declaration: 'export type KeyedStandardSource = (key: string) => HostObservable<unknown> | undefined;',
declaration: 'export type KeyedStandardSource = (key: string, member?: string) => HostObservable<unknown> | undefined;',
},
{
name: 'KeyPropsOf',
File diff suppressed because it is too large. Load diff
@@ -754,8 +754,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
parameters: [],
},
{
signature: 'readonly operator: PeerScope',
description: 'The operator Peer every admitted request speaks for; its scope lives as long as Connection.',
signature: 'readonly browserPeer: PeerScope',
description: 'The Peer every authenticated browser connection shares; its principal is the owner and its scope lives as long as Connection.',
parameters: [],
},
{
@@ -777,9 +777,15 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
signature: 'admit(request: ConnectionTrustRequest): PeerAdmission',
description: 'Admit one request: it passes requestRejection and speaks for the operator, or it is refused with that status.',
description: 'Admit one network request: it passes requestRejection and speaks for browserPeer, or it is refused with that status.',
parameters: [{ name: 'request', description: 'request headers from the HTTP or upgrade request.' }],
returns: 'the operator Peer, or the rejection status.',
returns: 'the admitted Peer, or the rejection status.',
},
{
signature: 'createPeer(options: { readonly principal: Principal }): PeerScope',
description: 'Create an independent Peer. In-process only: no route, header, query, or cookie reaches it. Disposed with the Context that read `ctx.connection`, or earlier by its own `dispose()`.',
parameters: [{ name: 'options', description: '.principal - principal fixed for the Peer\'s lifetime.' }],
returns: 'a new Peer distinct from every other Peer.',
},
{
signature: 'authorizeIndex(request: ConnectionIndexRequest, response: ConnectionIndexResponse): boolean',
@@ -1873,6 +1879,49 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
],
},
{
key: 'remoteAccess',
summary: 'Registry of access policies, the Gateway\'s access decisions, list filtering, and the per-Peer access snapshot.',
description: 'Registry of access policies, the Gateway\'s access decisions, list filtering, and the per-Peer access snapshot. Every composition that mounts the Gateway mounts this service too.',
methods: [
{
signature: 'registerPolicy<K extends Exclude<keyof PrincipalMap, \'owner\'>>(kind: K, policy: RemoteAccessPolicy<PrincipalMap[K]>): () => void',
description: 'Register the policy of one non-owner principal kind in the caller\'s fiber; open snapshots are recomputed on registration and removal.',
parameters: [{ name: 'kind', description: 'the principal kind the policy serves; never `owner`.' }, { name: 'policy', description: 'business-owned bindings and refusal reasons for principals of `kind`.' }],
returns: 'disposer removing this policy.',
throws: ['Error when `kind` already has a policy.'],
},
{
signature: 'invalidate(principal?: Principal): void',
description: 'Report that the bindings or condition answers for `principal` may have changed. Business packages call this on every grant, revocation, mode change, and expiry; open snapshots re-push, and the next failure of each policy is logged again.',
parameters: [{ name: 'principal', description: 'affected principal; omitted for every principal.' }],
},
{
signature: 'decide(request: RemoteAccessRequest<Principal>): RemoteAccessDecision',
description: 'Gateway-only: decide one request whose resources the Gateway resolved. The owner is allowed, and so is `remoteAccess/follow`: every Peer reads its own snapshot. Otherwise the action is allowed only when, on every listed resource, some binding of the principal\'s policy names that resource, has a role listing the action, and has no condition or a condition that allows. `bindings()` is called once per decision.',
parameters: [{ name: 'request', description: 'the caller or recipient, the action, the resources it touches, and a call\'s arguments.' }],
returns: '`allow`; or `deny` with `no-allow` when the principal kind has no policy, the first refusing condition\'s reason on the first resource no binding allows, else `explain()`\'s reason, else `no-allow`; `policy-failed` when `bindings()`, a condition, or `explain()` throws, logged once per policy until the next `invalidate()`. The Gateway throws `remote-access/denied` for a denied call and drops a denied event.',
},
{
signature: 'allows(resource: RemoteAccessResource): boolean',
description: 'Filter items inside a Remote method. The invocation is read from the Context that accessed this service, so call it as `this.ctx.remoteAccess.allows(resource)` from the Remote method. Each check reads the policy\'s bindings again, so a long-running stream sees binding changes from the next check on.',
parameters: [{ name: 'resource', description: 'resource of one result item.' }],
returns: 'true for the owner and outside a Remote call; otherwise whether the invocation\'s endpoint action is allowed on `resource` without call arguments, false when the policy is absent or fails.',
},
{
signature: 'snapshotFor(principal: Principal): RemoteAccessBaseline',
description: 'The baseline a page opens with before its snapshot stream connects. A business package rendering a non-owner page assigns this value to REMOTE_ACCESS_SNAPSHOT_KEY after every index injection row, replacing the owner baseline the webServer index carries.',
parameters: [{ name: 'principal', description: 'the page\'s caller identity.' }],
returns: 'the owner baseline for the owner; otherwise the principal\'s current grants.',
},
{
signature: '@Remote({ mode: \'stream\' }) async *follow(signal: AbortSignal): AsyncIterable<RemoteAccessFrame>',
description: 'Stream the calling Peer\'s access snapshot: one baseline, then `upsert` and `remove` frames after each `invalidate()` and each policy registration or removal that changes a grant. The owner receives only `{ owner: true }`. The generation ends when `signal` aborts or this service unloads, so a reloaded service starts every reader from a fresh baseline.',
parameters: [{ name: 'signal', description: 'generation cancellation.' }],
returns: 'the access frames of one generation.',
},
],
},
{
key: 'sandbox',
summary: 'Abstract process-sandbox service.',
@@ -2051,7 +2100,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'the new Session identity.',
},
{
signature: '@Remote(\'prompt\') prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>',
signature: '@Remote(\'prompt\') async prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>',
description: 'Admit one prompt after explicitly resuming its Session.',
parameters: [{ name: 'request', description: 'Session identity, prompt content, source metadata, and delivery mode.' }, { name: 'signal', description: 'caller cancellation before prompt admission begins.' }],
returns: 'acknowledgement that the Agent accepted the prompt.',
@@ -2075,13 +2124,13 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'acknowledgement that cancellation was requested.',
},
{
signature: '@Remote(\'page\') page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>',
signature: '@Remote({ name: \'page\', resources: { session: addressedSession } }) page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>',
description: 'Read one cold-safe, message-aligned Session history page.',
parameters: [{ name: 'request', description: 'durable address, backward cursor, and page budget.' }, { name: 'signal', description: 'cancellation for persistence reads.' }],
returns: 'one chronological page.',
},
{
signature: '@Remote({ mode: \'stream\' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>',
signature: '@Remote({ mode: \'stream\', resources: { session: addressedSession } }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>',
description: 'Follow one Session log from its opening or resume cursor.',
parameters: [{ name: 'request', description: 'durable address and last committed sequence already held by the caller.' }, { name: 'signal', description: 'cancellation owned by the Remote stream carrier.' }],
returns: 'a complete opening snapshot followed by gap-free durable event frames and optional cursorless assistant-stream frames.',
@@ -2093,7 +2142,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'a sequenced baseline, cached hints when migration is deferred, or null when absent.',
},
{
signature: '@Remote({ mode: \'stream\' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>',
signature: '@Remote({ mode: \'stream\' }) async *control(signal: AbortSignal): AsyncIterable<SessionControlFrame>',
description: 'Stream a complete live-control baseline followed by replacement frames.',
parameters: [{ name: 'signal', description: 'cancellation owned by the Remote stream carrier.' }],
returns: 'one complete baseline followed by live replacement frames.',
@@ -3459,9 +3508,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
signature: 'hasLiveClient(): boolean',
description: 'Check for an active Client event stream.',
description: 'Check for an active owner Client event stream, the only kind that can answer forwarded waterfalls.',
parameters: [],
returns: 'whether a stream is open and has not been cancelled.',
returns: 'whether an owner stream is open and has not been cancelled.',
},
{
signature: 'registerRemoteEvents( source: TypertRemoteEventSource, host: RemoteEventHostInfo, ): () => Promise<void>',
@@ -3768,8 +3817,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'the complete resulting pin set, most recently pinned first.',
},
{
signature: '@Remote({ mode: \'stream\' }) follow(signal: AbortSignal): AsyncIterable<WorkspaceFollowFrame>',
description: 'Stream a complete Workspace baseline followed by ordered increments.',
signature: '@Remote({ mode: \'stream\' }) async *follow(signal: AbortSignal): AsyncIterable<WorkspaceFollowFrame>',
description: 'Stream a complete Workspace baseline followed by ordered increments. A non-owner receives only the Workspaces and Sessions `allows()` admits. When a delivered Workspace is no longer admitted at its next change or the next order change, the stream sends a `replace` frame with the complete filtered state instead of a `remove`, because a Client never resurrects a removed Workspace; a `remove` frame reports a deletion and reaches only a Client that received that Workspace.',
parameters: [{ name: 'signal', description: 'generation cancellation.' }],
returns: 'baseline followed by ordered Workspace increments.',
},
@@ -4993,7 +5042,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'ConnectionFetchHandler',
declaration: 'export interface ConnectionFetchHandler {\n requestBodyMode(request: {\n readonly method: string;\n readonly url: URL;\n }): ConnectionRequestBodyMode;\n fetch(request: Request): Promise<Response>;\n}',
declaration: 'export interface ConnectionFetchHandler {\n requestBodyMode(request: {\n readonly method: string;\n readonly url: URL;\n }): ConnectionRequestBodyMode;\n fetch(request: Request, peer: PeerScope): Promise<Response>;\n}',
},
{
name: 'ConnectionFetchMethod',
@@ -5645,7 +5694,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'InvokeRemoteRequest',
declaration: 'export interface InvokeRemoteRequest {\n readonly namespace: string;\n readonly method: string;\n readonly args: Readonly<Record<string, unknown>>;\n readonly uplink?: AsyncIterable<unknown>;\n readonly uplinkWindow?: RemoteUplinkWindow | undefined;\n readonly peer?: PeerScope;\n readonly signal?: AbortSignal;\n}',
declaration: 'export interface InvokeRemoteRequest {\n readonly namespace: string;\n readonly method: string;\n readonly args: Readonly<Record<string, unknown>>;\n readonly uplink?: AsyncIterable<unknown>;\n readonly uplinkWindow?: RemoteUplinkWindow | undefined;\n readonly peer: PeerScope;\n readonly signal?: AbortSignal;\n}',
},
{
name: 'JobAppendOptions',
@@ -6185,7 +6234,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'PeerScope',
declaration: 'export interface PeerScope {\n readonly id: PeerId;\n readonly ctx: Context;\n dispose(): Promise<void>;\n}',
declaration: 'export interface PeerScope {\n readonly id: PeerId;\n readonly ctx: Context;\n readonly principal: Principal;\n readonly signal: AbortSignal;\n dispose(): Promise<void>;\n}',
},
{
name: 'PendingModule',
@@ -6311,6 +6360,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'PreToolDecision',
declaration: 'export type PreToolDecision = {\n kind: \'allow\';\n} | {\n kind: \'deny\';\n reason: string;\n info?: ToolErrorInfo;\n} | {\n kind: \'cancel\';\n} | {\n kind: \'ask\';\n reason?: string;\n displayReason?: {\n readonly en: string;\n readonly [locale: string]: string;\n };\n};',
},
{
name: 'Principal',
declaration: 'export type Principal = PrincipalMap[keyof PrincipalMap];',
},
{
name: 'PrincipalMap',
declaration: 'export interface PrincipalMap {\n owner: {\n readonly kind: \'owner\';\n };\n}',
},
{
name: 'ProductEvent',
declaration: 'export type ProductEvent = {\n [K in keyof ProductEventMap]: {\n eventName: K;\n attributes: ProductEventMap[K];\n timestamp: number;\n };\n}[keyof ProductEventMap];',
@@ -6483,6 +6540,62 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'Reload',
declaration: 'export interface Reload {\n filename: string;\n modules: ReloadModules;\n runtime?: Plugin.Runtime | undefined;\n}',
},
{
name: 'RemoteAccessAction',
declaration: 'export type RemoteAccessAction = RemoteAccessEndpoint | `event:${string}`;',
},
{
name: 'RemoteAccessBaseline',
declaration: 'export type RemoteAccessBaseline = Extract<RemoteAccessFrame, {\n type: \'baseline\';\n}>;',
},
{
name: 'RemoteAccessBinding',
declaration: 'export type RemoteAccessBinding = {\n [K in RemoteAccessResourceKind]: {\n readonly role: RemoteAccessRole<K>;\n readonly resource: RemoteAccessResourceOf<K>;\n readonly condition?: RemoteAccessCondition<K>;\n };\n}[RemoteAccessResourceKind];',
},
{
name: 'RemoteAccessCondition',
declaration: 'export type RemoteAccessCondition<K extends RemoteAccessResourceKind> = (request: {\n readonly action: RemoteAccessAction;\n readonly resource: RemoteAccessResourceOf<K>;\n readonly args?: Readonly<Record<string, unknown>>;\n}) => RemoteAccessDecision;',
},
{
name: 'RemoteAccessDecision',
declaration: 'export type RemoteAccessDecision = {\n readonly kind: \'allow\';\n} | {\n readonly kind: \'deny\';\n readonly reason: string;\n};',
},
{
name: 'RemoteAccessEndpoint',
declaration: 'export type RemoteAccessEndpoint = `${string}/${string}`;',
},
{
name: 'RemoteAccessFrame',
declaration: 'export type RemoteAccessFrame = {\n readonly type: \'baseline\';\n readonly owner: true;\n} | {\n readonly type: \'baseline\';\n readonly owner: false;\n readonly grants: readonly RemoteAccessGrant[];\n} | {\n readonly type: \'upsert\';\n readonly grant: RemoteAccessGrant;\n} | {\n readonly type: \'remove\';\n readonly resource: RemoteAccessResource;\n};',
},
{
name: 'RemoteAccessGrant',
declaration: 'export interface RemoteAccessGrant {\n readonly resource: RemoteAccessResource;\n readonly actions: readonly string[];\n}',
},
{
name: 'RemoteAccessPolicy',
declaration: 'export interface RemoteAccessPolicy<P extends Principal> {\n bindings(principal: P): readonly RemoteAccessBinding[];\n explain?(request: RemoteAccessRequest<P>): string | undefined;\n}',
},
{
name: 'RemoteAccessRequest',
declaration: 'export interface RemoteAccessRequest<P extends Principal> {\n readonly principal: P;\n readonly action: RemoteAccessAction;\n readonly resources: readonly RemoteAccessResource[];\n readonly args?: Readonly<Record<string, unknown>>;\n}',
},
{
name: 'RemoteAccessResource',
declaration: 'export type RemoteAccessResource = {\n readonly kind: \'global\';\n} | {\n [Kind in TypertResourceKind]: {\n readonly kind: Kind;\n readonly id: TypertResourceId<TypertResourceMap[Kind]>;\n };\n}[TypertResourceKind];',
},
{
name: 'RemoteAccessResourceKind',
declaration: 'export type RemoteAccessResourceKind = \'global\' | TypertResourceKind;',
},
{
name: 'RemoteAccessResourceOf',
declaration: 'export type RemoteAccessResourceOf<K extends RemoteAccessResourceKind> = Extract<RemoteAccessResource, {\n readonly kind: K;\n}>;',
},
{
name: 'RemoteAccessRole',
declaration: 'export interface RemoteAccessRole<K extends RemoteAccessResourceKind> {\n readonly name: string;\n readonly resource: K;\n readonly actions: readonly RemoteAccessAction[];\n}',
},
{
name: 'RemoteError',
declaration: 'export class RemoteError<Code extends RemoteErrorCode = RemoteErrorCode> extends Error {\n readonly isDSHRemoteError: true;\n constructor(readonly code: Code, message: string, readonly details: RemoteErrorDetailsMap[Code], options?: ErrorOptions);\n}',
@@ -8173,7 +8286,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'TypertGatewayWireStream',
declaration: 'export interface TypertGatewayWireStream {\n readonly open: (endpoint: string, payload: unknown, uplink: AsyncIterable<unknown>, peer: PeerScope | undefined, signal: AbortSignal) => Promise<AsyncIterable<unknown>>;\n readonly failure: (error: unknown) => {\n readonly code: string;\n readonly message: string;\n readonly details: object;\n };\n}',
declaration: 'export interface TypertGatewayWireStream {\n readonly open: (endpoint: string, payload: unknown, uplink: AsyncIterable<unknown>, peer: PeerScope, signal: AbortSignal) => Promise<AsyncIterable<unknown>>;\n readonly failure: (error: unknown) => {\n readonly code: string;\n readonly message: string;\n readonly details: object;\n };\n}',
},
{
name: 'TypertMemberModel',
@@ -8205,7 +8318,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'TypertRemoteEventFrame',
declaration: 'export interface TypertRemoteEventFrame {\n readonly event: string;\n readonly args: readonly unknown[];\n}',
declaration: 'export interface TypertRemoteEventFrame {\n readonly event: string;\n readonly args: readonly unknown[];\n readonly resources?: Readonly<Record<string, TypertResourceReader>>;\n}',
},
{
name: 'TypertRemoteEventInvocation',
@@ -8227,6 +8340,26 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'TypertRemoteValue',
declaration: 'export interface TypertRemoteValue<Value = unknown> {\n readonly create: () => TypertSchema<Value>;\n readonly description?: string | undefined;\n readonly jsonSchema?: Readonly<Record<string, unknown>> | undefined;\n readonly jsonSchemaError?: string | undefined;\n}',
},
{
name: 'TypertResource',
declaration: 'export interface TypertResource<Id> {\n readonly [RESOURCE_ID]: Id;\n}',
},
{
name: 'TypertResourceId',
declaration: 'export type TypertResourceId<Resource> = Resource extends TypertResource<infer Id> ? Id : never;',
},
{
name: 'TypertResourceKind',
declaration: 'export type TypertResourceKind = Extract<keyof TypertResourceMap, string>;',
},
{
name: 'TypertResourceMap',
declaration: 'export interface TypertResourceMap {\n}',
},
{
name: 'TypertResourceReader',
declaration: 'export type TypertResourceReader = {\n read(...args: readonly unknown[]): unknown;\n}[\'read\'];',
},
{
name: 'TypertSchema',
declaration: 'export interface TypertSchema<Output = unknown> {\n parse(value: unknown): Output;\n}',
@@ -8557,7 +8690,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'WorkspaceFollowIncrement',
declaration: 'export type WorkspaceFollowIncrement = {\n readonly type: \'upsert\';\n readonly workspace: WorkspaceView;\n} | {\n readonly type: \'remove\';\n readonly workspaceId: WorkspaceId;\n} | {\n readonly type: \'order\';\n readonly workspaceIds: readonly WorkspaceId[];\n} | {\n readonly type: \'archived\';\n readonly archivedSessionIds: readonly SessionId[];\n} | {\n readonly type: \'pinned\';\n readonly pinnedSessionIds: readonly SessionId[];\n};',
declaration: 'export type WorkspaceFollowIncrement = {\n readonly type: \'replace\';\n readonly value: WorkspaceBaseline;\n} | {\n readonly type: \'upsert\';\n readonly workspace: WorkspaceView;\n} | {\n readonly type: \'remove\';\n readonly workspaceId: WorkspaceId;\n} | {\n readonly type: \'order\';\n readonly workspaceIds: readonly WorkspaceId[];\n} | {\n readonly type: \'archived\';\n readonly archivedSessionIds: readonly SessionId[];\n} | {\n readonly type: \'pinned\';\n readonly pinnedSessionIds: readonly SessionId[];\n};',
},
{
name: 'WorkspaceInsertBeforeRequest',
@@ -17,6 +17,7 @@ Every package below exports a Cordis plugin that a bundle patch can name in a Lo
| `@deepseek-ai/dsh-api-account-controller` | no | Expose safe account operations over authenticated Remote |
| `@deepseek-ai/dsh-api-gateway` | yes | Typert Remote Host dispatcher and Client API endpoint |
| `@deepseek-ai/dsh-api-job-controller` | yes | Job Remote observation stream and the reference-counted client job-output service |
| `@deepseek-ai/dsh-api-remote-access` | no | Method-level Remote access control for non-owner Peers and the per-Peer access snapshot |
| `@deepseek-ai/dsh-api-remotes` | no | Remote BFF assembly for application-selected Host capabilities |
| `@deepseek-ai/dsh-api-session-controller` | yes | Session Remote commands, cold reads, and live control transport |
| `@deepseek-ai/dsh-api-settings-controller` | yes | Remote owner for the configuration surfaces over the settings-domain seams |
+9 -6
View File
@@ -43,12 +43,14 @@ const MAX_DECL_CHARS = 1200
/**
* Line budget for ONE slot's expanded report. The whole point of narrowing to a
* single slot is to spend less context, so a report a model cannot finish
* reading is a defect rather than a detail. The widest measured slot renders 60
* lines, so this leaves room to document a slot properly while catching the two
* ways a report runs away: an owner share that hands down a subsystem instead of
* a share, and prose that grew into a manual.
* reading is a defect rather than a detail. The budget leaves room to document
* a slot properly while catching the two ways a report runs away: an owner
* share that hands down a subsystem instead of a share, and prose that grew
* into a manual. Every report also lists one line per standard prop seat of its
* scope, so the widest Session-scope slot (`tool.call.toolview`, with the
* global and Session access seats) sets the current 122.
*/
const MAX_ENTRY_LINES = 120
const MAX_ENTRY_LINES = 122
/** One register-call option as the catalog teaches it. */
interface OptionDoc {
@@ -255,7 +257,8 @@ function standardKits(files: readonly ScannedFile[]): ReadonlyMap<string, readon
const kits = new Map<string, readonly string[]>()
for (const scope of SCOPES) {
const extra = SCOPE_KIT[scope]
kits.set(scope, [...global, ...extra === undefined ? [] : standardKitMembers(files, extra)])
// A scope-kit member replaces the global member of the same name, so each seat is listed once.
kits.set(scope, [...new Set([...global, ...extra === undefined ? [] : standardKitMembers(files, extra)])])
}
return kits
}
+10
View File
@@ -137,6 +137,7 @@ export const SERVICE_PAGE: Record<string, string> = {
tools: 'tools.md',
typert: 'typert.md',
typertGateway: 'typert.md',
remoteAccess: 'typert.md',
userQuestions: 'user-questions.md',
web: 'web.md',
workflowEngine: 'workflow.md',
@@ -281,6 +282,10 @@ export const EVENT_WALK_EXEMPTIONS: Record<string, string> = {
* appear on more than one page.
*/
export const LINK_MAP: Readonly<Record<string, string>> = {
Principal: 'typert.md',
PrincipalMap: 'typert.md',
RemoteAccessResource: 'typert.md',
RemoteAccessResourceKind: 'typert.md',
RemoteStream: 'typert.md',
RemoteUplinkWindow: 'typert.md',
EventLogOptions: 'otel.md',
@@ -864,6 +869,11 @@ export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
ConnectionTrustRequest: 'transport authentication input is owned by packages/client/connection/src/rpc.ts',
PeerAdmission: 'Peer admission outcome is owned by packages/client/connection/src/rpc.ts',
PeerScope: 'Peer scope contract is owned by packages/typert/protocol/src/types.ts',
RemoteAccessPolicy: 'access policy contract is owned by packages/api/remote-access/README.md',
RemoteAccessRequest: 'Gateway decision requests are owned by packages/api/remote-access/README.md',
RemoteAccessDecision: 'Gateway decisions are owned by packages/api/remote-access/README.md',
RemoteAccessFrame: 'access snapshot frames are owned by packages/api/remote-access/README.md',
RemoteAccessBaseline: 'access snapshot frames are owned by packages/api/remote-access/README.md',
ConnectionIndexRequest: 'frontend authentication request is owned by packages/client/connection/src/rpc.ts',
ConnectionIndexResponse: 'frontend authentication response is owned by packages/client/connection/src/rpc.ts',
Profile: 'resolved profile layers are owned by packages/boot/app-boot/README.md',
+8
View File
@@ -370,6 +370,14 @@ const SERVICE_ROLES: ServiceRole[] = [
mode: 'core',
note: 'Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier.',
},
{
key: 'remoteAccess',
pkg: 'api-remote-access',
title: 'Remote method access control',
mode: 'core',
consumers: ['api-gateway', 'api-session-controller', 'api-workspace-controller'],
note: 'Decides which Remote methods a non-owner Peer may call through business-registered role-based policies, filters list results, and streams each Peer access snapshot.',
},
{
key: 'sessionPersistence',
pkg: 'session-persistence',
-1
View File
@@ -176,7 +176,6 @@
},
"packages/api/gateway/tests/gateway.host.spec.ts": {
"344e2cbbed2a8ef8d5356a24def92ac3453ffaa2efa58f5481d59206f5b79cbc": 1,
"36f7992641e8249fd0972dedd8b989d5fcbf97d60047120c5d4cf4182e76da0a": 1,
"529f85662621d1be427dd178aee815ab0595dc05bc2324545d4914e17515dace": 1,
"52f51bf0e468505650c8d072d3f1d77b1d2e16492a6c7b5082e7e6c15240d7be": 1,
"7fedb21312a9c9efcaa4f269ad32fb590191f31e6ca49b000abcda55e9dac795": 1,
+35
View File
@@ -2086,6 +2086,16 @@
"symbol": "RemoteStreamHandle",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "PrincipalMap",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "Principal",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "PeerScope",
@@ -2101,6 +2111,31 @@
"symbol": "RemoteInvocation",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypertResourceMap",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "RemoteAccessResourceKind",
"source": "packages/typert/protocol/src/remote-access.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "RemoteAccessResource",
"source": "packages/typert/protocol/src/remote-access.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "RemoteScopeOptions",
"source": "packages/typert/protocol/src/index.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "RemoteMethodOptions",
"source": "packages/typert/protocol/src/index.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypertRegistryContract",