Compare commits

...

1 Commits

Author SHA1 Message Date
ONLY-yours da3d3d33f3 🐛 fix: restore userId in gateway dispatch, gate local-system by executionTarget, add device switcher for regular agents
- Fix GatewayHttpClient.dispatchAgentRun stripping userId from request body,
  causing 'Missing userId' error when routing Claude Code to desktop device
- Gate activeDeviceId=undefined when executionTarget='sandbox' so local-system
  tools are not injected in sandbox mode
- Add HeteroDeviceSwitcher to RuntimeConfig for regular agents (lab flag gated)
  so users can select a desktop device for local-system tool execution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:28:34 +08:00
3 changed files with 27 additions and 12 deletions
+1 -2
View File
@@ -85,8 +85,7 @@ export class GatewayHttpClient {
topicId: string;
userId: string;
}): Promise<{ success: boolean; error?: string }> {
const { userId: _userId, ...body } = params;
const res = await this.post('/api/device/agent/run', body);
const res = await this.post('/api/device/agent/run', params);
if (!res.ok) {
const text = await res.text().catch(() => '');
return { error: text || `HTTP ${res.status}`, success: false };
@@ -19,6 +19,8 @@ import { useAgentStore } from '@/store/agent';
import { agentByIdSelectors, chatConfigByIdSelectors } from '@/store/agent/selectors';
import { useChatStore } from '@/store/chat';
import { topicSelectors } from '@/store/chat/selectors';
import { useUserStore } from '@/store/user';
import { labPreferSelectors } from '@/store/user/selectors';
import ContextWindow from '../ActionBar/Token';
import { useAgentId } from '../hooks/useAgentId';
@@ -27,6 +29,7 @@ import { useChatInputStore } from '../store';
import ApprovalMode from './ApprovalMode';
import CloudRepoSwitcher from './CloudRepoSwitcher';
import GitStatus from './GitStatus';
import HeteroDeviceSwitcher from './HeteroDeviceSwitcher';
import ModeSelector from './ModeSelector';
import { useRepoType } from './useRepoType';
import WorkingDirectory from './WorkingDirectory';
@@ -118,6 +121,10 @@ const RuntimeConfig = memo(() => {
agentByIdSelectors.getAgentEnableModeById(agentId)(s),
]);
const enableExecutionDeviceSwitcher = useUserStore(
labPreferSelectors.enableExecutionDeviceSwitcher,
);
const topicWorkingDirectory = useChatStore(topicSelectors.currentTopicWorkingDirectory);
const agentWorkingDirectory = useAgentStore((s) =>
agentId ? agentByIdSelectors.getAgentWorkingDirectoryById(agentId)(s) : undefined,
@@ -285,6 +292,9 @@ const RuntimeConfig = memo(() => {
{/* Left: Chat mode switcher + (agent-only) runtime env + working directory */}
<Flexbox horizontal align={'center'} gap={4}>
<ModeSelector />
{enableAgentMode && !isHeterogeneous && enableExecutionDeviceSwitcher && agentId && (
<HeteroDeviceSwitcher agentId={agentId} />
)}
{enableAgentMode && (
<>
<Popover
+16 -10
View File
@@ -1223,7 +1223,11 @@ export class AiAgentService {
// bypassing the engine's enabledToolIds exclusion. Skipping the
// assignment here closes that bypass at the source.
//
// Resolution order ():
// Resolution order:
// 0. executionTarget === 'sandbox': always skip — sandbox and device are
// mutually exclusive. Without this gate a single online device would
// be auto-activated and local-system tool calls would silently route
// to that device instead of being suppressed for the sandbox session.
// 1. boundDeviceId (topic-bound > agent-bound): use if online; if offline,
// respect the explicit choice and stay unrouted — don't silently fall
// back to a different device, that would surprise the user.
@@ -1235,15 +1239,17 @@ export class AiAgentService {
// behind (the local-system system prompt's
// `{{workingDirectory}}` reached the LLM as a literal, wasting the
// first N steps groping for cwd).
activeDeviceId = !canUseDevice
? undefined
: boundDeviceId
? onlineDevices.some((device) => device.deviceId === boundDeviceId)
? boundDeviceId
: undefined
: onlineDevices.length === 1
? onlineDevices[0].deviceId
: undefined;
const regularAgentExecutionTarget = agentConfig.agencyConfig?.executionTarget;
activeDeviceId =
!canUseDevice || regularAgentExecutionTarget === 'sandbox'
? undefined
: boundDeviceId
? onlineDevices.some((device) => device.deviceId === boundDeviceId)
? boundDeviceId
: undefined
: onlineDevices.length === 1
? onlineDevices[0].deviceId
: undefined;
const toolsEngine = createServerAgentToolsEngine(toolsContext, {
additionalManifests: [...lobehubSkillManifests, ...klavisManifests],