feat(goal): add TypeRT gateway example

This commit is contained in:
imccyu
2026-08-06 15:28:54 +08:00
parent 61c2c15dc4
commit 9400926bdf
36 changed files with 809 additions and 155 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/remotes/README.md
README.md: e29188b8e3ae5ecefe194f1355558e9bdeaae7dd
README.zh.md: e6425ab190a28e0a38c3713c4e21645789a8f00c

View File

@@ -0,0 +1,22 @@
# @deepseek-ai/dsh-client-remotes
English | [中文](README.zh.md)
Platform-neutral Client facade for Host Remote capabilities selected by this application. Its Client entry imports generated `/remote` artifacts as runtime values, mounts each contribution through `ctx.api`, and re-exports their declaration merges. Client business packages depend on this facade rather than the Host API Gateway or individual Remote runtime entries.
The current assembly mounts only the Goal Remote contribution. Cordis effect ownership withdraws every contribution when this assembly unloads, while the Client face of `@deepseek-ai/dsh-host-api-gateway` owns descriptor validation, concrete root and scoped methods, invocation, and cancellation.
This package contains no transport or Host discovery logic. It can be reused by Web or a future TUI Client that provides the same React-free `ctx.api` contract.
## Model Experience
None, as this Client assembly selects Remote application methods and registers no model surface.
#### KV Cache effect
No direct effect; mounted Host capabilities own any model-visible behavior they trigger.
## Known Limitations and Deferred Work
- The capability set is fixed by explicit build-time value imports; the Client does not discover the Host's active Services or Remote definitions at runtime.
- Additional capabilities require an explicit `/remote` value import and mount in this assembly.

View File

@@ -0,0 +1,22 @@
# @deepseek-ai/dsh-client-remotes
[English](README.md) | 中文
为本应用选定的 Host Remote 能力提供平台无关的 Client 外观。其 Client 入口以运行时值形式导入生成的 `/remote` 产物,通过 `ctx.api` 挂载每项贡献并重新导出对应的声明合并。Client 业务包依赖此外观,而不依赖 Host API Gateway 或单独的 Remote 运行时入口。
当前组合仅挂载 Goal Remote 贡献。该组合卸载时Cordis effect 的所有权机制会撤回所有贡献;`@deepseek-ai/dsh-host-api-gateway` 的 Client 侧负责描述符校验、具体的根级方法和作用域方法、调用与取消。
本包不包含传输逻辑或 Host 发现逻辑。Web 和未来的 TUI Client 只要提供同一份不依赖 React 的 `ctx.api` 契约,均可复用本包。
## 模型体验
无,因为此 Client 组合只选择应用的 Remote 方法,不注册任何模型接口。
#### KV Cache 影响
无直接影响;其触发的任何模型可见行为均由已挂载的 Host 能力负责。
## 已知限制与暂缓事项
- 能力集合由构建时显式导入的值固定确定Client 不会在运行时发现 Host 中已启用的服务或 Remote 定义。
- 若要增加能力,必须显式导入相应的 `/remote` 值并在此组合中挂载。

View File

