Prechádzať zdrojové kódy

refactor(api-session-controller): route subagent calls through Remote

imccyu 4 týždňov pred
rodič
commit
cbe5d76e5c

+ 4 - 3
packages/api/session-controller/src/client/contract/result.ts

@@ -1,10 +1,11 @@
-/** Client operation results spanning Session Remote calls and the legacy subagent carrier. */
+/** Client operation results spanning the Session and subagent Remote calls. */
 
 import type { RpcError } from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentControlError } from '@deepseek-ai/dsh-subagent/client'
 import type { SessionError } from '../../types.ts'
 
 /** Failure surfaced by the Client Session object layer. */
-export type ClientFailure = RpcError | SessionError
+export type ClientFailure = RpcError | SessionError | SubagentControlError
 
 /** Success or failure returned by a Client Session operation. */
 export type ClientResult<T> =
@@ -13,7 +14,7 @@ export type ClientResult<T> =
 
 /**
  * Fold a rejected carrier operation into the Client Session failure vocabulary.
- * @param error - rejection from a legacy subagent or local carrier call.
+ * @param error - rejection from a Remote or local carrier call.
  * @returns the failure branch of a Client Session result.
  */
 export function transportResult<T>(error: unknown): ClientResult<T> {

+ 1 - 1
packages/api/session-controller/src/client/contract/sessions.ts

@@ -5,7 +5,7 @@
  * explicit act of widening what features may do to the sessions domain.
  */
 import type { Context } from '@deepseek-ai/cordis'
-import type { SubagentAddress } from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types'
 import type { AgentContext } from '../scope.ts'

+ 1 - 1
packages/api/session-controller/src/client/contract/snapshot.ts

@@ -2,7 +2,7 @@
 import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
 import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
-import type { SubagentAddress } from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
 import type { ClientFailure } from './result.ts'
 
 /** One transient inbox occurrence from the authoritative queue snapshot. */

+ 2 - 1
packages/api/session-controller/src/client/index.ts

@@ -73,6 +73,7 @@ export const inject = [
   'remote',
   'remote.commands',
   'remote.session',
+  'remote.subagents',
 ]
 
 /**
@@ -82,7 +83,7 @@ export const inject = [
 export function apply(ctx: Context): void {
   const connection = ctx.get('connection') as ConnectionHandle
   const remotes = ctx.remote as unknown as SessionRemotes
-  const sessions = new ClientSessions(ctx, connection.api, remotes)
+  const sessions = new ClientSessions(ctx, remotes)
   ctx.remote.$on('api-session/added', (summary) => { sessions.handleSessionAdded(summary) })
   ctx.remote.$on('api-session/removed', (sessionId) => { sessions.handleSessionRemoved(sessionId) })
   ctx.remote.$on('api-session/status', (sessionId, running) => {

+ 4 - 7
packages/api/session-controller/src/client/sessions/manager.ts

@@ -2,9 +2,7 @@
 // dispatch entry + list state, constructed and held by ClientSessions (one per browser client).
 // List data never enters zustand; React connects via subscribe/getListSnapshot.
 
-import type {
-  IApiClient, SubagentAddress, SubagentCatalog,
-} from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress, SubagentCatalog } from '@deepseek-ai/dsh-subagent/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types'
 import type {
@@ -145,11 +143,10 @@ export class SessionManager {
   })
 
   /**
-   * @param api - shared wire client.
+   * @param remote - generated Remote namespaces the Session cluster calls.
    * @param restoredSelection - persisted real-Session selection candidate.
    */
   constructor(
-    private readonly api: IApiClient,
     private readonly remote: SessionRemotes,
     restoredSelection?: SessionId,
     restoredAddress?: SubagentAddress,
@@ -321,7 +318,7 @@ export class SessionManager {
     const parentAvailable = address === undefined
       ? undefined
       : this.catalogs.get(address.parentSessionId)?.parentAvailable
-    return new Session(sessionId, this.api, this.remote, {
+    return new Session(sessionId, this.remote, {
       ...(address === undefined ? {} : {
         address,
         ...catalogAvailability(parentAvailable),
@@ -369,7 +366,7 @@ export class SessionManager {
     this.notifier.markDirty()
     const operation = (async () => {
       try {
-        const { result } = await this.api.subagents.list({ parentSessionId })
+        const result = toSessionResult(await this.remote.subagents.list(parentSessionId))
         if (result.ok) {
           const parentAvailable = this.catalogInflight.get(parentSessionId)?.parentAvailableOverride
             ?? result.value.parentAvailable

+ 18 - 0
packages/api/session-controller/src/client/sessions/remotes.ts

@@ -8,6 +8,9 @@
 import type { EncodedImageAttachment } from '@deepseek-ai/dsh-attachment/types'
 import type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
+import type {
+  SubagentCatalog, SubagentInterruptReceipt, SubagentPromptReceipt, SubagentPromptRequest,
+} from '@deepseek-ai/dsh-subagent/client'
 import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
 import type { SessionRemote } from '../transport.ts'
 
@@ -21,9 +24,24 @@ export interface SessionCommandsRemote {
   ): Promise<RemoteResult<object | undefined>>
 }
 
+/** Narrow subagent namespace consumed by a Client Session and its manager. */
+export interface SessionSubagentsRemote {
+  list(parentSessionId: SessionId, signal?: AbortSignal): Promise<RemoteResult<SubagentCatalog>>
+  prompt(
+    request: SubagentPromptRequest,
+    signal?: AbortSignal,
+  ): Promise<RemoteResult<SubagentPromptReceipt>>
+  interruptByParent(
+    childSessionId: SessionId,
+    parentSessionId: SessionId,
+    mode: 'continuable',
+  ): Promise<RemoteResult<SubagentInterruptReceipt>>
+}
+
 /** Generated Remote namespaces consumed by the Client Session object layer. */
 export interface SessionRemotes {
   readonly $stream: ClientRemote['$stream']
   readonly commands: SessionCommandsRemote
   readonly session: SessionRemote
+  readonly subagents: SessionSubagentsRemote
 }

+ 1 - 6
packages/api/session-controller/src/client/sessions/service.ts

@@ -15,9 +15,7 @@
  * survives frozen (read-only view) until the stage moves on.
  */
 import type { Context, Fiber } from '@deepseek-ai/cordis'
-import type {
-  IApiClient, SubagentAddress,
-} from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import { workspaceTitleOf } from '@deepseek-ai/dsh-util-workspace-path'
 import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types'
@@ -218,12 +216,10 @@ export class ClientSessions implements ISessions {
 
   /**
    * @param ctx - client root context (scope fibers mount under it).
-   * @param api - wire client shared with every Session.
    * @param remote - generated Remote namespaces shared with every Session.
    */
   constructor(
     private readonly rootCtx: Context,
-    api: IApiClient,
     remote: SessionRemotes,
   ) {
     this.selection = createSnapshotStore<SessionSelection>(
@@ -231,7 +227,6 @@ export class ClientSessions implements ISessions {
       { persist: { name: 'dsh.sessions.current' } })
     const restored = this.selection.getSnapshot()
     this.manager = new SessionManager(
-      api,
       remote,
       restored.sessionId,
       restored.subagentAddress,

+ 13 - 10
packages/api/session-controller/src/client/sessions/session.ts

@@ -3,9 +3,7 @@
 import type { Context } from '@deepseek-ai/cordis'
 import { randomUUID } from '@deepseek-ai/dsh-util-crypto'
 import type { AttachmentIdType, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
-import type {
-  IApiClient, SubagentAddress,
-} from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
 import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import {
@@ -133,13 +131,11 @@ export class Session implements SessionFace {
 
   /**
    * @param sessionId - Host session identity (client sessions are always Host-born).
-   * @param api - shared wire client.
    * @param remote - generated Remote namespaces this session calls.
    * @param options - optional manager-owned state observers.
    */
   constructor(
     readonly sessionId: SessionId,
-    private readonly api: IApiClient,
     private readonly remote: SessionRemotes,
     private readonly options: SessionOptions = {},
   ) {
@@ -222,13 +218,16 @@ export class Session implements SessionFace {
             },
           }
         } else {
-          const routed = (await this.api.subagents.prompt({
-            ...this.address,
+          const routed = toSessionResult(await this.remote.subagents.prompt({
+            requestId: randomUUID() as SessionRequestId,
+            parentSessionId: this.address.parentSessionId,
+            childSessionId: this.address.childSessionId,
+            mode: this.address.mode,
             content: content.flatMap(part => part.type === 'text'
               ? [{ type: 'text' as const, text: part.text }]
               : []),
             clientTimeZone: resolvedClientTimeZone(),
-          }, signal)).result
+          }, signal))
           result = routed.ok ? { ok: true, value: { accepted: true } } : routed
         }
       }
@@ -290,7 +289,7 @@ export class Session implements SessionFace {
   /**
    * Stop the active turn while the Host preserves pending inbox work; failures
    * land in promptError (same error-strip display slot). A continuable
-   * subagent address routes through `subagent.interrupt`, whose durable
+   * subagent address routes through `subagents.interruptByParent`, whose durable
    * parent-address authority works without a live parent Agent; a one-shot
    * address stays uncancellable (the UI offers no stop action, so this arm is
    * defensive).
@@ -314,7 +313,11 @@ export class Session implements SessionFace {
     let result: ClientResult<{ accepted: true }>
     try {
       result = address !== undefined
-        ? (await this.api.subagents.interrupt(address)).result
+        ? toSessionResult(await this.remote.subagents.interruptByParent(
+          address.childSessionId,
+          address.parentSessionId,
+          address.mode,
+        ))
         : toSessionResult(await this.remote.session.cancel({ sessionId: this.sessionId }))
     } catch (error) {
       result = transportResult(error)

+ 1 - 0
packages/api/session-controller/tsconfig.client.json

@@ -21,6 +21,7 @@
     { "path": "../../llm/llm" },
     { "path": "../../session/session-projection" },
     { "path": "../../session/session-title" },
+    { "path": "../../subagent/subagent" },
     { "path": "../../util/brand" },
     { "path": "../../util/crypto" },
     { "path": "../../util/workspace-path" },

+ 0 - 1
packages/client/connection/src/client/api.ts

@@ -14,7 +14,6 @@ export type {
   ModelReasoningEffort, ModelSelection,
   SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
   CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi,
-  SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt,
 } from '@deepseek-ai/dsh-host-apiproxy/api'
 export type {
   RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,

+ 0 - 1
packages/client/connection/src/client/index.ts

@@ -35,7 +35,6 @@ export type {
   SkillsApi, SkillEntry,
   ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
   MessageId, ModelReasoningEffort, ModelSelection,
-  SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt,
   RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
   ClientRequest, ServerResponse, RpcMessage,
   HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,

+ 1 - 2
packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx

@@ -6,7 +6,7 @@ import {
   type SessionListState, type SessionProjectionMap, type SessionSummary,
   type SubagentCatalogSnapshot,
 } from '@deepseek-ai/dsh-api-session-controller/client'
-import type { SubagentAddress } from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import {
   IconChevronDownOutline14, IconChevronRightOutline14, IconRefreshOutline14, StateDot,
@@ -14,7 +14,6 @@ import {
 import type { PropsLocale, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
 import { NS } from './locales.ts'
 import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
-import type {} from '@deepseek-ai/dsh-subagent/client'
 import type {} from '@deepseek-ai/dsh-token-meter/client'
 import css from './SubagentHeaderLineage.module.css'
 import { indexSubagentDescendants } from './subagent-lineage.ts'

+ 1 - 1
packages/client/ui-subagent/src/client/index.ts

@@ -1,6 +1,6 @@
 /** Web subagent catalog, navigation, and addressed-session composer owner. */
 import type { Context as ClientContext } from '@deepseek-ai/cordis'
-import type { SubagentAddress } from '@deepseek-ai/dsh-client-connection/client'
+import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
 import { SubagentHeaderLineage, type SubagentCatalogInjected } from './SubagentHeaderLineage.tsx'