Use remote workspace for OpenClaw execution

This commit is contained in:
Haitao Pan 2026-05-26 13:42:28 +08:00
parent 42a8512010
commit 49e7a60e47
3 changed files with 80 additions and 6 deletions

View File

@ -452,6 +452,11 @@ extension AppControllerDesktopThreadActions on AppController {
final capturedLocalAttachments = List<CollaborationAttachment>.unmodifiable(
localAttachments,
);
final executionWorkingDirectory = gatewayExecutionWorkingDirectoryInternal(
target: currentTarget,
workingDirectory: workingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
);
if (usesOpenClawGatewayQueueInternal(currentTarget, provider)) {
await enqueueOpenClawGatewayTurnInternal(
OpenClawGatewayQueuedTurnInternal(
@ -465,7 +470,8 @@ extension AppControllerDesktopThreadActions on AppController {
selectedSkillLabels: capturedSelectedSkillLabels,
attachments: capturedAttachments,
localAttachments: capturedLocalAttachments,
workingDirectory: workingDirectory,
workingDirectory: executionWorkingDirectory,
localWorkingDirectory: workingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
model: model,
routing: routing,
@ -489,6 +495,8 @@ extension AppControllerDesktopThreadActions on AppController {
attachments: capturedAttachments,
localAttachments: capturedLocalAttachments,
workingDirectory: workingDirectory,
localWorkingDirectory: workingDirectory,
executionWorkingDirectory: executionWorkingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
model: model,
routing: routing,
@ -511,6 +519,8 @@ extension AppControllerDesktopThreadActions on AppController {
required List<GatewayChatAttachmentPayload> attachments,
required List<CollaborationAttachment> localAttachments,
required String workingDirectory,
String? localWorkingDirectory,
String? executionWorkingDirectory,
required String remoteWorkingDirectoryHint,
required String model,
required ExternalCodeAgentAcpRoutingConfig routing,
@ -530,7 +540,8 @@ extension AppControllerDesktopThreadActions on AppController {
final taskPrompt = taskWorkspaceContextPromptInternal(
sessionKey: sessionKey,
userPrompt: messageWithSkills,
workingDirectory: workingDirectory,
workingDirectory: localWorkingDirectory ?? workingDirectory,
executionWorkingDirectory: executionWorkingDirectory ?? workingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
);
if (appendUserTurn) {
@ -545,7 +556,7 @@ extension AppControllerDesktopThreadActions on AppController {
target: target,
provider: provider,
prompt: taskPrompt,
workingDirectory: workingDirectory,
workingDirectory: executionWorkingDirectory ?? workingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
model: model,
thinking: thinking,
@ -614,6 +625,7 @@ extension AppControllerDesktopThreadActions on AppController {
required String sessionKey,
required String userPrompt,
required String workingDirectory,
String? executionWorkingDirectory,
required String remoteWorkingDirectoryHint,
}) {
final requestText = userPrompt.trim().isEmpty
@ -624,12 +636,14 @@ extension AppControllerDesktopThreadActions on AppController {
..writeln('- sessionKey: $sessionKey')
..writeln('- localWorkspace: ${workingDirectory.trim()}');
final remoteHint = remoteWorkingDirectoryHint.trim();
final executionWorkspace =
executionWorkingDirectory?.trim().isNotEmpty == true
? executionWorkingDirectory!.trim()
: (remoteHint.isNotEmpty ? remoteHint : workingDirectory.trim());
if (remoteHint.isNotEmpty) {
buffer.writeln('- remoteWorkspaceHint: $remoteHint');
}
buffer.writeln(
'- currentTaskWorkspace: ${remoteHint.isNotEmpty ? remoteHint : workingDirectory.trim()}',
);
buffer.writeln('- currentTaskWorkspace: $executionWorkspace');
buffer
..writeln()
..writeln('Workspace isolation rules:')
@ -665,6 +679,18 @@ extension AppControllerDesktopThreadActions on AppController {
provider.providerId == kCanonicalGatewayProviderId;
}
String gatewayExecutionWorkingDirectoryInternal({
required AssistantExecutionTarget target,
required String workingDirectory,
required String remoteWorkingDirectoryHint,
}) {
final remoteHint = remoteWorkingDirectoryHint.trim();
if (target.isGateway && remoteHint.isNotEmpty) {
return remoteHint;
}
return workingDirectory.trim();
}
Future<void> enqueueOpenClawGatewayTurnInternal(
OpenClawGatewayQueuedTurnInternal turn,
) async {
@ -830,6 +856,8 @@ extension AppControllerDesktopThreadActions on AppController {
attachments: turn.attachments,
localAttachments: turn.localAttachments,
workingDirectory: turn.workingDirectory,
localWorkingDirectory: turn.localWorkingDirectory,
executionWorkingDirectory: turn.workingDirectory,
remoteWorkingDirectoryHint: turn.remoteWorkingDirectoryHint,
model: turn.model,
routing: turn.routing,

View File

@ -16,6 +16,7 @@ class OpenClawGatewayQueuedTurnInternal {
required this.attachments,
required this.localAttachments,
required this.workingDirectory,
required this.localWorkingDirectory,
required this.remoteWorkingDirectoryHint,
required this.model,
required this.routing,
@ -35,6 +36,7 @@ class OpenClawGatewayQueuedTurnInternal {
final List<GatewayChatAttachmentPayload> attachments;
final List<CollaborationAttachment> localAttachments;
final String workingDirectory;
final String localWorkingDirectory;
final String remoteWorkingDirectoryHint;
final String model;
final ExternalCodeAgentAcpRoutingConfig routing;

View File

@ -1157,6 +1157,49 @@ void main() {
},
);
test(
'sendChatMessage sends remote Gateway cwd while keeping local workspace context',
() async {
final fakeGoTaskService = _RecordingGoTaskServiceClient();
final controller = _connectedGatewayController(fakeGoTaskService);
addTearDown(controller.dispose);
await controller.ensureActiveAssistantThreadInternal();
await controller.setAssistantExecutionTarget(
AssistantExecutionTarget.gateway,
);
await controller.sendChatMessage(
'gateway task with inline attachment',
attachments: <GatewayChatAttachmentPayload>[
GatewayChatAttachmentPayload(
type: 'file',
mimeType: 'text/plain',
fileName: 'note.txt',
content: base64Encode(utf8.encode('note body')),
),
],
);
expect(fakeGoTaskService.requests, hasLength(1));
final request = fakeGoTaskService.requests.single;
final localWorkspace = controller.assistantWorkspacePathForSession(
request.sessionId,
);
expect(localWorkspace, isNotEmpty);
expect(request.target, AssistantExecutionTarget.gateway);
expect(request.provider.providerId, 'openclaw');
expect(request.workingDirectory, startsWith('/owners/'));
expect(request.workingDirectory, isNot(localWorkspace));
expect(request.remoteWorkingDirectoryHint, request.workingDirectory);
expect(request.prompt, contains('- localWorkspace: $localWorkspace'));
expect(
request.prompt,
contains('- currentTaskWorkspace: ${request.workingDirectory}'),
);
expect(request.inlineAttachments, hasLength(1));
},
);
test(
'sendChatMessage forwards inline attachment content and size',
() async {
@ -2997,6 +3040,7 @@ void main() {
attachments: const <GatewayChatAttachmentPayload>[],
localAttachments: const <CollaborationAttachment>[],
workingDirectory: '/tmp/$sessionKey',
localWorkingDirectory: '/tmp/$sessionKey-local',
remoteWorkingDirectoryHint: '/threads/$sessionKey',
model: '',
routing: const ExternalCodeAgentAcpRoutingConfig.auto(