You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/19 19:26:17 UTC

(maka) branch feat/runtime-host-rust updated: refactor(workhub): use scoped Session and attachment adapters

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch feat/runtime-host-rust
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/feat/runtime-host-rust by this push:
     new 3d797af0b6 refactor(workhub): use scoped Session and attachment adapters
3d797af0b6 is described below

commit 3d797af0b63951ae0a6f460f30175fd31a690bc9
Author: Wang <m4...@gmail.com>
AuthorDate: Sun Sep 20 03:26:12 2026 +0800

    refactor(workhub): use scoped Session and attachment adapters
    
    Generated-by: OpenAI Codex
---
 ...t.ts => runtime-host-attachment-ingest.test.ts} | 27 +++++----
 .../runtime-host-session-catalog-ipc-main.test.ts  | 18 +++++-
 .../workhub-coordination-session-preload.test.ts   | 45 --------------
 ...workhub-coordination-transcript-preload.test.ts |  9 +--
 .../src/main/runtime-host-artifacts-ipc-main.ts    | 28 +++++++++
 apps/desktop/src/main/runtime-host-client.ts       | 24 --------
 .../src/main/runtime-host-desktop-candidate.ts     | 10 +---
 .../main/runtime-host-session-catalog-ipc-main.ts  |  8 +++
 .../src/main/runtime-host-workhub-ipc-main.ts      | 62 --------------------
 apps/desktop/src/preload/bridge-contract.d.ts      | 10 ++--
 apps/desktop/src/preload/preload.ts                | 46 ++++++---------
 .../src/preload/workhub-coordination-session.ts    | 35 -----------
 .../renderer/features/workhub/ui/workhub-root.tsx  | 68 ++++++++++++++++++----
 .../platform/desktop/create-workhub-services.ts    |  5 +-
 ...ersation.d.ts => attachment-ingest-result.d.ts} |  2 +-
 tests/fixtures/client-workhub-queue.mjs            |  2 +
 16 files changed, 158 insertions(+), 241 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-workhub-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-attachment-ingest.test.ts
similarity index 61%
rename from apps/desktop/src/main/__tests__/runtime-host-workhub-ipc-main.test.ts
rename to apps/desktop/src/main/__tests__/runtime-host-attachment-ingest.test.ts
index f13a98c8c1..8b90fa6b58 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-workhub-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-attachment-ingest.test.ts
@@ -20,30 +20,33 @@
 import assert from 'node:assert/strict';
 import test from 'node:test';
 import type { IpcHandler } from '../ipc-reconnect-policy.js';
-import { registerRuntimeHostWorkHubIpc } from '../runtime-host-workhub-ipc-main.js';
+import { registerRuntimeHostAttachmentIngestIpc } from '../runtime-host-artifacts-ipc-main.js';
 