@@ -0,0 +1,55 @@
{
"name": "@deepseek-ai/dsh-client-remotes",
"description": "Platform-neutral assembly of explicitly selected Host Remote contributions",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./client": {
"types": "./lib/types/client/index.d.ts",
"default": "./lib/client.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"dshClient": {
"inject": [
"@deepseek-ai/dsh-host-api-gateway"
],
"platform": "web",
"immediately": true
},
"scripts": {
"bundle": "tsdown",
"watch": "tsdown --watch"
},
"license": "BSD-3-Clause",
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/client.js",
"lib/types/**/*.d.ts"
],
"peerDependencies": {
"@deepseek-ai/dsh-host-api-gateway": "^0.0.1",
"@deepseek-ai/dsh-goal": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-host-api-gateway": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -0,0 +1,19 @@
/** Platform-neutral assembly of generated Host Remote contributions. */
import type { Context } from 'cordis'
import type {} from '@deepseek-ai/dsh-host-api-gateway/client'
import goalsRemote from '@deepseek-ai/dsh-goal/remote'
export type { ClientApi } from '@deepseek-ai/dsh-host-api-gateway/client'
export type {} from '@deepseek-ai/dsh-goal/remote'
/** Required service: the typed Client API contribution mount. */
export const inject = ['api']
/**
* Mount the Host capabilities explicitly selected for this Client assembly.
* @param ctx - Client Cordis root carrying the typed API service.
*/
export function apply(ctx: Context): void {
ctx.api.mount(goalsRemote)
}

View File

@@ -0,0 +1,4 @@
/** Host Loader entry for the Client Remote contribution assembly. */
/** Host plugin body; the selected contributions mount only in Client environments. */
export function apply(): void {}

View File

@@ -0,0 +1,24 @@
/** Package-owned invariant companion for `@deepseek-ai/dsh-client-remotes`. */
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-client-remotes'
/** Cordis companion plugin name. */
export const name = 'client-remotes-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** No runtime invariant: the API service owns contribution and method lifecycle atomically. */
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -0,0 +1,214 @@
import { execFile } from 'node:child_process'
import { existsSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { describe, expect, it } from 'vitest'
/**
* Built-artifact smoke for the first generated Remote: plain Node boots the
* Host and Browser bundle handoffs, then crosses the real `/api2` HTTP route.
*/
const packageDir = fileURLToPath(new URL('..', import.meta.url))
const root = resolve(packageDir, '../../..')
const artifact = (path: string): string => join(root, path)
const artifactUrl = (path: string): string => pathToFileURL(artifact(path)).href
const requiredArtifacts = [
'packages/client/connection/lib/client.js',
'packages/client/connection/lib/index.js',
'packages/client/remotes/lib/client.js',
'packages/core/agent/lib/index.js',
'packages/core/session/lib/index.js',
'packages/goal/goal/lib/index.js',
'packages/goal/goal/lib/typert.host.js',
'packages/host/api-gateway/lib/client.js',
'packages/host/api-gateway/lib/index.js',
'packages/typert/registry/lib/client.js',
'packages/typert/registry/lib/index.js',
].every(path => existsSync(artifact(path)))
describe.skipIf(!requiredArtifacts)('Goal Remote built LIB chain', () => {
it('runs root and Agent-scoped calls through generated bundles and real HTTP', async () => {
const urls = Object.fromEntries(Object.entries({
agent: 'packages/core/agent/lib/index.js',
apiGatewayClient: 'packages/host/api-gateway/lib/client.js',
apiGatewayHost: 'packages/host/api-gateway/lib/index.js',
connectionClient: 'packages/client/connection/lib/client.js',
connectionHost: 'packages/client/connection/lib/index.js',
goal: 'packages/goal/goal/lib/index.js',
goalTypert: 'packages/goal/goal/lib/typert.host.js',
registryClient: 'packages/typert/registry/lib/client.js',
registryHost: 'packages/typert/registry/lib/index.js',
remotesClient: 'packages/client/remotes/lib/client.js',
session: 'packages/core/session/lib/index.js',
}).map(([key, path]) => [key, artifactUrl(path)]))
const script = `
import { createServer } from 'node:http'
import * as cordis from 'cordis'
const urls = ${JSON.stringify(urls)}
const { Context } = cordis
const { default: AgentRegistry } = await import(urls.agent)
const connectionHost = await import(urls.connectionHost)
const { default: TypertGatewayService } = await import(urls.apiGatewayHost)
const { default: GoalService } = await import(urls.goal)
const { TYPERT } = await import(urls.goalTypert)
const { default: TypertRegistry } = await import(urls.registryHost)
const { Session, SessionId } = await import(urls.session)
const routes = []
const host = new Context()
host.provide('httpServer', {
register(route) {
routes.push(route)
return () => { routes.splice(routes.indexOf(route), 1) }
},
tapIndex() { return () => {} },
port: 0,
})
await host.plugin({ inject: connectionHost.inject, apply: connectionHost.apply })
await host.plugin(TypertRegistry)
await host.plugin(AgentRegistry)
await host.plugin(TypertGatewayService)
await host.plugin(GoalService)
host.typert.register(TYPERT)
const makeAgent = rawId => {
const session = new Session(SessionId(rawId))
return {
id: session.id,
options: {},
session,
ctx: host.extend(),
status: 'idle',
acceptsNextStep: false,
send() {},
updateInbox() { return 'not-found' },
followup() {},
steer() { return { outcome: Promise.resolve({ status: 'rejected' }) } },
inject(input) { session.append('user/message', input, { surfaceOp: 'append' }) },
reserveTurnAdmission() {},
cancel() {},
whenIdle() { return Promise.resolve() },
}
}
const rootAgent = makeAgent('built-root-agent')
const scopedAgent = makeAgent('built-scoped-agent')
host.agents.register(rootAgent)
host.agents.register(scopedAgent)
if (routes.length !== 1) throw new Error('Gateway did not register exactly one /api2 route')
const server = createServer((request, response) => { void routes[0].handler(request, response) })
await new Promise(resolveListen => server.listen(0, '127.0.0.1', resolveListen))
const address = server.address()
if (address === null || typeof address === 'string') throw new Error('HTTP server has no TCP address')
const origin = 'http://127.0.0.1:' + String(address.port)
const handoffs = new Map()
globalThis.window = {
__ModuleLoader__: {
load(handoff) { handoffs.set(handoff.id, handoff) },
},
}
globalThis.location = { hostname: '127.0.0.1', origin, search: '' }
await import(urls.registryClient)
await import(urls.connectionClient)
await import(urls.apiGatewayClient)
await import(urls.remotesClient)
const instantiate = id => {
const handoff = handoffs.get(id)
if (handoff === undefined) throw new Error('missing Client bundle handoff ' + id)
return handoff.factory(specifier => {
if (specifier === 'cordis') return cordis
throw new Error('unexpected Client external ' + specifier)
})
}
const client = new Context()
for (const id of [
'@deepseek-ai/dsh-typert-registry',
'@deepseek-ai/dsh-client-connection',
'@deepseek-ai/dsh-host-api-gateway',
'@deepseek-ai/dsh-client-remotes',
]) {
const plugin = instantiate(id)
await client.plugin({ inject: plugin.inject, apply: plugin.apply })
}
client.typert.contexts.registerClient('agent', {
identity: candidate => candidate.builtAgentId,
})
let invalidRejected = false
try {
await client.api.goals.create(rootAgent.id, { objective: 1 })
} catch {
invalidRejected = true
}
const rootResult = await client.api.goals.create(rootAgent.id, { objective: 'root goal' })
const agentContext = client.extend({ builtAgentId: scopedAgent.id })
const scopedResult = await agentContext.goals.create({ objective: 'scoped goal', maxGoalRounds: 3 })
const result = {
invalidRejected,
rootResult,
scopedResult,
rootGoal: host.goals.get(rootAgent)?.objective,
scopedGoal: host.goals.get(scopedAgent)?.objective,
rootEvents: rootAgent.session.events.length,
scopedEvents: scopedAgent.session.events.length,
}
await client.fiber.dispose()
await new Promise((resolveClose, rejectClose) => server.close(error => {
if (error === undefined) resolveClose()
else rejectClose(error)
}))
await host.fiber.dispose()
console.log(JSON.stringify(result))
`
const result = await runPlainNode(script)
expect(result.exitCode, `stderr:\n${result.stderr}`).toBe(0)
const output = JSON.parse(result.stdout.trim().split('\n').at(-1) ?? '{}') as {
invalidRejected: boolean
rootResult: { ref: { id: string; revision: number } }
scopedResult: { ref: { id: string; revision: number } }
rootGoal: string
scopedGoal: string
rootEvents: number
scopedEvents: number
}
expect(output).toMatchObject({
invalidRejected: true,
rootResult: { ref: { revision: 1 } },
scopedResult: { ref: { revision: 1 } },
rootGoal: 'root goal',
scopedGoal: 'scoped goal',
rootEvents: 1,
scopedEvents: 1,
})
expect(output.rootResult.ref.id).toMatch(/^goal-/)
expect(output.scopedResult.ref.id).toMatch(/^goal-/)
}, 60_000)
})
/** Execute one ESM script without tsx or a TypeScript loader. */
function runPlainNode(script: string): Promise<{
readonly exitCode: number | null
readonly stdout: string
readonly stderr: string
}> {
return new Promise((resolveRun) => {
execFile(process.execPath, ['--input-type=module', '-e', script], {
cwd: packageDir,
encoding: 'utf8',
timeout: 55_000,
}, (error, stdout, stderr) => {
resolveRun({
exitCode: error === null ? 0 : typeof error.code === 'number' ? error.code : null,
stdout,
stderr,
})
})
})
}

View File

@@ -0,0 +1,30 @@
{
"extends": "../../../tsconfig.base.client.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": [
"src"
],
"references": [
{
"path": "../../../vendor/cordis"
},
{
"path": "../../host/api-gateway"
},
{
"path": "../../ui/commands"
},
{
"path": "../../goal/goal"
},
{
"path": "../../session-title/session-title"
},
{
"path": "../../support/invariants"
}
]
}

View File

@@ -0,0 +1,3 @@
import { clientBundle } from '../tsdown.client.ts'
export default clientBundle('@deepseek-ai/dsh-client-remotes', ['lib/types/index.js', 'lib/types/invariant.js'])

View File

@@ -24,7 +24,9 @@
},
"dshClient": {
"inject": [
"@deepseek-ai/dsh-client-connection"
"@deepseek-ai/dsh-client-connection",
"@deepseek-ai/dsh-client-remotes",
"@deepseek-ai/dsh-typert-registry"
],
"platform": "web",
"immediately": true
@@ -47,11 +49,17 @@
},
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-client-remotes": "^0.0.1",
"@deepseek-ai/dsh-type-meta": "^0.0.1",
"@deepseek-ai/dsh-typert-registry": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-client-remotes": "workspace:^",
"@deepseek-ai/dsh-timeout": "workspace:^",
"@deepseek-ai/dsh-type-meta": "workspace:^",
"@deepseek-ai/dsh-typert-registry": "workspace:^",
"@types/react": "~18.3.1",
"cordis": "^4.0.0-rc.7"
},

