diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml index 341bf44923..71ded0fa8d 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .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 diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md index a8254090e0..215c647bcd 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md @@ -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 { // 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.` 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.`, 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.`, 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.`, 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.`, 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. diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md index f1b7e5f9c6..0ce431b7cb 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md @@ -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 { // 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 key;Gateway 不知道 Agent Context 的内部结构。 +Remote Scope 使用独立的 merge-extensible map 和 Context provider。Agent 包注册 `agent` provider,负责用 wire identity 找到 Agent Context,并从该 Context 解析 descriptor 指定的 service key;Gateway 不知道 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.` 子 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.` 的 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.` 的 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.` 的 Cordis service,或两个 contribution 冲突占用同一 namespace/method,mount 直接失败,不覆盖现有服务。 +根 `Context` 只 merge direct `TypeRTClientRemote` surface;`AgentContext` 把该属性替换为 `TypeRTClientRemote` 与 `TypeRTRemoteScopeApi<'agent'>` 的交叉,因而 scoped-only 方法不会暴露给 root 代码。若调用方绕过类型从 Root 动态调用 scoped-only 方法,binder 明确报错。若 Client 已有名为 `remote.` 的 Cordis service,或两个 contribution 冲突占用同一 namespace/method,mount 直接失败,不覆盖现有服务。 生成的 Remote JS 只包含 descriptor、symbol key 和 codec,不打包 Host Service 实现。Client Remote Service 据此创建真实函数,因此运行时不依赖 JavaScript Proxy;Proxy 可以作为实现选择,但不会成为类型或反射来源。 @@ -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 事件流、调用授权、重试、幂等及跨版本协议兼容均不属于本决策。 diff --git a/docs/api-gateway.i18n.yaml b/docs/api-gateway.i18n.yaml index d07272c182..2a6ae0807b 100644 --- a/docs/api-gateway.i18n.yaml +++ b/docs/api-gateway.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/api-gateway.md -api-gateway.md: 90aa661cc86a4f419e173560c55511c969182990 -api-gateway.zh.md: 6fcbb562b204e71d00833042ee0632bda0217940 +api-gateway.md: ba95d429dd0c9f9f354baf0063197cea6e3ecbf8 +api-gateway.zh.md: 4e42ebea7a5db19c7df23079050b9488679a3a23 diff --git a/docs/api-gateway.md b/docs/api-gateway.md index 90aa661cc8..ba95d429dd 100644 --- a/docs/api-gateway.md +++ b/docs/api-gateway.md @@ -6,17 +6,17 @@ This is the current-state reference for the TypeRT API Gateway. It describes how ## Programming model -Business services use `@Remote` or `@RemoteContext` to select the methods exposed to the Client. Unmarked methods do not enter the generated Client types or runtime contributions and cannot be called through `ctx.remote`. +Business services use `@Remote` or `@RemoteScope` to select the methods exposed to the Client. Unmarked methods do not enter the generated Client types or runtime contributions and cannot be called through `ctx.remote`. `@Remote` denotes calling a Cordis service registered on the root Host Context. Complex Host objects cannot cross the wire directly; the business package must declare their association with a wire identity through `TypeRTLookupMap` and register a default resolution provider with `ctx.typert.lookups` at runtime. For example, an `Agent` parameter named `agent` in the Host signature produces an `agentId` wire field, and the Gateway resolves that id to a Host object before invoking the business method. Host composition can use `ctx.typert.lookups.configure()` to override the resolution policy for a lookup key without changing the parameter name, wire field, or canonical type symbol owned by the business package. -`@RemoteContext(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. +`@RemoteScope(key)` first resolves an identity to a scoped Context through `ctx.typert.contexts`, then obtains the service from that Context and invokes the method. It applies when the method itself depends on scoped composition and does not need to receive objects such as `Agent` explicitly. Services normally extend `GatewayService` so the constructor explicitly binds the Cordis service key and default Remote namespace. A service that already has another base class can instead declare `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`; both forms leave an inspectable public binding and do not depend on the compiler injecting a symbol into the constructor. ```ts import type { Agent } from '@deepseek-ai/dsh-agent' -import { GatewayService, Remote, RemoteContext } from '@deepseek-ai/dsh-type-meta' +import { GatewayService, Remote, RemoteScope } from '@deepseek-ai/dsh-type-meta' import type { Context } from 'cordis' export interface CreateGoalRequest { @@ -42,7 +42,7 @@ export class GoalService extends GatewayService { return this.create(agent, request) } - @RemoteContext('agent', 'current') + @RemoteScope('agent', 'current') currentForClient(): CreateGoalResult { return { accepted: true } } @@ -55,7 +55,7 @@ export class GoalService extends GatewayService { Remote methods may return a value synchronously or return a Promise. For cooperative cancellation, the final parameter in the Host signature must be `signal: AbortSignal` using the global type; it is recorded in the descriptor instead of entering `args`, while the generated Client method accepts an optional final `AbortSignal`. -The Client uses concrete functions on ordinary objects, not a JavaScript Proxy. Direct and scoped calls appear under `ctx.remote.` and `agentCtx.remote.`. Each namespace is a traced Cordis child Service registered as `remote.`; the Client assembly mounts contributions through `ctx.remote.$mount()`, consumers inject both `remote` and the namespace Service they call, and the namespace unloads after its last method is withdrawn. When an `@Remote` method has exactly one lookup parameter and a same-named `TypeRTContextMap` uses the same wire identity, the generated scoped signature omits that identity parameter. `@RemoteContext` generates only the scoped invocation interface. +The Client uses concrete functions on ordinary objects, not a JavaScript Proxy. Direct and scoped calls appear under `ctx.remote.` and `agentCtx.remote.`. Each namespace is a traced Cordis child Service registered as `remote.`; the Client assembly mounts contributions through `ctx.remote.$mount()`, consumers inject both `remote` and the namespace Service they call, and the namespace unloads after its last method is withdrawn. When an `@Remote` method has exactly one lookup parameter and a same-named `TypeRTContextMap` uses the same wire identity, the generated scoped signature omits that identity parameter. `@RemoteScope` generates only the scoped invocation interface. ```ts import type { SessionId } from '@deepseek-ai/dsh-session/types' @@ -101,7 +101,7 @@ Each contributing business package writes generated files to its own `lib/` dire | `typert.host.js` | Host Loader | Runtime reflection for the Host face, strict invocation descriptors, and schema registration values | | `typert.host.d.ts` | Host type system | Generated declarations for the Host face | | `typert.remote-client.js` | `api-remotes` | A mountable `TypeRTRemoteContribution` containing strict descriptors and runtime codecs | -| `typert.remote-client.d.ts` | Client type system | Declaration merges for `TypeRTRemoteNamespaceMap` and `TypeRTRemoteContextMap`, plus Client-safe type references | +| `typert.remote-client.d.ts` | Client type system | Declaration merges for `TypeRTRemoteNamespaceMap` and `TypeRTRemoteScopeMap`, plus Client-safe type references | | `typert.remote-client.d.ts.map` | Editor | Maps generated method properties back to Remote method declarations in the Host package | Business packages expose the Host Loader entry through `./typert` and the Host-for-Client entry through `./remote`. The generator also validates these package exports and published-file lists; it generates artifacts only for explicit contribution packages that provide the corresponding entry. @@ -126,7 +126,7 @@ Unloading a Client contribution removes its descriptors and concrete methods tog When the Host starts from source through `node --import tsx/esm`, it does not execute the TypeRT compiler plugin. Standard decorator initializers still record the method name and invocation mode in a module-private `WeakMap`, while `GatewayService` or `bindTypeRTGateway()` supplies the explicit service binding; the Gateway can therefore construct a weaker temporary descriptor without starting a `ts.Program`. -The SRC fallback parses simple parameter names from the live function. When a parameter name matches the `parameter` of a registered lookup, such as `agent` or `session`, it uses the lookup's `agentId` or `sessionId` wire field and resolves the object on the Host; other parameters are checked only for cycle-free, JSON-safe data with no special prototype. `@RemoteContext` directly uses the wire field of a registered Host Context provider. SRC does not read TypeScript types, generate Zod schemas, infer optional parameters, or support destructuring, default values, rest parameters, or duplicate parameter names. +The SRC fallback parses simple parameter names from the live function. When a parameter name matches the `parameter` of a registered lookup, such as `agent` or `session`, it uses the lookup's `agentId` or `sessionId` wire field and resolves the object on the Host; other parameters are checked only for cycle-free, JSON-safe data with no special prototype. `@RemoteScope` directly uses the wire field of a registered Host Context provider. SRC does not read TypeScript types, generate Zod schemas, infer optional parameters, or support destructuring, default values, rest parameters, or duplicate parameter names. SRC solves only dispatch for a Host process running from source. The Client does not discover decorators from the running Host, and the Client Remote refuses to mount SRC descriptors that lack strict codecs; its types, codecs, and Remote registration values always come from the most recently generated `lib/typert.remote-client.*` artifacts. diff --git a/docs/api-gateway.zh.md b/docs/api-gateway.zh.md index 6fcbb562b2..4e42ebea7a 100644 --- a/docs/api-gateway.zh.md +++ b/docs/api-gateway.zh.md @@ -6,17 +6,17 @@ ## 编程模型 -业务 Service 通过 `@Remote` 或 `@RemoteContext` 选择对 Client 开放的方法。未标记的方法不会进入生成的 Client 类型或运行时贡献,也不能通过 `ctx.remote` 调用。 +业务 Service 通过 `@Remote` 或 `@RemoteScope` 选择对 Client 开放的方法。未标记的方法不会进入生成的 Client 类型或运行时贡献,也不能通过 `ctx.remote` 调用。 `@Remote` 表示调用根 Host Context 中注册的 Cordis Service。复杂的 Host 对象不能直接跨 wire 传输;业务包必须通过 `TypeRTLookupMap` 声明它与 wire identity 的关联,并在运行时向 `ctx.typert.lookups` 注册默认解析提供方。例如 `Agent` 参数在 Host 签名中名为 `agent`,生成的 wire 字段为 `agentId`,Gateway 在调用业务方法前将 id 解析为 Host 对象。Host 组合可以用 `ctx.typert.lookups.configure()` 覆盖某个 lookup key 的解析策略,而不改变业务包拥有的参数名、wire 字段或规范类型 symbol。 -`@RemoteContext(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context,再从该 Context 取得 Service 并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。 +`@RemoteScope(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context,再从该 Context 取得 Service 并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。 Service 通常继承 `GatewayService`,让 Cordis service key 与默认 Remote namespace 在构造器中显式绑定。已有其他基类的 Service 可以改为声明 `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`;两种方式都会留下可检查的公开 binding,不依赖编译器向构造函数注入 symbol。 ```ts import type { Agent } from '@deepseek-ai/dsh-agent' -import { GatewayService, Remote, RemoteContext } from '@deepseek-ai/dsh-type-meta' +import { GatewayService, Remote, RemoteScope } from '@deepseek-ai/dsh-type-meta' import type { Context } from 'cordis' export interface CreateGoalRequest { @@ -42,7 +42,7 @@ export class GoalService extends GatewayService { return this.create(agent, request) } - @RemoteContext('agent', 'current') + @RemoteScope('agent', 'current') currentForClient(): CreateGoalResult { return { accepted: true } } @@ -55,7 +55,7 @@ export class GoalService extends GatewayService { Remote 方法可以同步返回或返回 Promise。若需要协作式取消,Host 签名的最后一个参数必须是全局类型的 `signal: AbortSignal`;它记录在描述符中而不是进入 `args`,Client 生成的方法则接受最后一个可选的 `AbortSignal`。 -Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直接调用与作用域调用分别出现在 `ctx.remote.` 和 `agentCtx.remote.`。每个 namespace 都是注册为 `remote.` 的可追踪 Cordis 子 Service;Client assembly 通过 `ctx.remote.$mount()` 挂载贡献,消费方同时注入 `remote` 与所调用的 namespace Service,最后一个方法撤回后该 namespace 随即卸载。当一个 `@Remote` 方法恰好有一个 lookup 参数、且同名 `TypeRTContextMap` 使用相同 wire identity 时,生成的作用域签名会省略该 identity 参数。`@RemoteContext` 只生成作用域调用界面。 +Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直接调用与作用域调用分别出现在 `ctx.remote.` 和 `agentCtx.remote.`。每个 namespace 都是注册为 `remote.` 的可追踪 Cordis 子 Service;Client assembly 通过 `ctx.remote.$mount()` 挂载贡献,消费方同时注入 `remote` 与所调用的 namespace Service,最后一个方法撤回后该 namespace 随即卸载。当一个 `@Remote` 方法恰好有一个 lookup 参数、且同名 `TypeRTContextMap` 使用相同 wire identity 时,生成的作用域签名会省略该 identity 参数。`@RemoteScope` 只生成作用域调用界面。 ```ts import type { SessionId } from '@deepseek-ai/dsh-session/types' @@ -101,7 +101,7 @@ API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对 | `typert.host.js` | Host Loader | Host face 的运行时反射、严格调用描述符和 schema 注册值 | | `typert.host.d.ts` | Host 类型系统 | Host face 的生成声明 | | `typert.remote-client.js` | `api-remotes` | 可挂载的 `TypeRTRemoteContribution`,包含严格描述符与运行时 codec | -| `typert.remote-client.d.ts` | Client 类型系统 | `TypeRTRemoteNamespaceMap` 与 `TypeRTRemoteContextMap` 的声明合并及 Client-safe 类型引用 | +| `typert.remote-client.d.ts` | Client 类型系统 | `TypeRTRemoteNamespaceMap` 与 `TypeRTRemoteScopeMap` 的声明合并及 Client-safe 类型引用 | | `typert.remote-client.d.ts.map` | 编辑器 | 将生成的方法属性映射回 Host 包中的 Remote 方法声明 | 业务包通过 `./typert` 暴露 Host Loader 入口,通过 `./remote` 暴露 Host-for-Client 入口。生成器同时校验这些 package export 及发布文件清单;只有具备相应入口的显式贡献包才会生成产物。 @@ -126,7 +126,7 @@ Client 卸载一个贡献时会一起移除描述符和具体方法,中止其 Host 通过 `node --import tsx/esm` 从源码启动时不会执行 TypeRT 编译插件。标准 decorator 初始化器仍会把方法名和调用模式记录到模块私有 `WeakMap`,`GatewayService` 或 `bindTypeRTGateway()` 则提供显式 service binding;Gateway 因而可以在不启动 `ts.Program` 的情况下构造一个较弱的临时描述符。 -SRC 回退从运行中函数解析简单参数名。参数名与某个已注册 lookup 的 `parameter` 相同,例如 `agent` 或 `session`,就使用其 `agentId` 或 `sessionId` wire 字段并在 Host 解析对象;其他参数只检查值是否为无循环、无特殊 prototype 的 JSON-safe 数据。`@RemoteContext` 直接使用已注册 Host Context provider 的 wire 字段。SRC 不读取 TypeScript 类型,不生成 Zod schema,不推断可选参数,也不支持解构、默认值、rest 或重复参数名。 +SRC 回退从运行中函数解析简单参数名。参数名与某个已注册 lookup 的 `parameter` 相同,例如 `agent` 或 `session`,就使用其 `agentId` 或 `sessionId` wire 字段并在 Host 解析对象;其他参数只检查值是否为无循环、无特殊 prototype 的 JSON-safe 数据。`@RemoteScope` 直接使用已注册 Host Context provider 的 wire 字段。SRC 不读取 TypeScript 类型,不生成 Zod schema,不推断可选参数,也不支持解构、默认值、rest 或重复参数名。 SRC 只解决 Host 源码进程的分发问题。Client 不会从运行中的 Host 发现 decorator,Client Remote 也拒绝挂载缺少严格 codec 的 SRC 描述符;其类型、codec 和 Remote 注册值始终来自最近一次生成的 `lib/typert.remote-client.*`。 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 933f204fa0..b66552b175 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/development.md -development.md: 37bc88c7c1cfedfbe1a93e08a4cbde833ac32372 -development.zh.md: a738e53cb3434d7930aa82107782a4c22aea1470 +development.md: b7ecab3536d739c105f11a640a07ea83a22f4398 +development.zh.md: 33ceba9f05c45c06acae7c83425a30c5e26ca433 diff --git a/docs/development.md b/docs/development.md index 37bc88c7c1..b7ecab3536 100644 --- a/docs/development.md +++ b/docs/development.md @@ -62,7 +62,7 @@ Host and client stay two aggregate programs because both sides declaration-merge Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Decision record: [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md); the tsc-first emit pipeline is the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md). -Business services declare callable methods on the Host with `@Remote` or `@RemoteContext`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order. +Business services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order. If a relevant local check consumes built package output, build once first: diff --git a/docs/development.zh.md b/docs/development.zh.md index a738e53cb3..33ceba9f05 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -62,7 +62,7 @@ host 与 client 保持两个聚合 program,是因为两侧在相同键下以 静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。决策记录:[solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md);tsc-first 发射管线见 [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md)。 -业务 Service 在 Host 使用 `@Remote` 或 `@RemoteContext` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。 +业务 Service 在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。 如果相关的本地检查需要使用构建后的包产物,请先构建一次: diff --git a/packages/api/gateway/README.i18n.yaml b/packages/api/gateway/README.i18n.yaml index 3a9a0ba50d..3f4cd32e4d 100644 --- a/packages/api/gateway/README.i18n.yaml +++ b/packages/api/gateway/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/api/gateway/README.md -README.md: e37359db71c1388667e9e61f538354711e90c0c1 -README.zh.md: 2054febb9a5423297c32b029b40a035062250aab +README.md: 0e1a03d2016b8cfbe165dbf1b0a9802290b29502 +README.zh.md: 6b5ccff2340405cc0045147239c5bd4f3eead7da diff --git a/packages/api/gateway/README.md b/packages/api/gateway/README.md index e37359db71..0e1a03d201 100644 --- a/packages/api/gateway/README.md +++ b/packages/api/gateway/README.md @@ -6,9 +6,9 @@ Two-sided TypeRT RPC endpoint for Host and Client Cordis environments. The Host ## Host service: `TypertGatewayService` (ctx key: `typertGateway`) -`ctx.typertGateway.invoke()` resolves the current descriptor and Cordis Service for each call, validates exact named arguments, resolves registered object or Context identities, invokes the public business method, and validates its result. Business Services extend `GatewayService` and mark methods with `@Remote` or `@RemoteContext` from [`dsh-type-meta`](../../typert/type-meta/README.md); `bindTypeRTGateway()` remains available when another base class owns inheritance. +`ctx.typertGateway.invoke()` resolves the current descriptor and Cordis Service for each call, validates exact named arguments, resolves registered object or Context identities, invokes the public business method, and validates its result. Business Services extend `GatewayService` and mark methods with `@Remote` or `@RemoteScope` from [`dsh-type-meta`](../../typert/type-meta/README.md); `bindTypeRTGateway()` remains available when another base class owns inheritance. -Strict mode reads generated invocation descriptors from `ctx.typert.local`. Lookup parameters use the currently active resolver in `ctx.typert.lookups`: the business package registers the stable declaration and default policy, while Host composition can override resolution behavior with effect-scoped `configure()`; `@RemoteContext` resolves its receiver through a registered Host Context provider. SRC mode is a development fallback for endpoints that have never had a strict definition; it parses simple parameter names and accepts only JSON-safe values for non-lookup parameters. Withdrawing an observed strict definition fails instead of weakening validation. +Strict mode reads generated invocation descriptors from `ctx.typert.local`. Lookup parameters use the currently active resolver in `ctx.typert.lookups`: the business package registers the stable declaration and default policy, while Host composition can override resolution behavior with effect-scoped `configure()`; `@RemoteScope` resolves its receiver through a registered Host Context provider. SRC mode is a development fallback for endpoints that have never had a strict definition; it parses simple parameter names and accepts only JSON-safe values for non-lookup parameters. Withdrawing an observed strict definition fails instead of weakening validation. The Host entry registers a trusted-host interceptor on Connection's shared `/api` FetchHandler. Connection passes this composite handler through its HTTP bridge; the handler dispatches claimed endpoints to Gateway and unclaimed endpoints to API Proxy. Direct `invoke()` calls preserve business errors; `TypertGatewayError` distinguishes failures owned by dispatch, binding, providers, lookup, Context, arguments, and codecs. A resolver may use `TypeRTLookupFailure` to carry an existing RPC error, preserving its original error code for policy rejections such as cold-resume failures or ownership fences. diff --git a/packages/api/gateway/README.zh.md b/packages/api/gateway/README.zh.md index 2054febb9a..6b5ccff234 100644 --- a/packages/api/gateway/README.zh.md +++ b/packages/api/gateway/README.zh.md @@ -6,9 +6,9 @@ ## Host 服务:`TypertGatewayService`(ctx key:`typertGateway`) -每次调用时,`ctx.typertGateway.invoke()` 都会解析当前的描述符和 Cordis 服务,校验具名参数是否完全匹配,解析已注册的对象或 Context 身份标识,调用公开的业务方法,并校验其结果。业务服务继承 [`dsh-type-meta`](../../typert/type-meta/README.md) 的 `GatewayService`,并用 `@Remote` 或 `@RemoteContext` 标记方法;已有其他基类时仍可改用 `bindTypeRTGateway()`。 +每次调用时,`ctx.typertGateway.invoke()` 都会解析当前的描述符和 Cordis 服务,校验具名参数是否完全匹配,解析已注册的对象或 Context 身份标识,调用公开的业务方法,并校验其结果。业务服务继承 [`dsh-type-meta`](../../typert/type-meta/README.md) 的 `GatewayService`,并用 `@Remote` 或 `@RemoteScope` 标记方法;已有其他基类时仍可改用 `bindTypeRTGateway()`。 -严格模式从 `ctx.typert.local` 读取生成的调用描述符。查找参数使用 `ctx.typert.lookups` 中当前有效的 resolver:业务包注册稳定声明与默认策略,Host 组合可用 effect-scoped `configure()` 覆盖解析行为;`@RemoteContext` 则通过已注册的 Host Context 提供方解析其接收者。SRC 模式是开发阶段的回退路径,适用于从未具备严格定义的端点;它解析简单参数名,并且只允许非查找参数使用可安全表示为 JSON 的值。已观测到的严格定义一旦撤回,系统会直接报错,而不会降低校验强度。 +严格模式从 `ctx.typert.local` 读取生成的调用描述符。查找参数使用 `ctx.typert.lookups` 中当前有效的 resolver:业务包注册稳定声明与默认策略,Host 组合可用 effect-scoped `configure()` 覆盖解析行为;`@RemoteScope` 则通过已注册的 Host Context 提供方解析其接收者。SRC 模式是开发阶段的回退路径,适用于从未具备严格定义的端点;它解析简单参数名,并且只允许非查找参数使用可安全表示为 JSON 的值。已观测到的严格定义一旦撤回,系统会直接报错,而不会降低校验强度。 Connection 可用时,Host 入口会在 Connection 共享的 `/api` FetchHandler 上注册 trusted-host interceptor。Connection 把这个复合 handler 交给 HTTP bridge;handler 将已认领 endpoint 分发给 Gateway,未认领 endpoint 则交给 API Proxy。直接调用 `invoke()` 会保留业务错误;`TypertGatewayError` 可区分分发、绑定、提供方、查找、Context、参数和编解码器各自负责的故障。resolver 可以用 `TypeRTLookupFailure` 携带既有 RPC error,使冷恢复失败或 ownership fence 等策略拒绝保持原错误码。 diff --git a/packages/api/gateway/tests/client.spec.ts b/packages/api/gateway/tests/client.spec.ts index 216f2359e7..1383175e73 100644 --- a/packages/api/gateway/tests/client.spec.ts +++ b/packages/api/gateway/tests/client.spec.ts @@ -5,7 +5,7 @@ import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client import type { InvocationDescriptor, TypeRTContext, - TypeRTRemoteContextApi, + TypeRTRemoteScopeApi, TypeRTRemoteNamespace, } from '@deepseek-ai/dsh-type-meta' import TypertRegistry from '@deepseek-ai/dsh-typert-registry' @@ -24,7 +24,7 @@ declare module '@deepseek-ai/dsh-type-meta' { ) => Promise<{ readonly ref: string }> } - interface TypeRTRemoteContextMap { + interface TypeRTRemoteScopeMap { 'fixture:goals/create': ( request: { readonly objective: string }, signal?: AbortSignal, @@ -38,7 +38,7 @@ declare module '@deepseek-ai/dsh-type-meta' { } -type FixtureContext = Omit & { readonly remote: TypeRTRemoteContextApi<'fixture'> } +type FixtureContext = Omit & { readonly remote: TypeRTRemoteScopeApi<'fixture'> } const idSchema = z.string().min(1) const requestSchema = z.object({ objective: z.string().min(1) }) diff --git a/packages/api/gateway/tests/gateway.spec.ts b/packages/api/gateway/tests/gateway.spec.ts index d298116b82..4fa0ea80ad 100644 --- a/packages/api/gateway/tests/gateway.spec.ts +++ b/packages/api/gateway/tests/gateway.spec.ts @@ -8,7 +8,7 @@ import type { HttpServerService, WebRoute } from '@deepseek-ai/dsh-host-webserve import { bindTypeRTGateway, Remote, - RemoteContext, + RemoteScope, TypeRTLookupFailure, type InvocationDescriptor, type TypeRTContext, @@ -65,7 +65,7 @@ class GoalService extends Service { } } - @RemoteContext('gatewayFixture') + @RemoteScope('gatewayFixture') rename(request: { readonly title: string }): unknown { this.calls.push('rename') return { title: request.title, scope: (this.ctx as MarkedContext).fixtureScope ?? 'root' } @@ -299,7 +299,7 @@ class ContextWireService extends Service { super(ctx, 'contextWire') } - @RemoteContext('gatewayFixture') + @RemoteScope('gatewayFixture') run(agentId: string): string { return agentId } @@ -389,7 +389,7 @@ describe('TypertGatewayService', () => { expect(service.lastSignal?.aborted).toBe(false) }) - it('resolves strict Remote Context identity without adding a business argument', async () => { + it('resolves strict Remote Scope identity without adding a business argument', async () => { const { ctx, service } = await setup() const scoped = ctx.extend({ fixtureScope: 'agent-scope' }) ctx.typert.contexts.registerHost('gatewayFixture', contextProvider(scoped)) @@ -432,7 +432,7 @@ describe('TypertGatewayService', () => { expect(service.calls).toEqual([]) }) - it('derives SRC Remote Context identity and preserves the scoped Proxy receiver', async () => { + it('derives SRC Remote Scope identity and preserves the scoped Proxy receiver', async () => { const { ctx } = await setup() const scoped = ctx.extend({ fixtureScope: 'agent-src' }) ctx.typert.contexts.registerHost('gatewayFixture', contextProvider(scoped)) diff --git a/packages/client/runtime/src/client/agents/scope.ts b/packages/client/runtime/src/client/agents/scope.ts index 1154d10feb..25644d24ba 100644 --- a/packages/client/runtime/src/client/agents/scope.ts +++ b/packages/client/runtime/src/client/agents/scope.ts @@ -18,11 +18,11 @@ import { Context as CordisContext } from 'cordis' import type { Context, Fiber } from 'cordis' import type { SessionId } from '@deepseek-ai/dsh-client-connection/client' -import type { TypeRTClientRemote, TypeRTRemoteContextApi } from '@deepseek-ai/dsh-type-meta' +import type { TypeRTClientRemote, TypeRTRemoteScopeApi } from '@deepseek-ai/dsh-type-meta' /** Client Cordis Context carrying one Agent identity and its scoped Remote namespaces. */ export type AgentContext = Omit & { - readonly remote: TypeRTClientRemote & TypeRTRemoteContextApi<'agent'> + readonly remote: TypeRTClientRemote & TypeRTRemoteScopeApi<'agent'> } /** Context tag written by {@link createScope}. */ diff --git a/packages/typert/generator/src/analyzer.ts b/packages/typert/generator/src/analyzer.ts index bc5024a7d8..c5d89b3726 100644 --- a/packages/typert/generator/src/analyzer.ts +++ b/packages/typert/generator/src/analyzer.ts @@ -1032,10 +1032,10 @@ class FaceAnalyzer { if (invocation.kind === 'context') { const context = this.contextDeclarations().get(invocation.context) if (context === undefined) { - this.fail(method, `Remote Context ${invocation.context} has no TypeRTContextMap entry`) + this.fail(method, `Remote Scope ${invocation.context} has no TypeRTContextMap entry`) } const wire = `${invocation.context}Id` - if (wires.has(wire)) this.fail(method, `Remote Context wire field ${wire} conflicts with a method parameter`) + if (wires.has(wire)) this.fail(method, `Remote Scope wire field ${wire} conflicts with a method parameter`) receiver = { kind: 'context', context: invocation.context, @@ -1200,18 +1200,18 @@ class FaceAnalyzer { } marker = { kind: 'direct', exportName } } else if (ts.isCallExpression(expression) - && this.isTypeMetaSymbol(expression.expression, 'RemoteContext')) { + && this.isTypeMetaSymbol(expression.expression, 'RemoteScope')) { if (expression.arguments.length < 1 || expression.arguments.length > 2) { - this.fail(expression, 'RemoteContext() requires a Context key and optional exported method name') + this.fail(expression, 'RemoteScope() requires a Context key and optional exported method name') } const context = stringLiteralValue(expression.arguments[0]) if (context === undefined || !isRemoteSegment(context)) { - this.fail(expression.arguments[0] ?? expression, 'RemoteContext() key must be a string literal containing only RPC endpoint segment characters') + this.fail(expression.arguments[0] ?? expression, 'RemoteScope() key must be a string literal containing only RPC endpoint segment characters') } const exportArgument = expression.arguments[1] const exportName = exportArgument === undefined ? undefined : stringLiteralValue(exportArgument) if (exportArgument !== undefined && (exportName === undefined || !isRemoteSegment(exportName))) { - this.fail(exportArgument, 'RemoteContext() name must be a string literal containing only RPC endpoint segment characters') + this.fail(exportArgument, 'RemoteScope() name must be a string literal containing only RPC endpoint segment characters') } marker = { kind: 'context', context, ...exportName === undefined ? {} : { exportName } } } else { @@ -2529,7 +2529,7 @@ function sourceFileHasSurface(sourceFile: ts.SourceFile): boolean { ? decorator.expression.expression : decorator.expression const name = expressionName(expression) - if (name === 'Remote' || name === 'RemoteContext') return true + if (name === 'Remote' || name === 'RemoteScope') return true } } } diff --git a/packages/typert/generator/src/emitter.ts b/packages/typert/generator/src/emitter.ts index bb39959606..cbed0047c3 100644 --- a/packages/typert/generator/src/emitter.ts +++ b/packages/typert/generator/src/emitter.ts @@ -376,7 +376,7 @@ export class FaceModelEmitter { lines.push(' }') } if (scoped.length > 0) { - lines.push(' interface TypeRTRemoteContextMap {') + lines.push(' interface TypeRTRemoteScopeMap {') for (const invocation of scoped) { this.pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, true) } diff --git a/packages/typert/generator/tests/fixtures/remote-model/packages/remote/src/index.ts b/packages/typert/generator/tests/fixtures/remote-model/packages/remote/src/index.ts index 4aa51ec433..e84d6fd142 100644 --- a/packages/typert/generator/tests/fixtures/remote-model/packages/remote/src/index.ts +++ b/packages/typert/generator/tests/fixtures/remote-model/packages/remote/src/index.ts @@ -1,4 +1,4 @@ -import { GatewayService, Remote, RemoteContext } from '@deepseek-ai/dsh-type-meta' +import { GatewayService, Remote, RemoteScope } from '@deepseek-ai/dsh-type-meta' import type { Agent } from '@fixture/domain' import type { CreateGoalRequest, @@ -19,7 +19,7 @@ export class GoalService extends GatewayService { return { ref: `${agent.id}:${request.title}` } } - @RemoteContext('agent') + @RemoteScope('agent') rename(request: RenameGoalRequest): RenameGoalResult { return { renamed: request.title.length > 0 } } diff --git a/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts b/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts index 5347a6b77e..707dc84ce9 100644 --- a/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts +++ b/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts @@ -11,7 +11,7 @@ declare module '@deepseek-ai/dsh-type-meta' { export interface TypeRTLookupMap {} export interface TypeRTContextMap {} export interface TypeRTRemoteMap {} - export interface TypeRTRemoteContextMap {} + export interface TypeRTRemoteScopeMap {} export type TypeRTRemoteNamespace = { [Endpoint in keyof TypeRTRemoteMap as Endpoint extends `${Namespace}/${infer Method}` @@ -56,7 +56,7 @@ declare module '@deepseek-ai/dsh-type-meta' { context: ClassMethodDecoratorContext Result>, ) => void - export function RemoteContext(key: Extract, exportName?: string): + export function RemoteScope(key: Extract, exportName?: string): ( method: (this: This, ...args: Args) => Result, context: ClassMethodDecoratorContext Result>, diff --git a/packages/typert/generator/tests/remote-model.spec.ts b/packages/typert/generator/tests/remote-model.spec.ts index eaaf680cc6..0e62a56bf4 100644 --- a/packages/typert/generator/tests/remote-model.spec.ts +++ b/packages/typert/generator/tests/remote-model.spec.ts @@ -288,7 +288,7 @@ export interface BoxPayload { const root = copyFixture() editFile(root, 'packages/remote/src/index.ts', source => source .replace(' @Remote\n', '') - .replace(" @RemoteContext('agent')\n", '')) + .replace(" @RemoteScope('agent')\n", '')) editFile(root, 'packages/remote/src/types.ts', source => `${source} /** @typert schema */ @@ -377,8 +377,8 @@ export interface ClientMarker { name: 'duplicate GatewayService field binding', edit: (source: string) => source .replace( - 'import { GatewayService, Remote, RemoteContext }', - 'import { GatewayService, Remote, RemoteContext, bindTypeRTGateway }', + 'import { GatewayService, Remote, RemoteScope }', + 'import { GatewayService, Remote, RemoteScope, bindTypeRTGateway }', ) .replace( 'export class GoalService extends GatewayService {', @@ -495,11 +495,11 @@ export interface ClientMarker { expect(() => analyzeRemote(root)).not.toThrow() }) - it('rejects a Remote Context without a static Context declaration', () => { + it('rejects a Remote Scope without a static Context declaration', () => { const root = copyFixture() - editFile(root, 'packages/remote/src/index.ts', source => source.replace("@RemoteContext('agent')", "@RemoteContext('missing')")) + editFile(root, 'packages/remote/src/index.ts', source => source.replace("@RemoteScope('agent')", "@RemoteScope('missing')")) - expect(() => analyzeRemote(root, false)).toThrow(/Remote Context missing has no TypeRTContextMap entry/) + expect(() => analyzeRemote(root, false)).toThrow(/Remote Scope missing has no TypeRTContextMap entry/) }) it('rejects a direct scoped projection whose Context and lookup wire symbols differ', () => { @@ -579,7 +579,7 @@ function assertRemoteConsumerTypechecks( import remote from '@fixture/remote/remote' import type { TypeRTRemoteContribution, - TypeRTRemoteContextMap, + TypeRTRemoteScopeMap, TypeRTRemoteMap, TypeRTRemoteNamespaceMap, } from '@deepseek-ai/dsh-type-meta' @@ -587,8 +587,8 @@ import type { CreateGoalResult, RenameGoalResult } from '@fixture/remote/types' const contribution: TypeRTRemoteContribution = remote declare const create: TypeRTRemoteMap['goals/create'] -declare const createScoped: TypeRTRemoteContextMap['agent:goals/create'] -declare const rename: TypeRTRemoteContextMap['agent:goals/rename'] +declare const createScoped: TypeRTRemoteScopeMap['agent:goals/create'] +declare const rename: TypeRTRemoteScopeMap['agent:goals/rename'] const created: Promise = create('agent-1', { title: 'ship' }) const cancellable: Promise = create('agent-1', { title: 'ship' }, new AbortController().signal) const createdScoped: Promise = createScoped({ title: 'ship' }) diff --git a/packages/typert/type-meta/README.i18n.yaml b/packages/typert/type-meta/README.i18n.yaml index 6c21127e54..a61602c07b 100644 --- a/packages/typert/type-meta/README.i18n.yaml +++ b/packages/typert/type-meta/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/typert/type-meta/README.md -README.md: a76169742cb78d0d19814bcd0f978c71036a5a1c -README.zh.md: 6f2d2fd6e241441fae8102c0639608e9b27b9bec +README.md: 9bd475f8973ec54756fe0e63d5b7fa485381697d +README.zh.md: 10a6309bc47001d572abb3dd3f794ebfcf6252e8 diff --git a/packages/typert/type-meta/README.md b/packages/typert/type-meta/README.md index a76169742c..9bd475f897 100644 --- a/packages/typert/type-meta/README.md +++ b/packages/typert/type-meta/README.md @@ -7,7 +7,7 @@ Compiler-independent declarations shared by business packages, generated TypeRT ## Remote declarations - `@Remote` marks a public instance method for direct invocation on its registered Cordis Service. -- `@RemoteContext(key)` marks a method whose receiver is selected from a merge-declared scoped Context kind. +- `@RemoteScope(key)` marks a method whose receiver is selected from a merge-declared scoped Context kind. - `GatewayService` binds the Cordis key passed to `super(ctx, serviceKey, options?)` to the same default wire namespace. - `bindTypeRTGateway(this, serviceKey, options?)` provides the same visible, frozen binding for a Service that cannot inherit from `GatewayService`. - `remoteMethods(service)` returns a detached declaration-order snapshot used by the Gateway's SRC fallback. @@ -18,7 +18,7 @@ Decorator initializers retain markers in a module-private `WeakMap` keyed by the ## TypeRT protocol -Business packages extend `TypeRTLookupMap` and `TypeRTContextMap` to associate Host objects or scoped Contexts with their wire identities. Generated artifacts extend `TypeRTRemoteMap`, `TypeRTRemoteContextMap`, and `TypeRTRemoteNamespaceMap` so Client imports expose only selected Remote methods. `InvocationDescriptor` is the shared runtime form consumed by the registry, Gateway, and Client API. +Business packages extend `TypeRTLookupMap` and `TypeRTContextMap` to associate Host objects or scoped Contexts with their wire identities. Generated artifacts extend `TypeRTRemoteMap`, `TypeRTRemoteScopeMap`, and `TypeRTRemoteNamespaceMap` so Client imports expose only selected Remote methods. `InvocationDescriptor` is the shared runtime form consumed by the registry, Gateway, and Client Remote. Lookup and Context packages own both sides of their contract: declaration merging supplies the static association, while runtime providers register identity resolution with `ctx.typert`. A lookup or Host Context provider supplies the stable declaration and default resolver, while Host composition may separately configure a synchronous or asynchronous resolver; policy rejections may use `TypeRTLookupFailure` to carry a failure value owned by the boundary adapter. Strict codecs carry generated schemas; `src-json` codecs identify the weaker source-launch path. diff --git a/packages/typert/type-meta/README.zh.md b/packages/typert/type-meta/README.zh.md index 6f2d2fd6e2..10a6309bc4 100644 --- a/packages/typert/type-meta/README.zh.md +++ b/packages/typert/type-meta/README.zh.md @@ -7,7 +7,7 @@ ## Remote 声明 - `@Remote` 将公开实例方法标记为可在其注册的 Cordis 服务上直接调用。 -- `@RemoteContext(key)` 标记接收者选自合并声明的作用域 Context 类型的方法。 +- `@RemoteScope(key)` 标记接收者选自合并声明的作用域 Context 类型的方法。 - `GatewayService` 将 `super(ctx, serviceKey, options?)` 接收的 Cordis key 同时绑定为默认 wire namespace。 - `bindTypeRTGateway(this, serviceKey, options?)` 为无法继承 `GatewayService` 的 Service 提供同样可见且冻结的绑定。 - `remoteMethods(service)` 返回按声明顺序排列、与内部状态分离的快照,供 Gateway 的 SRC 回退路径使用。 @@ -18,7 +18,7 @@ Host 方法通过将 `signal: AbortSignal` 声明为最后一个参数来启用 ## TypeRT 协议 -业务包扩展 `TypeRTLookupMap` 和 `TypeRTContextMap`,以关联 Host 对象或作用域 Context 与其协议身份。生成的产物扩展 `TypeRTRemoteMap`、`TypeRTRemoteContextMap` 和 `TypeRTRemoteNamespaceMap`,使 Client 导入后仅暴露选定的 Remote 方法。`InvocationDescriptor` 是供注册表、Gateway 和 Client API 使用的共享运行时形式。 +业务包扩展 `TypeRTLookupMap` 和 `TypeRTContextMap`,以关联 Host 对象或作用域 Context 与其协议身份。生成的产物扩展 `TypeRTRemoteMap`、`TypeRTRemoteScopeMap` 和 `TypeRTRemoteNamespaceMap`,使 Client 导入后仅暴露选定的 Remote 方法。`InvocationDescriptor` 是供注册表、Gateway 和 Client Remote 使用的共享运行时形式。 查找包与 Context 包同时负责其契约的两侧:声明合并提供静态关联,运行时提供方则向 `ctx.typert` 注册身份解析。lookup 或 Host Context provider 提供稳定声明与默认 resolver,Host 组合可以另行配置同步或异步 resolver;策略拒绝可用 `TypeRTLookupFailure` 携带由边界适配器拥有的失败值。严格编解码器携带生成的 schema;`src-json` 编解码器标识约束更弱的源码启动路径。 diff --git a/packages/typert/type-meta/src/index.ts b/packages/typert/type-meta/src/index.ts index 774c6d3b32..1375d7872b 100644 --- a/packages/typert/type-meta/src/index.ts +++ b/packages/typert/type-meta/src/index.ts @@ -60,9 +60,9 @@ export type { TypeRTLookupResolver, TypeRTLookupRegistry, TypeRTLookupWire, - TypeRTRemoteContextApi, - TypeRTRemoteContextMap, - TypeRTRemoteContextNamespace, + TypeRTRemoteScopeApi, + TypeRTRemoteScopeMap, + TypeRTRemoteScopeNamespace, TypeRTRemoteContribution, TypeRTRemoteMap, TypeRTRemoteNamespace, @@ -191,16 +191,16 @@ export function Remote( } /** - * Create a decorator for a method resolved from one scoped Remote Context. - * @param key - merge-declared Context key. + * Create a decorator for a method resolved from one Remote Scope. + * @param key - scope key declared through the Context map. * @param exportName - optional Remote export name; defaults to the method name. * @returns a standard method decorator that records only private module state. */ -export function RemoteContext( +export function RemoteScope( key: Extract, exportName?: string, ): RemoteMethodDecorator { - validateName('Context key', key) + validateName('Scope key', key) if (exportName !== undefined) validateName('Remote export name', exportName) return function ( _method: (this: This, ...args: Args) => Result, diff --git a/packages/typert/type-meta/src/types.ts b/packages/typert/type-meta/src/types.ts index 5e7c20cd7c..c1d6b3dcf9 100644 --- a/packages/typert/type-meta/src/types.ts +++ b/packages/typert/type-meta/src/types.ts @@ -40,7 +40,7 @@ export interface TypeRTContextMap {} export interface TypeRTRemoteMap {} /** Merge-extensible scoped Remote method signatures generated for consumers. */ -export interface TypeRTRemoteContextMap {} +export interface TypeRTRemoteScopeMap {} /** * Resolve one direct Remote namespace from the generated flat endpoint map. @@ -57,24 +57,24 @@ export type TypeRTRemoteNamespace = { * The calling Cordis Context supplies the concrete identity at runtime. * @template Namespace - wire namespace between the Context prefix and method. */ -export type TypeRTRemoteContextNamespace< +export type TypeRTRemoteScopeNamespace< Namespace extends string, ContextKey extends string = string, > = { - [Endpoint in keyof TypeRTRemoteContextMap as Endpoint extends `${ContextKey}:${Namespace}/${infer Method}` + [Endpoint in keyof TypeRTRemoteScopeMap as Endpoint extends `${ContextKey}:${Namespace}/${infer Method}` ? Method - : never]: TypeRTRemoteContextMap[Endpoint] + : never]: TypeRTRemoteScopeMap[Endpoint] } -type TypeRTRemoteContextNamespaceKey< +type TypeRTRemoteScopeNamespaceKey< ContextKey extends string, - Endpoint = keyof TypeRTRemoteContextMap, + Endpoint = keyof TypeRTRemoteScopeMap, > = Endpoint extends `${ContextKey}:${infer Namespace}/${string}` ? Namespace : never /** Generated scoped Remote namespaces available to one Context kind. */ -export type TypeRTRemoteContextApi = { - [Namespace in TypeRTRemoteContextNamespaceKey]: - TypeRTRemoteContextNamespace +export type TypeRTRemoteScopeApi = { + [Namespace in TypeRTRemoteScopeNamespaceKey]: + TypeRTRemoteScopeNamespace } /** Merge-extensible direct namespace surface generated for Client Remote services. */ @@ -227,7 +227,7 @@ export interface TypeRTLookupDefinition { readonly wireTypeSymbol: string } -/** Host resolver for one scoped Remote Context kind. */ +/** Host resolver for one scoped Remote kind. */ export interface TypeRTHostContextProvider { /** Wire field carrying the Context identity. */ readonly wire: string diff --git a/packages/typert/type-meta/tests/fixtures/source-launch.ts b/packages/typert/type-meta/tests/fixtures/source-launch.ts index b13a80796d..14eec6610d 100644 --- a/packages/typert/type-meta/tests/fixtures/source-launch.ts +++ b/packages/typert/type-meta/tests/fixtures/source-launch.ts @@ -2,7 +2,7 @@ import { Context } from 'cordis' import { GatewayService, Remote, - RemoteContext, + RemoteScope, remoteMethods, } from '@deepseek-ai/dsh-type-meta' @@ -16,7 +16,7 @@ class Goals extends GatewayService { return value } - @RemoteContext('agent') + @RemoteScope('agent') scoped(value: string): string { return value } diff --git a/packages/typert/type-meta/tests/type-meta.spec.ts b/packages/typert/type-meta/tests/type-meta.spec.ts index b84b76300c..bfe99630b9 100644 --- a/packages/typert/type-meta/tests/type-meta.spec.ts +++ b/packages/typert/type-meta/tests/type-meta.spec.ts @@ -6,7 +6,7 @@ import { bindTypeRTGateway, GatewayService, Remote, - RemoteContext, + RemoteScope, remoteMethods, type TypeRTContext, } from '@deepseek-ai/dsh-type-meta' @@ -29,7 +29,7 @@ describe('type-meta Remote declarations', () => { return value } - @RemoteContext('metaFixture') + @RemoteScope('metaFixture') scoped(value: string): string { return value } @@ -84,7 +84,7 @@ describe('type-meta Remote declarations', () => { Reflect.get(Goals.prototype, 'create') as (this: Goals, ...args: unknown[]) => unknown, methodContext('create', initializers), ) - RemoteContext('metaFixture')( + RemoteScope('metaFixture')( Reflect.get(Goals.prototype, 'scoped') as (this: Goals, ...args: unknown[]) => unknown, methodContext('scoped', initializers), ) @@ -141,7 +141,7 @@ describe('type-meta Remote declarations', () => { Reflect.get(Service.prototype, 'run') as (this: Service, ...args: unknown[]) => unknown, methodContext('run', initializers), ) - RemoteContext('metaFixture', 'inspect')( + RemoteScope('metaFixture', 'inspect')( Reflect.get(Service.prototype, 'scoped') as (this: Service, ...args: unknown[]) => unknown, methodContext('scoped', initializers), ) @@ -166,8 +166,8 @@ describe('type-meta Remote declarations', () => { expect(() => Remote('bad name')).toThrow('export name') expect(() => Remote('.')).toThrow('export name') expect(() => Remote('..')).toThrow('export name') - expect(() => RemoteContext('' as 'metaFixture')).toThrow('Context key') - expect(() => RemoteContext('metaFixture', 'bad/name')).toThrow('export name') + expect(() => RemoteScope('' as 'metaFixture')).toThrow('Scope key') + expect(() => RemoteScope('metaFixture', 'bad/name')).toThrow('export name') for (const context of [ { ...methodContext('run', []), private: true }, @@ -195,7 +195,7 @@ describe('type-meta Remote declarations', () => { Reflect.get(Service.prototype, 'run'), methodContext('run', conflicting), ) - RemoteContext('metaFixture')( + RemoteScope('metaFixture')( Reflect.get(Service.prototype, 'run'), methodContext('run', conflicting), )