-test('returns a structured WorkHub attachment rejection across IPC', async () => {
+test('attachment ingestion checks the explicit Session before reading files and preserves structured rejections', async () => {
   const handlers = new Map<string, IpcHandler>();
-  registerRuntimeHostWorkHubIpc(
-    {} as Parameters<typeof registerRuntimeHostWorkHubIpc>[0],
-    {
+  let available = true;
+  registerRuntimeHostAttachmentIngestIpc({
+    client: { async getSession(id) { assert.equal(id, 'target'); return available ? {} as never : null; },
+      async ingestAttachment() { throw new Error('No file may be uploaded'); } },
+    ipcMain: {
       handle(channel, handler) {
         handlers.set(channel, handler);
       },
     },
-    {
-      attachmentIngest: {
-        approvals: {} as never,
-        stat: async () => ({ size: 0 }),
-      },
+    attachmentIngest: {
+      approvals: {} as never,
+      stat: async () => ({ size: 0 }),
     },
-  );
+  });
 
-  const prepareAttachments = handlers.get('workhub:prepareAttachments');
+  const prepareAttachments = handlers.get('attachments:prepare');
   assert.ok(prepareAttachments);
   const result = await prepareAttachments(
     { sender: { id: 7 } } as Parameters<IpcHandler>[0],
+    'target',
     Array.from({ length: 9 }, () => ({})),
   );
   assert.deepEqual(result, { ok: false, code: 'count_limit' });
+  available = false;
+  await assert.rejects(prepareAttachments({ sender: { id: 7 } } as Parameters<IpcHandler>[0], 'target', []), /Session is unavailable/);
 });
diff --git a/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts
index 0f7cff7040..b2cb03a485 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts
@@ -41,8 +41,22 @@ test('maps Runtime Host live run state without collapsing unknown and known-empt
   assert.deepEqual(running.runningTurnIds, ['turn-live']);
 });
 
-test('preserves the Session revision in Owner Desktop Host summaries', () => {
-  assert.equal(toDesktopHostSessionSummary(projection({ revision: 7 })).revision, 7);
+test('exact Session reads retain revision and live execution without depending on list visibility', async () => {
+  const ipc = ipcHarness();
+  const deps = createDeps([]);
+  let present = true;
+  deps.client.getSession = async (id) => {
+    assert.equal(id, 'managed');
+    return present ? projection({ id, revision: 7 }) : null;
+  };
+  deps.runningTurnIds = (id) => { assert.equal(id, 'managed'); return ['live']; };
+  registerRuntimeHostSessionCatalogIpc(deps, ipc as unknown as IpcMain);
+  const result = await ipc.invoke('sessions:get', 'managed') as { id: string; revision: number; runningTurnIds: string[] };
+  assert.equal(result.id, 'managed');
+  assert.equal(result.revision, 7);
+  assert.deepEqual(result.runningTurnIds, ['live']);
+  present = false;
+  await assert.rejects(ipc.invoke('sessions:get', 'managed'), /not found/);
 });
 
 test('session creation forwards the caller name for a mode that carries none', async () => {
diff --git a/apps/desktop/src/main/__tests__/workhub-coordination-session-preload.test.ts b/apps/desktop/src/main/__tests__/workhub-coordination-session-preload.test.ts
deleted file mode 100644
index d5dfa23560..0000000000
--- a/apps/desktop/src/main/__tests__/workhub-coordination-session-preload.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import assert from 'node:assert/strict';
-import test from 'node:test';
-import {
-  resolveDesktopWorkHubCoordinationCreateScope,
-} from '../../preload/workhub-coordination-session.js';
-
-test('creates against the Coordination Session Host instead of later UI focus', async () => {
-  const coordination = JSON.stringify(['host-a', 'maka_workhub_coordination']);
-  const scopeA = { hostId: 'host-a', targetEpoch: 'epoch-a' };
-  const scope = await resolveDesktopWorkHubCoordinationCreateScope(
-    coordination,
-    async (sessionId) => {
-      assert.equal(sessionId, coordination);
-      return { scope: scopeA, sessionId: 'maka_workhub_coordination' };
-    },
-  );
-
-  assert.equal(scope, scopeA);
-  await assert.rejects(
-    resolveDesktopWorkHubCoordinationCreateScope(
-      JSON.stringify(['host-a', 'ordinary']),
-      async () => ({ scope: scopeA, sessionId: 'ordinary' }),
-    ),
-    /Invalid WorkHub Coordination Session identity/,
-  );
-});
diff --git a/apps/desktop/src/main/__tests__/workhub-coordination-transcript-preload.test.ts b/apps/desktop/src/main/__tests__/workhub-coordination-transcript-preload.test.ts
index aec892a0d4..34622c67df 100644
--- a/apps/desktop/src/main/__tests__/workhub-coordination-transcript-preload.test.ts
+++ b/apps/desktop/src/main/__tests__/workhub-coordination-transcript-preload.test.ts
@@ -30,7 +30,7 @@ import type { MakaBridge } from '../../preload/bridge-contract.js';
 import type { DesktopTranscriptBatch } from '../../preload/transcript-contract.js';
 import { createDesktopWorkHubServices } from '../../renderer/platform/desktop/create-workhub-services.js';
 import { desktopSessionKey } from '../../shared/runtime-host-identity.js';
-import type { WorkHubPrepareAttachmentsResult } from '../../shared/workhub-conversation.js';
+import type { PrepareAttachmentsResult } from '../../shared/attachment-ingest-result.js';
 import { encodeDesktopTranscriptBatches, encodeDesktopTranscriptSnapshot } from '../desktop-transcript-ipc.js';
 import { AttachmentIngestBlockedError } from '@maka/core/attachments';
 import type { AttachmentRef } from '@maka/core/events';
@@ -48,7 +48,7 @@ test('WorkHub upload references round-trip through idle answers, both queue mode
     kind: 'doc', name: 'brief.txt', mimeType: 'text/plain', bytes: 5,
     ref: { kind: 'session_file', sessionId: nativeSessionId, relativePath: 'brief.txt' },
   };
-  let preparationResult: WorkHubPrepareAttachmentsResult = {
+  let preparationResult: PrepareAttachmentsResult = {
     ok: true,
     attachments: [uploaded],
   };
@@ -69,8 +69,9 @@ test('WorkHub upload references round-trip through idle answers, both queue mode
           if (channel === 'runtime-host:identities') return [owner];
           assert.equal((args[0] as typeof owner).hostId, owner.hostId);
           if (channel === 'plugins:connection') return { hostEpoch: 'host-process-epoch' };
-          if (channel === 'workhub:prepareAttachments') {
-            assert.deepEqual(structuredClone(args[1]), [{ name: 'brief.txt', mimeType: 'text/plain', base64: 'aGVsbG8=' }]);
+          if (channel === 'attachments:prepare') {
+            assert.equal(args[1], nativeSessionId);
+            assert.deepEqual(structuredClone(args[2]), [{ name: 'brief.txt', mimeType: 'text/plain', base64: 'aGVsbG8=' }]);
             return preparationResult;
           }
           assert.equal(args[1], nativeSessionId);
diff --git a/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts b/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts
index 88a7b14e90..f82e214a70 100644
--- a/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts
@@ -36,6 +36,34 @@ import {
 import type { createMainWindowController } from "./main-window.js";
 import type { DesktopRuntimeHostClient } from "./runtime-host-client.js";
 import type { ManagedArtifactPreview } from './managed-artifact-preview.js';
+import { AttachmentIngestBlockedError } from '@maka/core/attachments';
+import { prepareIngestItems, resolveAttachmentRefs } from './attachment-ingest.js';
+import type { PrepareAttachmentsResult } from '../shared/attachment-ingest-result.js';
+
+/** File approval belongs to the Renderer; the explicit Session belongs to this Host. */
+export function registerRuntimeHostAttachmentIngestIpc(deps: {
+  ipcMain: ReconnectableReadIpcMain;
+  client: Pick<DesktopRuntimeHostClient, 'getSession' | 'ingestAttachment'>;
+  attachmentIngest: Pick<Parameters<typeof prepareIngestItems>[0], 'approvals' | 'stat'> & {
+    resizeImage?: (bytes: Uint8Array) => Promise<Uint8Array>;
+  };
+}): void {
+  deps.ipcMain.handle('attachments:prepare', async (event, sessionId: string, items: unknown): Promise<PrepareAttachmentsResult> => {
+    if (!await deps.client.getSession(sessionId)) throw new Error('Attachment Session is unavailable');
+    try {
+      const prepared = await prepareIngestItems({ ...deps.attachmentIngest, senderId: event.sender.id, items });
+      const refs = await resolveAttachmentRefs({
+        files: prepared.files,
+        resizeImage: deps.attachmentIngest.resizeImage,
+        snapshot: ({ name, mimeType, content }) => deps.client.ingestAttachment({ sessionId, name, mimeType, content }),
+      });
+      return { ok: true, attachments: prepared.commit(() => refs) };
+    } catch (error) {
+      if (error instanceof AttachmentIngestBlockedError) return { ok: false, code: error.code };
+      throw error;
+    }
+  });
+}
 
 interface RuntimeHostArtifactsIpcDeps {
   uiLocale(): UiLocale;
diff --git a/apps/desktop/src/main/runtime-host-client.ts b/apps/desktop/src/main/runtime-host-client.ts
index f76a31f86b..9f85c1e48d 100644
--- a/apps/desktop/src/main/runtime-host-client.ts
+++ b/apps/desktop/src/main/runtime-host-client.ts
@@ -927,30 +927,6 @@ export class DesktopRuntimeHostClient {
     );
   }
 
-  async getWorkHubSession(): Promise<SessionCatalogProjection> {
-    return requireSessionProjection(await this.request('workhub.coordination.query', {}));
-  }
-
-  answerWorkHubCoordination(input: OperationInput<'workhub.coordination.answer'>) {
-    return this.request('workhub.coordination.answer', input);
-  }
-
-  configureWorkHubModel(input: OperationInput<'workhub.coordination.configureModel'>) {
-    return this.request('workhub.coordination.configureModel', input);
-  }
-
-  listWorkHubCoordinationCandidates() {
-    return this.request("workhub.coordination.candidates", {});
-  }
-
-  selectAndDelegateWorkHubTarget(input: OperationInput<'workhub.coordination.selectAndDelegate'>) {
-    return this.request('workhub.coordination.selectAndDelegate', input);
-  }
-
-  actWorkHubCoordinationFromTurn(input: OperationInput<'workhub.coordination.actFromTurn'>) {
-    return this.request('workhub.coordination.actFromTurn', input);
-  }
-
   listExternalSessionSources(): Promise<ExternalSessionSourceQueryResult> {
     return this.request("external-session.source.query", {});
   }
diff --git a/apps/desktop/src/main/runtime-host-desktop-candidate.ts b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
index ec7ef9a390..8abfe16578 100644
--- a/apps/desktop/src/main/runtime-host-desktop-candidate.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
@@ -25,7 +25,6 @@ import { redactSecrets } from '@maka/core/redaction';
 import type { CreateSessionRequestInput } from '@maka/core/runtime-inputs';
 import { isSideConversationSession } from '@maka/core/side-conversation';
 import {
-  isWorkHubCoordinationSessionId,
   type SessionChangedEvent,
   type SessionChangedReason,
 } from '@maka/core/session';
@@ -79,13 +78,12 @@ import {
 import {
   registerRuntimeHostSessionCatalogIpc,
 } from "./runtime-host-session-catalog-ipc-main.js";
-import { registerRuntimeHostWorkHubIpc } from "./runtime-host-workhub-ipc-main.js";
 import { registerRuntimeHostExternalSessionsIpc } from "./runtime-host-external-sessions-ipc-main.js";
 import { registerRuntimeHostSessionBundleIpc } from "./runtime-host-session-bundle-ipc-main.js";
 import { registerRuntimeHostCollaborationIpc } from './runtime-host-collaboration-ipc-main.js';
 import { TerminalCloseIntents } from './terminal-close-intents.js';
 import type { DesktopCollaborationConnectionTarget } from './runtime-host-collaboration-invitation.js';
-import { registerRuntimeHostAttachmentPreviewIpc } from './runtime-host-artifacts-ipc-main.js';
+import { registerRuntimeHostAttachmentPreviewIpc, registerRuntimeHostAttachmentIngestIpc } from './runtime-host-artifacts-ipc-main.js';
 import {
   registerRuntimeHostSessionDomainsIpc,
   type RuntimeHostSessionDomainsIpcDeps,
@@ -732,9 +730,7 @@ export async function createDesktopRuntimeHostCandidate(
         observations: sessionObservations,
         resolveSideConversation: async (sessionId) => {
           if (target.access === 'session_guest') return false;
-          const session = isWorkHubCoordinationSessionId(sessionId)
-            ? await client.getWorkHubSession()
-            : await client.getSession(sessionId);
+          const session = await client.getSession(sessionId);
           if (!session) throw new Error(`Runtime Host Session not found: ${sessionId}`);
           return isSideConversationSession(session.labels);
         },
@@ -904,7 +900,7 @@ export async function createDesktopRuntimeHostCandidate(
       throw new Error('This Runtime Host does not have a shareable connection target');
     });
     if (target.access === 'owner') {
-      registerRuntimeHostWorkHubIpc(client, ipc, {
+      registerRuntimeHostAttachmentIngestIpc({ client, ipcMain: ipc,
         attachmentIngest: { approvals: deps.attachmentApprovals, stat: deps.stat, resizeImage: deps.resizeImage },
       });
       registerRuntimeHostExternalSessionsIpc(
diff --git a/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts b/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts
index a221e8fc03..2271037f1e 100644
--- a/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts
@@ -56,6 +56,7 @@ type RuntimeHostSessionCatalogClient = Pick<
   DesktopRuntimeHostClient,
   | 'createSession'
   | 'listSessions'
+  | 'getSession'
   | 'previewSessionRemoval'
   | 'removeSession'
   | 'setSessionLifecycle'
@@ -115,6 +116,13 @@ export function registerRuntimeHostSessionCatalogIpc(
   handleReconnectableRead(ipcMain, 'sessions:list', (_event, filter?: unknown) =>
     listSessions(normalizeSessionListFilter(filter)),
   );
+  handleReconnectableRead(ipcMain, 'sessions:get', async (_event, sessionId: string) => {
+    await recoveryTask;
+    if (pendingCleanup.has(sessionId)) throw new Error('Session copy cleanup is pending');
+    const session = await deps.client.getSession(sessionId);
+    if (!session) throw new Error(`Runtime Host Session not found: ${sessionId}`);
+    return toDesktopHostSessionListSummary(session, deps.runningTurnIds(sessionId));
+  });
   ipcMain.handle('sessions:cleanupSessionCopy', async (_event, sessionId: string) => {
     await deps.sessionCopyCleanup.cleanup(sessionId);
     pendingCleanup.delete(sessionId);
diff --git a/apps/desktop/src/main/runtime-host-workhub-ipc-main.ts b/apps/desktop/src/main/runtime-host-workhub-ipc-main.ts
deleted file mode 100644
index 31cdb89b07..0000000000
--- a/apps/desktop/src/main/runtime-host-workhub-ipc-main.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { WORKHUB_COORDINATION_SESSION_ID } from '@maka/core/session';
-import { AttachmentIngestBlockedError } from '@maka/core/attachments';
-import { prepareIngestItems, resolveAttachmentRefs } from './attachment-ingest.js';
-import type { DesktopRuntimeHostClient } from './runtime-host-client.js';
-import type { ReconnectableReadIpcMain } from './ipc-reconnect-policy.js';
-import type {
-  WorkHubPrepareAttachmentsResult,
-} from '../shared/workhub-conversation.js';
-import { toDesktopHostSessionSummary } from './runtime-host-session-catalog-ipc-main.js';
-
-type RuntimeHostWorkHubClient = Pick<
-  DesktopRuntimeHostClient,
-  | 'ingestAttachment'
-  | 'getWorkHubSession'
->;
-
-export interface RuntimeHostWorkHubIpcOptions {
-  attachmentIngest?: Pick<Parameters<typeof prepareIngestItems>[0], 'approvals' | 'stat'> & { resizeImage?: (bytes: Uint8Array) => Promise<Uint8Array> };
-}
-
-/** Projects the Runtime Host WorkHub domain onto renderer IPC. */
-export function registerRuntimeHostWorkHubIpc(
-  client: RuntimeHostWorkHubClient,
-  ipcMain: ReconnectableReadIpcMain,
-  options: RuntimeHostWorkHubIpcOptions,
-): void {
-  ipcMain.handle('workhub:getSession', async () => toDesktopHostSessionSummary(await client.getWorkHubSession()));
-  ipcMain.handle('workhub:prepareAttachments', async (event, items: unknown): Promise<WorkHubPrepareAttachmentsResult> => {
-    if (!options.attachmentIngest) throw new Error('WorkHub attachments are unavailable');
-    try {
-      const prepared = await prepareIngestItems({ ...options.attachmentIngest, senderId: event.sender.id, items });
-      const refs = await resolveAttachmentRefs({
-        files: prepared.files,
-        resizeImage: options.attachmentIngest.resizeImage,
-        snapshot: ({ name, mimeType, content }) => client.ingestAttachment({ sessionId: WORKHUB_COORDINATION_SESSION_ID, name, mimeType, content }),
-      });
-      return { ok: true, attachments: prepared.commit(() => refs) };
-    } catch (error) {
-      if (error instanceof AttachmentIngestBlockedError) return { ok: false, code: error.code };
-      throw error;
-    }
-  });
-}
diff --git a/apps/desktop/src/preload/bridge-contract.d.ts b/apps/desktop/src/preload/bridge-contract.d.ts
index 496b52f672..716aff6540 100644
--- a/apps/desktop/src/preload/bridge-contract.d.ts
+++ b/apps/desktop/src/preload/bridge-contract.d.ts
@@ -19,8 +19,8 @@
 
 import type { ClientIdentity, ClientFileRequest } from '@maka-agent/plugin-sdk/client';
 import type {
-  WorkHubPrepareAttachmentsResult,
-} from '../shared/workhub-conversation.js';
+  PrepareAttachmentsResult,
+} from '../shared/attachment-ingest-result.js';
 import type { ConnectionEvent } from '@maka/core/connections';
 import type {
   ConnectionTestResult,
@@ -1104,11 +1104,8 @@ export interface MakaBridge {
       handler: () => void,
     ): () => void;
   };
-  workHub: {
-    getSession(coordinationSessionId: string): Promise<DesktopSessionSummary>;
-    prepareAttachments(coordinationSessionId: string, items: RendererIngestInput[]): Promise<WorkHubPrepareAttachmentsResult>;
-  };
   sessions: {
+    get(sessionId: string): Promise<DesktopSessionSummary>;
     list(filter?: SessionListFilter): Promise<DesktopSessionSummary[]>;
     listWithCoverage(): Promise<{
       sessions: DesktopSessionSummary[];
@@ -1618,6 +1615,7 @@ export interface MakaBridge {
     openBackup(kind: 'save' | 'reset' | 'restore', host?: DesktopRuntimeHostRef): Promise<{ ok: true } | { ok: false; code: string }>;
   };
   attachments: {
+    prepare(sessionId: string, items: RendererIngestInput[]): Promise<PrepareAttachmentsResult>;
     pickDirectory(): Promise<{ ok: true; reference: import('@maka/core/events').DirectoryReference } | { ok: false; reason: 'cancelled' }>;
     pickFiles(): Promise<
       | {
diff --git a/apps/desktop/src/preload/preload.ts b/apps/desktop/src/preload/preload.ts
index d271cd0d3a..c00e89354c 100644
--- a/apps/desktop/src/preload/preload.ts
+++ b/apps/desktop/src/preload/preload.ts
@@ -23,8 +23,8 @@ import type {
 } from './bridge-contract.js';
 
 import type {
-  WorkHubPrepareAttachmentsResult,
-} from '../shared/workhub-conversation.js';
+  PrepareAttachmentsResult,
+} from '../shared/attachment-ingest-result.js';
 import type { SessionObservationMessage } from '../shared/session-execution-projection.js';
 import { contextBridge, ipcRenderer } from 'electron';
 import { HOST_OPERATION_SPECS } from '@maka/runtime-host/protocol';
@@ -38,9 +38,6 @@ import { AttachmentIngestBlockedError } from '@maka/core/attachments';
 import { encodeIngestItems } from './attachment-ingest-payload.js';
 import { createThreadSearchClient } from './multi-host-thread-search.js';
 import { releaseSessionObservation } from './session-observation-release.js';
-import {
-  resolveDesktopWorkHubCoordinationCreateScope,
-} from './workhub-coordination-session.js';
 import type {
   MakaBridge,
   OnboardingSnapshot,
@@ -2094,30 +2091,6 @@ const makaBridge = {
       };
     },
   },
-  workHub: {
-    async getSession(coordinationSessionId: string) {
-      const scope = await resolveDesktopWorkHubCoordinationCreateScope(coordinationSessionId, runtimeHostSessionRef);
-      return projectSessionSummary(scope, await ipcRenderer.invoke('workhub:getSession', scope));
-    },
-    async prepareAttachments(coordinationSessionId: string, items: Parameters<MakaBridge['workHub']['prepareAttachments']>[1]) {
-      const scope = await resolveDesktopWorkHubCoordinationCreateScope(coordinationSessionId, runtimeHostSessionRef);
-      let encoded: Awaited<ReturnType<typeof encodeIngestItems>>;
-      try {
-        encoded = await encodeIngestItems(items);
-      } catch (error) {
-        if (error instanceof AttachmentIngestBlockedError) return { ok: false, code: error.code };
-        throw error;
-      }
-      const result = await ipcRenderer.invoke(
-        'workhub:prepareAttachments',
-        scope,
-        encoded,
-      ) as WorkHubPrepareAttachmentsResult;
-      return result.ok
-        ? { ok: true, attachments: projectDesktopAttachmentRefs(scope, result.attachments) }
-        : result;
-    },
-  },
   sessionLocal: {
     async listMessages(sessionId) {
       const session = await runtimeHostSessionRef(sessionId);
@@ -2143,6 +2116,10 @@ const makaBridge = {
     },
   } satisfies import('../shared/session-local-contract.js').DesktopSessionLocalBridge,
   sessions: {
+    async get(sessionId: string) {
+      const session = await runtimeHostSessionRef(sessionId);
+      return projectSessionSummary(session.scope, await ipcRenderer.invoke('sessions:get', session.scope, session.sessionId));
+    },
     list(filter?: SessionListFilter): Promise<DesktopSessionSummary[]> {
       return listDesktopSessions(filter);
     },
@@ -3178,6 +3155,17 @@ const makaBridge = {
     },
   },
   attachments: {
+    async prepare(sessionId: string, items: RendererIngestInput[]) {
+      const session = await runtimeHostSessionRef(sessionId);
+      let encoded: Awaited<ReturnType<typeof encodeIngestItems>>;
+      try { encoded = await encodeIngestItems(items); }
+      catch (error) {
+        if (error instanceof AttachmentIngestBlockedError) return { ok: false, code: error.code };
+        throw error;
+      }
+      const result = await ipcRenderer.invoke('attachments:prepare', session.scope, session.sessionId, encoded) as PrepareAttachmentsResult;
+      return result.ok ? { ok: true, attachments: projectDesktopAttachmentRefs(session.scope, result.attachments) } : result;
+    },
     pickDirectory: () => ipcRenderer.invoke('directories:pick'),
     pickFiles(): Promise<
       | {
diff --git a/apps/desktop/src/preload/workhub-coordination-session.ts b/apps/desktop/src/preload/workhub-coordination-session.ts
deleted file mode 100644
index 78b58ad0cb..0000000000
--- a/apps/desktop/src/preload/workhub-coordination-session.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { WORKHUB_COORDINATION_SESSION_ID } from '@maka/core/session';
-import type { DesktopTargetScope } from '../shared/runtime-host-identity.js';
-
-/** Resolves an exact Host scope from the durable Coordination identity, never from UI focus. */
-export async function resolveDesktopWorkHubCoordinationCreateScope(
-  coordinationSessionId: string,
-  resolveSession: (
-    sessionId: string,
-  ) => Promise<{ readonly scope: DesktopTargetScope; readonly sessionId: string }>,
-): Promise<DesktopTargetScope> {
-  const coordination = await resolveSession(coordinationSessionId);
-  if (coordination.sessionId !== WORKHUB_COORDINATION_SESSION_ID) {
-    throw new Error('Invalid WorkHub Coordination Session identity');
-  }
-  return coordination.scope;
-}
diff --git a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
index c4ddf88f91..e8be1f2221 100644
--- a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
+++ b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
@@ -19,6 +19,7 @@
 
 import { useCallback, useMemo } from 'react';
 import type { HostAttachments } from '@maka/workhub/slots';
+import type { CoordinationSessionAdapter } from '@maka/workhub/controller';
 import type { WorkHubRootProps, WorkHubAttachmentServices, WorkHubWindowServices } from '@maka/workhub/surface';
 import { useUiLocale } from '@maka/ui';
 import { ClientPluginSlot, type ClientHostRef } from '../../client-plugins/index.js';
@@ -32,23 +33,68 @@ export function WorkHubRoot({ host, ...props }: Pick<WorkHubRootProps, 'sessionI
   const locale = useUiLocale();
   const services = useWorkHubServices();
   const continuation = useMemo<NonNullable<WorkHubRootProps['continuation']>>(() => ({}), [host.hostId]);
+  const sessionKey = useCallback((id: string) => {
+    if (parseDesktopSessionKey(id).hostId !== host.hostId) throw new Error('WorkHub Session belongs to another Host');
+    return id;
+  }, [host.hostId]);
+  const sessions = useMemo<CoordinationSessionAdapter>(() => ({
+    getSession: (id) => services.getSession(sessionKey(id)),
+    listSessions: async () => (await services.listSessions()).filter((session) => parseDesktopSessionKey(session.id).hostId === host.hostId),
+    subscribeSessions: services.subscribeSessions,
+    subscribeAvailability: services.subscribeAvailability,
+    modelChoices: (id) => services.modelChoices(sessionKey(id)),
+    listActiveInteractions: (id) => services.listActiveInteractions(sessionKey(id)),
+    subscribeActiveInteractions: (handler) => services.subscribeActiveInteractions((event) => {
+      if (parseDesktopSessionKey(event.sessionId).hostId === host.hostId) handler(event);
+    }),
+    respondToUserForm: (id, input) => services.respondToUserForm(sessionKey(id), input),
+    respondToUserQuestion: (id, input) => services.respondToUserQuestion(sessionKey(id), input),
+    retractQueueEntry: (id, entry) => services.retractQueueEntry(sessionKey(id), entry),
+    promoteQueueEntry: (id, entry) => services.promoteQueueEntry(sessionKey(id), entry),
+    updateQueueEntry: (id, entry, revision, text) => services.updateQueueEntry(sessionKey(id), entry, revision, text),
+    reorderQueueEntries: (id, entries) => services.reorderQueueEntries(sessionKey(id), entries),
+    stop: (id, turn) => services.stop(sessionKey(id), turn),
+    observe: (id, ...args) => services.observe(sessionKey(id), ...args),
+    openTranscript: (id, ...args) => services.openTranscript(sessionKey(id), ...args),
+  }), [services, sessionKey, host.hostId]);
   const native = useMemo<WorkHubWindowServices>(() => ({
-    presentation: services.presentation,
+    presentation: {
+      getSnapshot: () => services.presentation.getSnapshot(),
+      setConversationLayout: (layout) => services.presentation.setConversationLayout(layout),
+      progressReady: (request) => services.presentation.progressReady(request),
+      resizeProgress: (request, height) => services.presentation.resizeProgress(request, height),
+      expandProgress: (request) => services.presentation.expandProgress(request),
+      detach: () => services.presentation.detach(),
+      dock: () => services.presentation.dock(),
+      hide: () => services.presentation.hide(),
+      openUsage: () => services.presentation.openUsage(),
+      toggleWorkbar: () => services.presentation.toggleWorkbar(),
+      openSession: (id) => services.presentation.openSession(sessionKey(id)),
+      subscribe: (handler) => services.presentation.subscribe(handler),
+      onViewportInset: (handler) => services.presentation.onViewportInset(handler),
+      onFocusComposer: (handler) => services.presentation.onFocusComposer(handler),
+    },
     control: services.control,
-    bindBrowserSession: services.bindBrowserSession,
-  }), [services]);
+    bindBrowserSession: (id) => services.bindBrowserSession(id === null ? null : sessionKey(id)),
+  }), [services, sessionKey]);
   const attachments = useMemo<WorkHubAttachmentServices>(() => ({
-    staging: services.attachments,
-    read: services.readAttachmentBytes,
-    prepare: services.prepareAttachments,
+    staging: {
+      pickFiles: () => services.attachments.pickFiles(),
+      previewApproval: (id) => services.attachments.previewApproval(id),
+    },
+    read: (id, artifact) => services.readAttachmentBytes(sessionKey(id), artifact),
+    prepare: (id, items) => services.prepareAttachments(sessionKey(id), items),
     copy: (locale) => getDesktopConversationCopy(locale).actions,
     formatError: localizedShellErrorMessage,
-  }), [services]);
+  }), [services, sessionKey]);
+  const contextUsage = useMemo<WorkHubRootProps['contextUsage']>(() => ({
+    context: (id) => services.inspector.context(sessionKey(id)),
+    subscribeSessionEvents: (id, handler) => services.inspector.subscribeSessionEvents(sessionKey(id), handler),
+  }), [services, sessionKey]);
   const hostAttachments = useCallback<HostAttachments>((sessionId, refs) => {
-    const session = parseDesktopSessionKey(sessionId);
-    if (session.hostId !== host.hostId) throw new Error('WorkHub Session belongs to another Host');
+    const session = parseDesktopSessionKey(sessionKey(sessionId));
     return hostAttachmentRefs({ scope: host, sessionId: session.sessionId }, refs);
-  }, [host.hostId]);
+  }, [host.hostId, sessionKey]);
   return <ClientPluginSlot host={host} entryId="maka.workhub.ui" name="workhub.surface" className="workHubPluginSurface"
-    input={{ ...props, locale, continuation, sessions: services, native, attachments, hostAttachments, contextUsage: services.inspector }} />;
+    input={{ ...props, locale, continuation, sessions, native, attachments, hostAttachments, contextUsage }} />;
 }
diff --git a/apps/desktop/src/renderer/platform/desktop/create-workhub-services.ts b/apps/desktop/src/renderer/platform/desktop/create-workhub-services.ts
index d33190a7cb..0b0929a756 100644
--- a/apps/desktop/src/renderer/platform/desktop/create-workhub-services.ts
+++ b/apps/desktop/src/renderer/platform/desktop/create-workhub-services.ts
@@ -35,7 +35,6 @@ export function createDesktopWorkHubServices(
     MakaBridge,
     | 'browser'
     | 'inspector'
-    | 'workHub'
     | 'workHubControl'
     | 'workHubPresentation'
     | 'sessions'
@@ -73,7 +72,7 @@ export function createDesktopWorkHubServices(
     },
     presentation: bridge.workHubPresentation,
     control: bridge.workHubControl,
-    getSession: (sessionId) => bridge.workHub.getSession(sessionId),
+    getSession: (sessionId) => bridge.sessions.get(sessionId),
     subscribeAvailability: (handler) => bridge.connections.subscribeEvents(() => handler()),
     listSessions: () => bridge.sessions.list(),
     subscribeSessions: (handler) => bridge.sessions.subscribeChanges(handler),
@@ -82,7 +81,7 @@ export function createDesktopWorkHubServices(
     attachments: bridge.attachments,
     readAttachmentBytes: bridge.attachments.readBytes,
     prepareAttachments: async (sessionId, items) => {
-      const result = await bridge.workHub.prepareAttachments(sessionId, items);
+      const result = await bridge.attachments.prepare(sessionId, items);
       if (!result.ok) throw new AttachmentIngestBlockedError(result.code);
       return result.attachments;
     },
diff --git a/apps/desktop/src/shared/workhub-conversation.d.ts b/apps/desktop/src/shared/attachment-ingest-result.d.ts
similarity index 95%
rename from apps/desktop/src/shared/workhub-conversation.d.ts
rename to apps/desktop/src/shared/attachment-ingest-result.d.ts
index 938a019e4a..84ccf3c4d2 100644
--- a/apps/desktop/src/shared/workhub-conversation.d.ts
+++ b/apps/desktop/src/shared/attachment-ingest-result.d.ts
@@ -20,6 +20,6 @@
 import type { AttachmentIngestBlockedCode } from '@maka/core/attachments';
 import type { AttachmentRef } from '@maka/core/events';
 
-export type WorkHubPrepareAttachmentsResult =
+export type PrepareAttachmentsResult =
   | { readonly ok: true; readonly attachments: AttachmentRef[] }
   | { readonly ok: false; readonly code: AttachmentIngestBlockedCode };
diff --git a/tests/fixtures/client-workhub-queue.mjs b/tests/fixtures/client-workhub-queue.mjs
index 9b5584bfad..b0c71d9b39 100644
--- a/tests/fixtures/client-workhub-queue.mjs
+++ b/tests/fixtures/client-workhub-queue.mjs
@@ -124,6 +124,8 @@ export async function verifyWorkhubQueue(connection) {
     let remote = await workhubRemote(connection);
     remotes.push(remote);
     await remote.method('resolve')();
+    const lookup = await request('session.catalog.query', { kind: 'get', sessionId });
+    assert.deepEqual(lookup.session, await remote.method('query')());
     const turnId = 'queue-root';
     await remote.method('answer')({ turnId, text: 'INITIAL' });
     await first.promise;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org