View File

@@ -18,6 +18,7 @@
import { Context as CordisContext } from 'cordis'
import type { Context, Fiber } from 'cordis'
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client'
import type { TypeRTRemoteContextApi } from '@deepseek-ai/dsh-type-meta'
/** Context tag written by {@link createScope}. */
const kScope = Symbol('dsh.client.scope')
@@ -29,7 +30,7 @@ export interface AgentScopeHandle {
* through it (passing it as the dispatch subject routes to this agent's
* tagged listeners plus every untagged one).
*/
ctx: Context
ctx: Context & TypeRTRemoteContextApi<'agent'>
/** Backing fiber (dispose tears down every scope-owned registration). */
fiber: Fiber
}
@@ -48,15 +49,16 @@ function agentScope(): void {}
*/
export function createScope(ctx: Context, key: SessionId): AgentScopeHandle {
const fiber = ctx.plugin(agentScope)
const scoped = fiber.ctx.extend({
[kScope]: key,
[CordisContext.filter](listenerCtx: Context): boolean {
const tag = scopeOf(listenerCtx)
return tag === undefined || tag === key
},
}) as Context & TypeRTRemoteContextApi<'agent'>
return {
fiber,
ctx: fiber.ctx.extend({
[kScope]: key,
[CordisContext.filter](listenerCtx: Context): boolean {
const tag = scopeOf(listenerCtx)
return tag === undefined || tag === key
},
}),
ctx: scoped,
}
}

