diff --git a/apps/desktop/scripts/vite-workspace-packages.test.mjs b/apps/desktop/scripts/vite-workspace-packages.test.mjs index 6b54f44fe2..ed7c711fb1 100644 --- a/apps/desktop/scripts/vite-workspace-packages.test.mjs +++ b/apps/desktop/scripts/vite-workspace-packages.test.mjs @@ -79,3 +79,28 @@ test('renderer loads a newly exported workspace module after its manifest change } throw failure; }); + +test('renderer-facing Runtime Host protocol does not load Node crypto', async (t) => { + const repoRoot = await realpath(new URL('../../..', import.meta.url)); + const root = join(repoRoot, 'apps/desktop/src/renderer'); + const server = await createServer({ + configFile: false, + root, + logLevel: 'silent', + server: { host: '127.0.0.1', port: 0 }, + optimizeDeps: { noDiscovery: true, include: [] }, + plugins: [workspacePackagesPlugin(repoRoot)], + }); + t.after(() => server.close()); + await server.listen(); + + const protocolModule = join( + repoRoot, + 'packages/runtime-host/dist/protocol/client-capability.js', + ); + const response = await fetch(`${server.resolvedUrls.local[0]}@fs/${protocolModule}`); + const transformed = await response.text(); + + assert.equal(response.status, 200, transformed); + assert.doesNotMatch(transformed, /vite-browser-external:node:crypto/u); +}); diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts index 69c98b496c..8c49cf8813 100644 --- a/apps/desktop/src/main/runtime-host-boot.ts +++ b/apps/desktop/src/main/runtime-host-boot.ts @@ -64,7 +64,8 @@ import { openRuntimeHostPeerEndpointOwner, type RuntimeHostPeerEndpointOwner, } from '@maka/runtime-host/peer-reachability'; -import { clientCapabilityEntityId, type WorkspaceTarget } from "@maka/runtime-host/protocol"; +import { clientCapabilityEntityId } from "@maka/runtime-host/client-capability-entity-id"; +import type { WorkspaceTarget } from "@maka/runtime-host/protocol"; import { runtimeHostProfileUsesHostWorkspace } from "@maka/runtime-host/profile-kind"; import { createCredentialMcpOAuthStorage, McpClientManager } from "@maka/mcp"; import { createWorkBoardStore } from "@maka/storage/work-board-store"; diff --git a/apps/desktop/src/main/runtime-host-native-capabilities.ts b/apps/desktop/src/main/runtime-host-native-capabilities.ts index ad6698a2b9..a4df30f0f1 100644 --- a/apps/desktop/src/main/runtime-host-native-capabilities.ts +++ b/apps/desktop/src/main/runtime-host-native-capabilities.ts @@ -30,7 +30,6 @@ import { CLIENT_CAPABILITY_MAX_OFFERS, CLIENT_CAPABILITY_MAX_TOOLS, CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER, - clientCapabilityEntityId, decodeClientCapabilityReplaceInput, decodeClientCapabilityToolDescriptor, type ClientCapabilityCallFrame, @@ -42,6 +41,7 @@ import { type ClientCapabilityServiceOffer, type ClientCapabilityToolDescriptor, } from "@maka/runtime-host/protocol"; +import { clientCapabilityEntityId } from "@maka/runtime-host/client-capability-entity-id"; import { toJSONSchema, z } from "zod"; import { withBrowserOriginAdmission } from './browser/browser-origin-admission.js'; import type { DesktopTargetScope } from '../shared/runtime-host-identity.js'; diff --git a/packages/cli/src/mcp-capability-provider.ts b/packages/cli/src/mcp-capability-provider.ts index 99c2fae840..92f331fd58 100644 --- a/packages/cli/src/mcp-capability-provider.ts +++ b/packages/cli/src/mcp-capability-provider.ts @@ -21,10 +21,10 @@ import { createHash } from 'node:crypto'; import type { McpBoundTool, McpToolBinding } from '@maka/core/mcp'; import type { McpClientManager } from '@maka/mcp'; import type { ClientCapabilityProvider } from '@maka/runtime-host/client'; +import { clientCapabilityEntityId } from '@maka/runtime-host/client-capability-entity-id'; import { CLIENT_CAPABILITY_MAX_TOOLS, CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER, - clientCapabilityEntityId, decodeClientCapabilityReplaceInput, type ClientCapabilityCallResult, type ClientCapabilityOffer, diff --git a/packages/runtime-host/package.json b/packages/runtime-host/package.json index 75a7a3f099..bd2aa832a2 100644 --- a/packages/runtime-host/package.json +++ b/packages/runtime-host/package.json @@ -9,6 +9,7 @@ "./adapter": "./dist/adapter/index.js", "./protocol": "./dist/protocol/index.js", "./client": "./dist/client/index.js", + "./client-capability-entity-id": "./dist/client-capability-entity-id.js", "./webrtc-stun-policy": "./dist/webrtc-stun-policy.js", "./peer-reachability": "./dist/peer-reachability/index.js", "./peer-mesh": "./dist/peer-mesh/index.js", diff --git a/packages/runtime-host/protocol-compatible-changes/client-capability-entity-id-boundary.json b/packages/runtime-host/protocol-compatible-changes/client-capability-entity-id-boundary.json new file mode 100644 index 0000000000..64d35cacb5 --- /dev/null +++ b/packages/runtime-host/protocol-compatible-changes/client-capability-entity-id-boundary.json @@ -0,0 +1,5 @@ +{ + "epoch": 109, + "files": ["packages/runtime-host/src/protocol/client-capability.ts"], + "reason": "Moves the Node-only clientCapabilityEntityId helper out of the browser-facing protocol module (#4700). Wire shape, validation limits, and error behavior are unchanged; older and newer peers decode identical frames." +} diff --git a/packages/runtime-host/src/client-capability-entity-id.ts b/packages/runtime-host/src/client-capability-entity-id.ts new file mode 100644 index 0000000000..12460fe4ef --- /dev/null +++ b/packages/runtime-host/src/client-capability-entity-id.ts @@ -0,0 +1,28 @@ +/* + * 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 { createHash } from 'node:crypto'; + +/** Normalize an arbitrary Client Capability identity into a wire-safe entity id. */ +export function clientCapabilityEntityId(value: string, maxLength = 128): string { + if (/^[A-Za-z0-9_-]+$/u.test(value) && value.length <= maxLength) return value; + const label = value.replace(/[^A-Za-z0-9_-]+/gu, '_').slice(0, maxLength - 25) || 'mcp'; + const digest = createHash('sha256').update(value).digest('hex').slice(0, 24); + return `${label}_${digest}`; +} diff --git a/packages/runtime-host/src/protocol/client-capability.ts b/packages/runtime-host/src/protocol/client-capability.ts index 9e9c43c535..924cf66462 100644 --- a/packages/runtime-host/src/protocol/client-capability.ts +++ b/packages/runtime-host/src/protocol/client-capability.ts @@ -17,7 +17,6 @@ * under the License. */ -import { createHash } from 'node:crypto'; import { TOOL_ACTIVITY_KINDS, type ToolActivityKind } from '@maka/core/events'; import { decodeInteractionAnswer, @@ -85,18 +84,6 @@ export const CLIENT_CAPABILITY_MAX_OFFERS = 32; export const CLIENT_CAPABILITY_MAX_SERVICES = 32; export const CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER = 64; -/** - * Normalize an arbitrary Client Capability identity (an MCP server id or tool - * name from user configuration) into a wire-safe entity id: identities that - * already fit pass through unchanged, anything else becomes a readable label - * plus a collision-proof digest of the original value. - */ -export function clientCapabilityEntityId(value: string, maxLength = 128): string { - if (/^[A-Za-z0-9_-]+$/u.test(value) && value.length <= maxLength) return value; - const label = value.replace(/[^A-Za-z0-9_-]+/gu, '_').slice(0, maxLength - 25) || 'mcp'; - const digest = createHash('sha256').update(value).digest('hex').slice(0, 24); - return `${label}_${digest}`; -} export const CLIENT_CAPABILITY_MAX_TOOLS = 256; export const CLIENT_CAPABILITY_MAX_MANIFEST_BYTES = 56 * 1024; export const CLIENT_CAPABILITY_MAX_RESULT_BYTES = 24 * 1024 * 1024;