refactor(typert): rename RemoteContext to RemoteScope

This commit is contained in:
imccyu
2026-08-07 18:35:50 +08:00
parent d6ffd87c5f
commit d362cdb54f
27 changed files with 127 additions and 127 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md
2026-08-02-typert-remote-method-calls.md: a8254090e042e4b359ae74fc5c19bad8abc5ef89
2026-08-02-typert-remote-method-calls.zh.md: f1b7e5f9c61b474379962ce007e5d6bb966e5ebd
2026-08-02-typert-remote-method-calls.md: 215c647bcd7413b92625ee670022dc7316e3045a
2026-08-02-typert-remote-method-calls.zh.md: 0ce431b7cbc948e937f722f2769b15a1d26dcec9

View File

@@ -16,7 +16,7 @@ The Host and Browser Client use separate TypeScript Programs because each side a
## Decision
A business Service extends `GatewayService` and declares callable methods with `@Remote` or `@RemoteContext()`. A Service that already has another base class may instead expose the same binding through `bindTypeRTGateway()`. TypeRT generates the Host-local reflection artifact and a platform-independent Remote consumer projection from the Host Program. The Client Program continues to generate its own local reflection artifact independently.
A business Service extends `GatewayService` and declares callable methods with `@Remote` or `@RemoteScope()`. A Service that already has another base class may instead expose the same binding through `bindTypeRTGateway()`. TypeRT generates the Host-local reflection artifact and a platform-independent Remote consumer projection from the Host Program. The Client Program continues to generate its own local reflection artifact independently.
The Remote consumer projection contains `.d.ts`, `.d.ts.map`, and `.js` files. The `.d.ts` exposes only methods marked with a Remote decorator and refers to the business package's single public type symbols. The `.d.ts.map` navigates consumer API methods back to their Host business method implementations. The `.js` carries endpoint, parameter, Context, and Zod information for the same contract. At the assembly layer, the Browser Client mounts the required Remote JS contributions onto the Client Remote Service. The projection and Remote abstraction remain platform-independent so that a future TUI can reuse them.
@@ -64,7 +64,7 @@ export class GoalService extends GatewayService {
`goals` is the explicit Cordis service key passed to `super()` and is the default wire namespace. Pass a `namespace` option as the third argument only when the protocol namespace genuinely needs to differ from the service key.
Use `@RemoteContext()` when the Service receiver must be resolved within an isolated kind of Context. Context identity does not enter the business method's parameters:
Use `@RemoteScope()` when the Service receiver must be resolved within an isolated kind of Context. Scope identity does not enter the business method's parameters:
```text
export class ScopedGoalService extends GatewayService {
@@ -72,28 +72,28 @@ export class ScopedGoalService extends GatewayService {
super(ctx, 'goals')
}
@RemoteContext('agent', 'create')
@RemoteScope('agent', 'create')
remoteExportCreate(request: CreateGoalRequest): Promise<CreateGoalResult> {
// Runs against the goals service resolved from the Agent Context.
}
}
```
An endpoint selects exactly one invocation mode. A flow that needs an explicit `Agent` parameter uses `@Remote`. A flow that first switches to an Agent Context and then resolves a scoped receiver uses `@RemoteContext('agent')`. TypeRT does not infer either mode from the method body or from a missing parameter.
An endpoint selects exactly one invocation mode. A flow that needs an explicit `Agent` parameter uses `@Remote`. A flow that first switches to an Agent Context and then resolves a scoped receiver uses `@RemoteScope('agent')`. TypeRT does not infer either mode from the method body or from a missing parameter.
Business packages depend only on the lightweight `@deepseek-ai/dsh-type-meta`. It provides `GatewayService` and declaration protocols for decorators, the binding fallback, lookup, Remote Context, and descriptors, without depending on the TypeScript compiler, Zod, HTTP, or the Client runtime.
Business packages depend only on the lightweight `@deepseek-ai/dsh-type-meta`. It provides `GatewayService` and declaration protocols for decorators, the binding fallback, lookup, Remote Scope, and descriptors, without depending on the TypeScript compiler, Zod, HTTP, or the Client runtime.
A method that cooperatively supports cancellation declares `signal: AbortSignal` as its final Host parameter. This reserved parameter is not a business value, lookup, or JSON field. The generated consumer method exposes it as a final optional parameter so ordinary calls remain unchanged while callers that own cancellation can pass a signal.
## Decorators and the explicit Gateway facet
A decorator only states that a method participates in the Remote contract. It performs no runtime type reflection and injects no hidden symbol into a Service constructor. The arguments to `@Remote('create')` and `@RemoteContext('agent', 'create')` are external method names; the decorated member may be the business method itself or an adapter such as `remoteExportCreate`. The member name becomes the external method name only when no alias is provided. Inheriting `GatewayService` is the normal explicit declaration that a Service has joined the Gateway; its public readonly `typertGateway` field keeps the binding visible on the runtime instance.
A decorator only states that a method participates in the Remote contract. It performs no runtime type reflection and injects no hidden symbol into a Service constructor. The arguments to `@Remote('create')` and `@RemoteScope('agent', 'create')` are external method names; the decorated member may be the business method itself or an adapter such as `remoteExportCreate`. The member name becomes the external method name only when no alias is provided. Inheriting `GatewayService` is the normal explicit declaration that a Service has joined the Gateway; its public readonly `typertGateway` field keeps the binding visible on the runtime instance.
In SRC mode, the decorator may record the prototype, method name, and invocation mode in a `WeakMap` internal to `dsh-type-meta`. It writes no custom properties to a Service instance, prototype, constructor, or method function.
In LIB mode, the TypeRT compiler performs strict method discovery, type resolution, and descriptor generation. It accepts a literal service key in `GatewayService`'s direct `super()` call or the explicit binding fallback; generation neither rewrites business source nor injects hidden registration metadata.
## Lookup and Remote Context registration
## Lookup and Remote Scope registration
The Gateway has no built-in branches for Agent, Session, or other business objects. Each object-owning package provides both a static declaration and a runtime provider:
@@ -115,7 +115,7 @@ The static declaration tells TypeRT that `Agent` corresponds to `SessionId` on t
Lookup objects such as Agent and Session may each occupy only one top-level parameter position. An ordinary JSON request may be passed as another complete parameter, but this design does not support `request.agent`, object destructuring, arrays of objects, nested lookups, or searching arbitrary complex structures for IDs.
Remote Context uses a separate merge-extensible map and provider. The Agent package registers an `agent` Context provider that locates the Agent Context from its wire identity and resolves the Service key named by the descriptor from that Context. The Gateway does not know the internal structure of an Agent Context.
Remote Scope uses a separate merge-extensible map and Context provider. The Agent package registers an `agent` provider that locates the Agent Context from its wire identity and resolves the Service key named by the descriptor from that Context. The Gateway does not know the internal structure of an Agent Context.
The Client also registers an `agent` Context binder. The binder only retrieves a `SessionId` from the Context in which a call occurs; it neither enumerates Scopes nor copies methods into each one. A Cordis Service tracker automatically rebinds a scoped namespace to the current Agent Context.
@@ -267,7 +267,7 @@ interface TypeRTRemoteNamespaceMap {
goals: TypeRTRemoteNamespace$676f616c73
}
interface TypeRTRemoteContextMap {
interface TypeRTRemoteScopeMap {
'agent:goals/create': (
request: CreateGoalRequest,
signal?: AbortSignal,
@@ -277,14 +277,14 @@ interface TypeRTRemoteContextMap {
`TypeRTRemoteMap` preserves canonical endpoint signatures for protocol typing and reflection. The root Remote type reads `TypeRTRemoteNamespaceMap` directly instead of deriving methods indirectly through a key-remapped mapped type; the TypeScript Language Service cannot reliably navigate such indirect properties through a declaration map. A namespace interface name encodes the namespace's UTF-8 bytes as hexadecimal, so `goals` deterministically becomes `TypeRTRemoteNamespace$676f616c73`. Different packages generate the same interface name for the same namespace and use module augmentation to merge their methods, while `TypeRTRemoteNamespaceMap.goals` always refers to that one type.
TypeRT projects `TypeRTRemoteContextMap` onto a dedicated Scope type according to its Context key. The final programming interface remains:
TypeRT projects `TypeRTRemoteScopeMap` onto a dedicated Scope type according to its Context key. The final programming interface remains:
```text
ctx.remote.goals.create(agentId, request)
agentCtx.remote.goals.create(request)
```
The Agent Scope supplies its own `SessionId` automatically. A `@Remote` method with an `agent` lookup can therefore generate both root and scoped consumer signatures. A `@RemoteContext('agent')` method also omits a separate Context identity, but generates only the scoped signature. The root `Context` exposes direct namespaces through `ctx.remote`, while `AgentContext.remote` intersects that direct surface with the scoped surface. A future TUI must preserve the same distinction.
The Agent Scope supplies its own `SessionId` automatically. A `@Remote` method with an `agent` lookup can therefore generate both root and scoped consumer signatures. A `@RemoteScope('agent')` method also omits a separate Scope identity, but generates only the scoped signature. The root `Context` exposes direct namespaces through `ctx.remote`, while `AgentContext.remote` intersects that direct surface with the scoped surface. A future TUI must preserve the same distinction.
`TypeRTClientRemote` remains platform-independent, and the Browser Client exposes it as `ctx.remote`. If a future TUI reuses this type, it must likewise access it through a dedicated Remote object and Agent Scope rather than treating the Host `Context` as a broader Service collection. Public Service methods without Remote markers do not enter the Remote maps.
@@ -313,7 +313,7 @@ Client business packages depend only on `@deepseek-ai/dsh-api-remotes/client`, n
The Client Remote Service materializes each `@Remote` descriptor as a real function on a `remote.<namespace>` child Service. The function constructs named `args` in descriptor parameter order, applies the Client's strict codec, and then calls `ctx.connection.rpc.call('/api', endpoint, { args }, signal)`. For a cancellation-aware descriptor, the generated function accepts a final optional signal and combines it with the contribution mount lifetime; unmounting therefore cancels every in-flight carrier call, while a caller can cancel one call independently.
Neither a direct descriptor with `scope` nor a `@RemoteContext` descriptor copies functions into every Agent Scope. The Client Remote Service creates one Cordis child Service per namespace, registered as `remote.<namespace>`, and materializes direct and scoped variants on it. Accessing a method through `agentCtx.remote.goals` captures the current Agent Context before returning the callable handle. The method then asks the corresponding Context binder for identity from that Context. A direct scoped projection substitutes this identity at the lookup position named by `scope.wire`; a Context descriptor writes the identity into the receiver's separate wire field. Both issue the same kind of `/api` call.
Neither a direct descriptor with `scope` nor a `@RemoteScope` descriptor copies functions into every Agent Scope. The Client Remote Service creates one Cordis child Service per namespace, registered as `remote.<namespace>`, and materializes direct and scoped variants on it. Accessing a method through `agentCtx.remote.goals` captures the current Agent Context before returning the callable handle. The method then asks the corresponding Context binder for identity from that Context. A direct scoped projection substitutes this identity at the lookup position named by `scope.wire`; a Remote Scope descriptor writes the identity into the receiver's separate wire field. Both issue the same kind of `/api` call.
```text
root ctx.remote.goals.create(agentId, request)
@@ -327,7 +327,7 @@ agentCtx.remote.goals.create(request)
→ ctx.connection.rpc.call('/api', 'goals/create', { args })
```
The root `Context` merges only the direct `TypeRTClientRemote` surface. `AgentContext` replaces that property with the intersection of `TypeRTClientRemote` and `TypeRTRemoteContextApi<'agent'>`, so scoped-only methods remain unavailable from root code. If a caller bypasses the type system and dynamically calls a scoped-only method from Root, the binder reports an explicit error. If the Client already has a Cordis service named `remote.<namespace>`, or two contributions claim the same namespace and method incompatibly, mounting fails instead of overwriting the existing service.
The root `Context` merges only the direct `TypeRTClientRemote` surface. `AgentContext` replaces that property with the intersection of `TypeRTClientRemote` and `TypeRTRemoteScopeApi<'agent'>`, so scoped-only methods remain unavailable from root code. If a caller bypasses the type system and dynamically calls a scoped-only method from Root, the binder reports an explicit error. If the Client already has a Cordis service named `remote.<namespace>`, or two contributions claim the same namespace and method incompatibly, mounting fails instead of overwriting the existing service.
Generated Remote JS contains only descriptors, symbol keys, and codecs; it does not bundle Host Service implementations. The Client Remote Service creates real functions from that data, so the runtime does not depend on a JavaScript Proxy. A Proxy remains an implementation option but is not a source of types or reflection.
@@ -337,7 +337,7 @@ Remote API is a consumer capability, not a synonym for Browser API. The shipped
Remote DTS, Remote JS, `TypeRTClientRemote`, `InvocationDescriptor`, the Remote RPC data protocol, and Context binders must not depend on the DOM, Browser module loaders, or HTTP. Through Connection, the Browser Client encodes descriptor-materialized methods as `/api` RPC calls.
A future TUI can join the same call abstraction without changing business decorators, Remote maps, or the shape of API calls. The TUI-visible API must still be generated exclusively from `@Remote` and `@RemoteContext`; sharing a process with the Host must not allow it to bypass Remote restrictions and expose Service methods directly.
A future TUI can join the same call abstraction without changing business decorators, Remote maps, or the shape of API calls. The TUI-visible API must still be generated exclusively from `@Remote` and `@RemoteScope`; sharing a process with the Host must not allow it to bypass Remote restrictions and expose Service methods directly.
TUI runtime mounting, carriers, Agent Scope association, and SRC startup wiring are outside this phase.
@@ -345,7 +345,7 @@ The Web already depends on build artifacts such as `lib/client.js`, so it requir
## SRC and LIB operating modes
SRC supports local source startup. The `WeakMap` records created by `@Remote` and `@RemoteContext()` provide method names and invocation modes. At runtime, the system reads ordered parameter names from the JavaScript function signature and combines them with registered lookup/Context providers to produce a permissive descriptor.
SRC supports local source startup. The `WeakMap` records created by `@Remote` and `@RemoteScope()` provide method names and invocation modes. At runtime, the system reads ordered parameter names from the JavaScript function signature and combines them with registered lookup/Context providers to produce a permissive descriptor.
For example, `@Remote('create') remoteExportCreate(agent, request, signal)` resolves to the external method `create`, implementation member `remoteExportCreate`, two top-level business parameters, and one cancellation injection point. Lookup registration rewrites `agent` to the wire field `agentId`, `request` is passed as a same-named JSON parameter, and the final `signal` stays outside the payload. SRC does not start a `ts.Program`, use a preload or loader hook, generate or rewrite source, or inspect the internal structure of an ordinary JSON object.
@@ -365,7 +365,7 @@ Invocation resolves the descriptor, receiver, lookup providers, and Context prov
An ordinary `@Remote` call retains the original Service instance as receiver. After lookups succeed, the Gateway calls the member identified by `implementation ?? method` with parameters in descriptor order, followed by the carrier signal when the descriptor declares cancellation.
A `@RemoteContext('agent')` call first asks the Agent Context provider to resolve the wire identity, then reads the descriptor's service key from that Context and invokes the scoped receiver. The business method receives neither a hidden Context parameter nor an Agent ID.
A `@RemoteScope('agent')` call first asks the Agent Context provider to resolve the wire identity, then reads the descriptor's service key from that Context and invokes the scoped receiver. The business method receives neither a hidden Context parameter nor an Agent ID.
```text
ctx.typertGateway.invoke({ namespace, method, args, signal })
@@ -448,7 +448,7 @@ The Gateway registers only its ownership matcher and RPC handler with Connection
## Package boundaries
- `@deepseek-ai/dsh-type-meta`: lightweight protocols for decorators, bindings, lookup, Remote Context, and descriptors.
- `@deepseek-ai/dsh-type-meta`: lightweight protocols for decorators, bindings, lookup, Remote Scope, and descriptors.
- TypeRT generator: analyzes Host/Client Programs, generates local faces and Remote consumer projections, and emits canonical symbol/Zod information.
- TypeRT runtime: separately stores the current environment's local reflection and imported Remote contributions.
- `@deepseek-ai/dsh-api-gateway`: its default entry associates Host definitions with Services, claims Remote endpoints, performs lookup, resolves Context receivers, invokes methods, encodes results, and registers an `/api` interceptor with Connection; its `/client` entry mounts Remote contributions, creates strict Remote namespace Services and methods, and delegates calls to `ctx.connection.rpc`. The entries share the Remote protocol but do not import each other's Cordis interface merges.
@@ -460,7 +460,7 @@ The Gateway registers only its ownership matcher and RPC handler with Connection
## Shipped scope and deferred work
The shipped vertical path is `@deepseek-ai/dsh-goal/remote → Browser Client Remote → Connection RPC /api → Host Gateway → GoalService.remoteExportCreate()`. The same direct descriptor with an Agent lookup supports both `ctx.remote.goals.create(agentId, request)` and `agentCtx.remote.goals.create(request)`. Ordinary cold sessions are resumed through `agentFor()` during lookup, while subagent-owned identities retain the existing `agent-busy` fence; `@RemoteContext('agent')` remains the distinct scoped-receiver mode.
The shipped vertical path is `@deepseek-ai/dsh-goal/remote → Browser Client Remote → Connection RPC /api → Host Gateway → GoalService.remoteExportCreate()`. The same direct descriptor with an Agent lookup supports both `ctx.remote.goals.create(agentId, request)` and `agentCtx.remote.goals.create(request)`. Ordinary cold sessions are resumed through `agentFor()` during lookup, while subagent-owned identities retain the existing `agent-busy` fence; `@RemoteScope('agent')` remains the distinct scoped-receiver mode.
Connection supplies the shared-channel interceptor and current HTTP carrier mapping. WebSocket migration, the TUI runtime and carrier, TUI Agent Scope wiring, Permission/Approval state machines, Session event streams, call authorization, retries, idempotency, and cross-version protocol compatibility remain outside this decision.

View File

@@ -16,7 +16,7 @@ Host 与 Browser Client 使用独立的 TypeScript Program因为两边会以
## 决策
业务 Service 继承 `GatewayService`,并通过 `@Remote``@RemoteContext()` 声明可调用方法;已有其他基类的 Service 可以改用 `bindTypeRTGateway()` 暴露同一绑定。TypeRT 从 Host Program 生成 Host 本地反射产物和平台无关的 Remote 消费端投影Client Program 继续独立生成自己的本地反射产物。
业务 Service 继承 `GatewayService`,并通过 `@Remote``@RemoteScope()` 声明可调用方法;已有其他基类的 Service 可以改用 `bindTypeRTGateway()` 暴露同一绑定。TypeRT 从 Host Program 生成 Host 本地反射产物和平台无关的 Remote 消费端投影Client Program 继续独立生成自己的本地反射产物。
Remote 消费端投影同时包含 `.d.ts``.d.ts.map``.js``.d.ts` 只暴露被 Remote decorator 标记的方法,并引用业务包唯一的公共类型符号;`.d.ts.map` 把消费端 API 方法导航回 Host 业务方法实现;`.js` 携带同一契约的 endpoint、参数、Context 和 Zod 信息。Browser Client 在 assembly 层把需要的 Remote JS 贡献集中挂到 Client Remote Service该投影和 Remote 抽象保持平台无关,以便未来 TUI 复用。
@@ -64,7 +64,7 @@ export class GoalService extends GatewayService {
`goals` 是传给 `super()` 的明确 Cordis service key并默认作为 wire namespace。只有协议 namespace 确实需要与 service key 不同时,才通过第三个参数传入 `namespace` 选项。
需要在某类隔离 Context 中查找 Service receiver 时使用 `@RemoteContext()`。Context identity 不进入业务方法参数:
需要在某类隔离 Context 中查找 Service receiver 时使用 `@RemoteScope()`。Scope identity 不进入业务方法参数:
```text
export class ScopedGoalService extends GatewayService {
@@ -72,28 +72,28 @@ export class ScopedGoalService extends GatewayService {
super(ctx, 'goals')
}
@RemoteContext('agent', 'create')
@RemoteScope('agent', 'create')
remoteExportCreate(request: CreateGoalRequest): Promise<CreateGoalResult> {
// Runs against the goals service resolved from the Agent Context.
}
}
```
同一个 endpoint 只能选择一种调用模式。需要显式 `Agent` 参数的流程使用 `@Remote`;需要切换到 Agent Context 再解析 scoped receiver 的流程使用 `@RemoteContext('agent')`,两者不会由 TypeRT 根据方法体或参数缺失自动猜测。
同一个 endpoint 只能选择一种调用模式。需要显式 `Agent` 参数的流程使用 `@Remote`;需要切换到 Agent Context 再解析 scoped receiver 的流程使用 `@RemoteScope('agent')`,两者不会由 TypeRT 根据方法体或参数缺失自动猜测。
业务包只依赖轻量的 `@deepseek-ai/dsh-type-meta`。它提供 `GatewayService`,以及 decorator、binding 回退、lookup、Remote Context 和 descriptor 的声明协议,不依赖 TypeScript compiler、Zod、HTTP 或 Client runtime。
业务包只依赖轻量的 `@deepseek-ai/dsh-type-meta`。它提供 `GatewayService`,以及 decorator、binding 回退、lookup、Remote Scope 和 descriptor 的声明协议,不依赖 TypeScript compiler、Zod、HTTP 或 Client runtime。
支持协作式取消的方法会把 `signal: AbortSignal` 声明为最后一个 Host 参数。这个保留参数不是业务值、lookup 或 JSON 字段。生成的消费方方法将其暴露为最后一个可选参数,因此普通调用保持不变,而拥有取消控制权的调用方可以传入 signal。
## Decorator 与显式 Gateway facet
Decorator 只表达“该方法参与 Remote 契约”,不负责运行时类型反射,也不向 Service constructor 注入隐藏 symbol。`@Remote('create')``@RemoteContext('agent', 'create')` 的参数是外部方法名;被装饰成员既可以是业务方法本身,也可以是 `remoteExportCreate` 这样的适配器。未给别名时才使用成员名作为外部方法名。继承 `GatewayService` 是 Service 加入 Gateway 的常规显式声明;其 public readonly `typertGateway` 字段使运行时实例上的绑定保持可见。
Decorator 只表达“该方法参与 Remote 契约”,不负责运行时类型反射,也不向 Service constructor 注入隐藏 symbol。`@Remote('create')``@RemoteScope('agent', 'create')` 的参数是外部方法名;被装饰成员既可以是业务方法本身,也可以是 `remoteExportCreate` 这样的适配器。未给别名时才使用成员名作为外部方法名。继承 `GatewayService` 是 Service 加入 Gateway 的常规显式声明;其 public readonly `typertGateway` 字段使运行时实例上的绑定保持可见。
SRC 运行时允许 decorator 在 `dsh-type-meta` 内部的 `WeakMap` 记录 prototype、方法名和调用模式。它不向 Service 实例、prototype、constructor 或方法函数写入自定义属性。
LIB 的严格方法发现、类型解析和 descriptor 生成由 TypeRT compiler 完成。它接受 `GatewayService` 直接 `super()` 调用中的字面量 service key或显式 binding 回退;生成过程不改写业务源码,也不注入隐藏注册元数据。
## Lookup 与 Remote Context 注册
## Lookup 与 Remote Scope 注册
Gateway 不内置 Agent、Session 或其他业务对象分支。对象所属包同时提供静态声明和运行时 provider
@@ -115,7 +115,7 @@ ctx.typert.lookups.register('agent', {
Agent、Session 等 lookup 对象只能各自占据一个顶层参数位置。普通 JSON request 可以作为另一个完整参数传入,但本设计不支持 `request.agent`、对象解构、对象数组、嵌套 lookup 或从任意复杂结构中搜索 ID。
Remote Context 使用独立的 merge-extensible map 和 provider。Agent 包注册 `agent` Context provider负责用 wire identity 找到 Agent Context并从该 Context 解析 descriptor 指定的 service keyGateway 不知道 Agent Context 的内部结构。
Remote Scope 使用独立的 merge-extensible map 和 Context provider。Agent 包注册 `agent` provider负责用 wire identity 找到 Agent Context并从该 Context 解析 descriptor 指定的 service keyGateway 不知道 Agent Context 的内部结构。
Client 侧也注册 `agent` Context binder。binder 只负责从一次调用所在的 Context 取得 `SessionId`;它不枚举 Scope也不逐个复制方法。scoped namespace 由 Cordis Service tracker 自动 rebind 到当前 Agent Context。
@@ -267,7 +267,7 @@ interface TypeRTRemoteNamespaceMap {
goals: TypeRTRemoteNamespace$676f616c73
}
interface TypeRTRemoteContextMap {
interface TypeRTRemoteScopeMap {
'agent:goals/create': (
request: CreateGoalRequest,
signal?: AbortSignal,
@@ -277,14 +277,14 @@ interface TypeRTRemoteContextMap {
`TypeRTRemoteMap` 保留规范 endpoint 签名,供协议类型和反射使用。根 Remote 类型直接读取 `TypeRTRemoteNamespaceMap`,不通过 key-remapped mapped type 间接推导方法TypeScript Language Service 无法把这种间接属性稳定导航到 declaration map。namespace interface 名由 namespace 的 UTF-8 bytes 编成 hex`goals` 因而稳定得到 `TypeRTRemoteNamespace$676f616c73`。不同 package 对同一 namespace 生成同名 interface依靠 module augmentation 合并各自方法,且 `TypeRTRemoteNamespaceMap.goals` 始终引用同一类型。
TypeRT 把 `TypeRTRemoteContextMap` 按 Context key 投影到专用 Scope 类型。最终编程界面保持:
TypeRT 把 `TypeRTRemoteScopeMap` 按 Context key 投影到专用 Scope 类型。最终编程界面保持:
```text
ctx.remote.goals.create(agentId, request)
agentCtx.remote.goals.create(request)
```
Agent Scope 自动提供自己的 `SessionId`。因此带 `agent` lookup 的 `@Remote` 方法可以同时生成 root 和 scoped 两种消费端签名;`@RemoteContext('agent')` 方法也省略独立的 Context identity但只生成 scoped 签名。根 `Context` 通过 `ctx.remote` 暴露 direct namespace`AgentContext.remote` 则把该 direct surface 与 scoped surface 取交集。未来 TUI 复用时必须维持相同区分。
Agent Scope 自动提供自己的 `SessionId`。因此带 `agent` lookup 的 `@Remote` 方法可以同时生成 root 和 scoped 两种消费端签名;`@RemoteScope('agent')` 方法也省略独立的 Scope identity但只生成 scoped 签名。根 `Context` 通过 `ctx.remote` 暴露 direct namespace`AgentContext.remote` 则把该 direct surface 与 scoped surface 取交集。未来 TUI 复用时必须维持相同区分。
`TypeRTClientRemote` 保持平台无关Browser Client 通过 `ctx.remote` 暴露它。未来 TUI 若复用该类型,也必须通过专用 Remote 对象和 Agent Scope 使用它,不能把 Host `Context` 当成更宽的 Service 集合;未标记的 public Service 方法不会进入 Remote maps。
@@ -313,7 +313,7 @@ Client 业务包只引用 `@deepseek-ai/dsh-api-remotes/client`,不直接依
Client Remote Service 把 `@Remote` descriptor 实体化为 `remote.<namespace>` 子 Service 上的真实函数。函数按 descriptor 的位置参数顺序构造具名 `args`,执行 Client strict codec然后调用 `ctx.connection.rpc.call('/api', endpoint, { args }, signal)`。对于支持取消的 descriptor生成的函数接受最后一个可选 signal并将其与 contribution 的挂载生命周期合并;因此卸载会取消所有正在进行的 carrier 调用,而调用方也可以单独取消一次调用。
`scope` 的 direct descriptor 和 `@RemoteContext` descriptor 都不为每个 Agent Scope 复制函数。Client Remote Service 为每个 namespace 创建一个注册为 `remote.<namespace>` 的 Cordis 子 Service并在其上实体化 direct 与 scoped 变体。通过 `agentCtx.remote.goals` 取得方法时accessor 会在返回可调用句柄前捕获当前 Agent Context。方法再通过对应 Context binder 从该 Context 取得 identity。direct scoped 投影用 identity 替代 `scope.wire` 指定的 lookup 位置,Context descriptor 则把 identity 写入 receiver 的独立 wire 字段;两者都发起同一种 `/api` 调用。
`scope` 的 direct descriptor 和 `@RemoteScope` descriptor 都不为每个 Agent Scope 复制函数。Client Remote Service 为每个 namespace 创建一个注册为 `remote.<namespace>` 的 Cordis 子 Service并在其上实体化 direct 与 scoped 变体。通过 `agentCtx.remote.goals` 取得方法时accessor 会在返回可调用句柄前捕获当前 Agent Context。方法再通过对应 Context binder 从该 Context 取得 identity。direct scoped 投影用 identity 替代 `scope.wire` 指定的 lookup 位置,Remote Scope descriptor 则把 identity 写入 receiver 的独立 wire 字段;两者都发起同一种 `/api` 调用。
```text
root ctx.remote.goals.create(agentId, request)
@@ -327,7 +327,7 @@ agentCtx.remote.goals.create(request)
→ ctx.connection.rpc.call('/api', 'goals/create', { args })
```
`Context` 只 merge direct `TypeRTClientRemote` surface`AgentContext` 把该属性替换为 `TypeRTClientRemote``TypeRTRemoteContextApi<'agent'>` 的交叉,因而 scoped-only 方法不会暴露给 root 代码。若调用方绕过类型从 Root 动态调用 scoped-only 方法binder 明确报错。若 Client 已有名为 `remote.<namespace>` 的 Cordis service或两个 contribution 冲突占用同一 namespace/methodmount 直接失败,不覆盖现有服务。
`Context` 只 merge direct `TypeRTClientRemote` surface`AgentContext` 把该属性替换为 `TypeRTClientRemote``TypeRTRemoteScopeApi<'agent'>` 的交叉,因而 scoped-only 方法不会暴露给 root 代码。若调用方绕过类型从 Root 动态调用 scoped-only 方法binder 明确报错。若 Client 已有名为 `remote.<namespace>` 的 Cordis service或两个 contribution 冲突占用同一 namespace/methodmount 直接失败,不覆盖现有服务。
生成的 Remote JS 只包含 descriptor、symbol key 和 codec不打包 Host Service 实现。Client Remote Service 据此创建真实函数,因此运行时不依赖 JavaScript ProxyProxy 可以作为实现选择,但不会成为类型或反射来源。
@@ -337,7 +337,7 @@ Remote API 是消费端能力,不等同于 Browser API。已交付的运行时
Remote DTS、Remote JS、`TypeRTClientRemote``InvocationDescriptor`、Remote RPC 数据协议和 Context binder 不得依赖 DOM、Browser module loader 或 HTTP。Browser Client 通过 Connection 把 descriptor 实体化的方法编码为 `/api` RPC 调用。
未来 TUI 可以在不改变业务 decorator、Remote maps 和 API 调用形状的前提下接入同一调用抽象。届时 TUI 可见的 API 仍只能由 `@Remote``@RemoteContext` 生成,不能因为它与 Host 同进程就绕过 Remote 限制直接暴露 Service 方法。
未来 TUI 可以在不改变业务 decorator、Remote maps 和 API 调用形状的前提下接入同一调用抽象。届时 TUI 可见的 API 仍只能由 `@Remote``@RemoteScope` 生成,不能因为它与 Host 同进程就绕过 Remote 限制直接暴露 Service 方法。
TUI 的 runtime 挂载、carrier、Agent Scope 关联和 SRC 启动接线均不属于本期实现。
@@ -345,7 +345,7 @@ Web 本身依赖 `lib/client.js` 等构建产物,因此启动 Web 前要求完
## SRC 与 LIB 运行模式
SRC 面向本地源码启动。`@Remote``@RemoteContext()` 的 WeakMap 记录给出方法名和调用模式,运行时从 JavaScript 函数签名读取顺序参数名,并结合已注册 lookup/Context provider 生成弱 descriptor。
SRC 面向本地源码启动。`@Remote``@RemoteScope()` 的 WeakMap 记录给出方法名和调用模式,运行时从 JavaScript 函数签名读取顺序参数名,并结合已注册 lookup/Context provider 生成弱 descriptor。
例如 `@Remote('create') remoteExportCreate(agent, request, signal)` 解析为外部方法 `create`、实现成员 `remoteExportCreate`、两个顶层业务参数和一个取消注入点lookup 注册把 `agent` 改写为 wire 字段 `agentId``request` 按同名 JSON 参数传递,最后一个 `signal` 则留在 payload 之外。SRC 不启动 `ts.Program`,不使用 preload、loader hook、源码生成或模块改写也不检查普通 JSON 对象的内部结构。
@@ -365,7 +365,7 @@ Host Gateway 向 Connection 注册一个 `/api` interceptor不维护第二份
普通 `@Remote` 调用保留原始 Service 实例作为 receiver。lookup 成功后Gateway 按 descriptor 的参数顺序调用 `implementation ?? method` 指定的成员;若 descriptor 声明取消,则在这些参数之后追加 carrier signal。
`@RemoteContext('agent')` 调用先由 Agent Context provider 解析 wire identity再从该 Context 读取 descriptor 的 service key 并调用 scoped receiver。业务方法不会收到隐藏 Context 参数或 Agent ID。
`@RemoteScope('agent')` 调用先由 Agent Context provider 解析 wire identity再从该 Context 读取 descriptor 的 service key 并调用 scoped receiver。业务方法不会收到隐藏 Context 参数或 Agent ID。
```text
ctx.typertGateway.invoke({ namespace, method, args, signal })
@@ -448,7 +448,7 @@ Gateway 只向 Connection 注册 ownership matcher 和 RPC handler不注册 H
## 包边界
- `@deepseek-ai/dsh-type-meta`:轻量 decorator、binding、lookup、Remote Context 和 descriptor 协议。
- `@deepseek-ai/dsh-type-meta`:轻量 decorator、binding、lookup、Remote Scope 和 descriptor 协议。
- TypeRT generator分析 Host/Client Program生成本地 face 和 Remote 消费端投影,并生成规范 symbol/Zod 信息。
- TypeRT runtime分别保存当前环境的 local reflection 与导入的 Remote contribution。
- `@deepseek-ai/dsh-api-gateway`:默认入口关联 Host definition 与 Service认领 Remote endpoint执行 lookup、Context receiver 解析、调用和结果编码,并向 Connection 注册 `/api` interceptor`/client` 入口挂载 Remote contribution创建严格 Remote namespace Service 和方法,并把调用交给 `ctx.connection.rpc`。两个入口共享 Remote 协议,但不互相导入各自的 Cordis interface merge。
@@ -460,7 +460,7 @@ Gateway 只向 Connection 注册 ownership matcher 和 RPC handler不注册 H
## 已交付范围与后续工作
已交付的纵向链路是 `@deepseek-ai/dsh-goal/remote → Browser Client Remote → Connection RPC /api → Host Gateway → GoalService.remoteExportCreate()`。同一个带 Agent lookup 的 direct descriptor 同时支持 `ctx.remote.goals.create(agentId, request)``agentCtx.remote.goals.create(request)`。普通冷会话在 lookup 时通过 `agentFor()` 恢复subagent-owned identity 保持既有 `agent-busy` fence`@RemoteContext('agent')` 仍是独立的 scoped receiver 模式。
已交付的纵向链路是 `@deepseek-ai/dsh-goal/remote → Browser Client Remote → Connection RPC /api → Host Gateway → GoalService.remoteExportCreate()`。同一个带 Agent lookup 的 direct descriptor 同时支持 `ctx.remote.goals.create(agentId, request)``agentCtx.remote.goals.create(request)`。普通冷会话在 lookup 时通过 `agentFor()` 恢复subagent-owned identity 保持既有 `agent-busy` fence`@RemoteScope('agent')` 仍是独立的 scoped receiver 模式。
Connection 提供共享 channel interceptor 与当前 HTTP carrier 映射。WebSocket 迁移、TUI runtime 与 carrier、TUI Agent Scope 接线、Permission/Approval 状态机、Session 事件流、调用授权、重试、幂等及跨版本协议兼容均不属于本决策。