View File

@@ -11,6 +11,7 @@ import type { Context } from 'cordis'
import type {
RpcResult, SessionId, SubagentAddress,
} from '@deepseek-ai/dsh-client-connection/client'
import type { TypeRTRemoteContextApi } from '@deepseek-ai/dsh-type-meta'
import type { HostObservable, SessionMaybeProvideInfo } from '@deepseek-ai/dsh-client-ui-slots'
import type { SessionSearchResultItem } from '../sessions/manager.ts'
import type {
@@ -19,6 +20,9 @@ import type {
import type { SessionFace } from './session.ts'
import type { ObservableSnapshot } from './store.ts'
/** Client Cordis Context carrying one Agent identity and its generated Remote namespaces. */
export type AgentContext = Context & TypeRTRemoteContextApi<'agent'>
/** The sessions-service face injected as `ctx.sessions`. */
export interface ISessions {
/** The useSessions standard feed (list rows + current selection; read face — writes stay inside the domain). */
@@ -95,7 +99,7 @@ export interface ISessions {
* @param id - session id.
* @returns scoped ctx, or undefined for a session neither listed nor already scoped.
*/
scope(id: SessionId): Context | undefined
scope(id: SessionId): AgentContext | undefined
/**
* Read the Agent scope tag off a context (service-method seam: fetch
* bundles must reach scope resolution through ctx.sessions).

View File

@@ -1,6 +1,8 @@
/** Browser runtime services for slots, sessions, workspaces, and connection-stream delivery. */
import type { Context } from 'cordis'
import type { ConnectionHandle, SessionId } from '@deepseek-ai/dsh-client-connection/client'
import type {} from '@deepseek-ai/dsh-client-remotes/client'
import type { TypeRTContext } from '@deepseek-ai/dsh-type-meta'
import type { MaybeSnapshotSelectorHook, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
import { SlotsService } from './slots.ts'
import { SessionsService } from './sessions/service.ts'
@@ -26,7 +28,7 @@ export type { ISession, ProjectionsFace, SessionFace } from './contract/session.
export type {
ISessionHistory, SessionHistoryFace, SessionHistorySnapshot,
} from './contract/session-history.ts'
export type { ISessions } from './contract/sessions.ts'
export type { AgentContext, ISessions } from './contract/sessions.ts'
export type { IWorkspaces } from './contract/workspaces.ts'
export type {
SessionBinding, SessionListState, SessionProvideContribution, SessionProvideDescriptor, SessionSummary,
@@ -75,6 +77,13 @@ export type { SessionId } from '@deepseek-ai/dsh-client-connection/client'
/** Client-side Cordis context after declaration merging. */
export type ClientContext = Context
declare module '@deepseek-ai/dsh-type-meta' {
interface TypeRTContextMap {
/** Client Agent scope identity; the agent and session share one wire id. */
agent: TypeRTContext<SessionId>
}
}
/** The conversation-snapshot selector hook (ConvViewProps/ToolRowProps take this). */
export type UseConversationSession = SnapshotSelectorHook<ConversationSnapshot>
@@ -170,8 +179,8 @@ declare module 'cordis' {
}
}
/** Required services: the wire handle mounted by the connection plugin. */
export const inject = ['connection']
/** Required services: the typed Remote API, wire handle, and Client TypeRT registry. */
export const inject = ['api', 'connection', 'typert']
/** Mounts the browser runtime services and connection stream.
* @param ctx - Client Cordis context.
@@ -180,6 +189,9 @@ export function apply(ctx: Context): void {
ctx.plugin(SlotsService)
const connection = ctx.get('connection') as ConnectionHandle
const sessions = new SessionsService(ctx, connection.api)
ctx.typert.contexts.registerClient('agent', {
identity: candidate => sessions.scopeOf(candidate),
})
const sessionHistory = new SessionHistoryService(ctx, connection.api)
const workspaces = new WorkspacesService(ctx, connection.api, sessions)
ctx.effect(

View File

@@ -29,7 +29,7 @@ import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/t
import type { SnapshotStore } from '../contract/store.ts'
import { createSnapshotStore } from '../contract/store.ts'
import type { SessionFace } from '../contract/session.ts'
import type { ISessions } from '../contract/sessions.ts'
import type { AgentContext, ISessions } from '../contract/sessions.ts'
import { createScope, scopeOf as scopeTagOf } from '../agents/scope.ts'
import { SessionManager } from './manager.ts'
import type { SessionListPhase, SessionSearchResultItem, SubagentCatalogSnapshot } from './manager.ts'
@@ -127,7 +127,7 @@ export interface SessionBinding {
readonly sessionId: SessionId
/** The outward session face only — feature code never sees the concrete class. */
readonly session: SessionFace
readonly ctx: Context
readonly ctx: AgentContext
}
// Scope primitives live in ../agents/scope.ts (the client mirror of host
@@ -182,7 +182,7 @@ function increasedForkTitle(title: string): string {
interface ScopeRecord {
fiber: Fiber
ctx: Context
ctx: AgentContext
binding: SessionBinding
/** The concrete Session for runtime-internal entry points (staging open()); the binding carries only the outward face. */
session: Session
@@ -483,7 +483,7 @@ export class SessionsService implements ISessions {
* @param id - session id (the agent identity — 1:1 same axis).
* @returns scoped ctx, or undefined for a session neither listed nor already scoped.
*/
scope(id: SessionId): Context | undefined {
scope(id: SessionId): AgentContext | undefined {
return this.resolve(id)?.ctx
}

View File

@@ -8,6 +8,7 @@ import { describe, expect, it } from 'vitest'
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
import type { ConnectionSinks } from '@deepseek-ai/dsh-client-connection/client'
import { SESSION_SEARCH_RESULT_LIMIT } from '@deepseek-ai/dsh-host-apiproxy/api'
import TypertRegistry from '@deepseek-ai/dsh-typert-registry'
import * as RuntimeClient from '../src/client/index.ts'
import type { SessionsService } from '../src/client/sessions/service.ts'
import type { WorkspacesService } from '../src/client/workspaces/service.ts'
@@ -22,6 +23,7 @@ interface Bench {
async function mount(): Promise<Bench> {
const ctx = new Context()
await ctx.plugin(TypertRegistry)
const api = new FakeApiClient()
const bench: Bench = { ctx, api, sinks: undefined, stopped: 0 }
const handle: ConnectionHandle = {
@@ -36,6 +38,7 @@ async function mount(): Promise<Bench> {
},
}
ctx.reflect.provide('connection', handle)
ctx.reflect.provide('api', {})
await ctx.plugin(RuntimeClient).await()
return bench
}

View File

@@ -20,6 +20,9 @@
{
"path": "../connection"
},
{
"path": "../remotes"
},
{
"path": "../../host/apiproxy"
},
@@ -43,6 +46,12 @@
},
{
"path": "../../support/invariants"
},
{
"path": "../../typert/type-meta"
},
{
"path": "../../typert/registry"
}
],
"exclude": [

View File

@@ -3,7 +3,7 @@ import type { Context } from 'cordis'
import { createScope, scopeOf, SessionProvideChannel } from '@deepseek-ai/dsh-client-runtime/client'
import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import type {
ConversationSnapshot, ISessions, ObservableSnapshot, ProjectionsFace, SessionFace, SessionId,
AgentContext, ConversationSnapshot, ISessions, ObservableSnapshot, ProjectionsFace, SessionFace, SessionId,
SessionListState, SessionProvideDescriptor, SessionSearchResultItem, SessionSummary, SnapshotStore,
SubagentAddress,
} from '@deepseek-ai/dsh-client-runtime/client'
@@ -134,7 +134,7 @@ interface SessionRecord {
summary: SessionSummary
snapshot: SnapshotStore<ConversationSnapshot>
session: FixtureSession
scope: Context | undefined
scope: AgentContext | undefined
scopeFiber: { dispose(): Promise<void> } | undefined
/** Materialized standard-props bundle (identity-stable per session; invalidated on roster change). */
provideInfo: SessionProvideInfo | undefined
@@ -144,7 +144,7 @@ interface SessionRecord {
export interface TestSessionBinding {
readonly sessionId: SessionId
readonly session: FixtureSession
readonly ctx: Context
readonly ctx: AgentContext
}
/**
@@ -345,7 +345,7 @@ export class TestSessions implements ISessions {
* @param id - session id.
* @returns the scoped context, or undefined for unknown sessions.
*/
scope(id: string): Context | undefined {
scope(id: string): AgentContext | undefined {
const record = this.records.get(id as SessionId)
if (record === undefined) return undefined
if (record.scope === undefined) {