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