feat: pass remote working directory as hint while preserving local cwd
Distinguishes between the local working directory (used for execution) and the remote working directory hint. Captures remote working directory from execution results and persists it for future turns.
This commit is contained in:
parent
16f214a6c6
commit
f2e3cfa790
@ -158,26 +158,27 @@ mergeAcpCapabilitiesIntoMountTargetsRuntimeInternal(
|
||||
String? assistantWorkingDirectoryForSessionRuntimeInternal(
|
||||
AppController controller,
|
||||
String sessionKey,
|
||||
) {
|
||||
return resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal(
|
||||
controller,
|
||||
sessionKey,
|
||||
requireLocalExistence: false,
|
||||
);
|
||||
}
|
||||
|
||||
String? assistantRemoteWorkingDirectoryHintForSessionRuntimeInternal(
|
||||
AppController controller,
|
||||
String sessionKey,
|
||||
) {
|
||||
final normalizedSessionKey = controller.normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
final remoteCandidate =
|
||||
final candidate =
|
||||
controller
|
||||
.assistantThreadRecordsInternal[normalizedSessionKey]
|
||||
?.lastRemoteWorkingDirectory
|
||||
?.trim() ??
|
||||
'';
|
||||
if (remoteCandidate.isNotEmpty) {
|
||||
return remoteCandidate;
|
||||
}
|
||||
final candidate =
|
||||
controller
|
||||
.assistantThreadRecordsInternal[normalizedSessionKey]
|
||||
?.workspaceBinding
|
||||
.workspacePath
|
||||
.trim() ??
|
||||
'';
|
||||
if (candidate.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -419,6 +419,13 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
|
||||
String? assistantWorkingDirectoryForSessionInternal(String sessionKey) =>
|
||||
assistantWorkingDirectoryForSessionRuntimeInternal(this, sessionKey);
|
||||
|
||||
String? assistantRemoteWorkingDirectoryHintForSessionInternal(
|
||||
String sessionKey,
|
||||
) => assistantRemoteWorkingDirectoryHintForSessionRuntimeInternal(
|
||||
this,
|
||||
sessionKey,
|
||||
);
|
||||
|
||||
String? resolveLocalAssistantWorkingDirectoryForSessionInternal(
|
||||
String sessionKey, {
|
||||
bool requireLocalExistence = true,
|
||||
|
||||
@ -235,9 +235,7 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
final currentTarget = assistantExecutionTargetForSession(currentSessionKey);
|
||||
final connectionState = currentAssistantConnectionState;
|
||||
if (!connectionState.connected) {
|
||||
final error = StateError(
|
||||
connectionState.detailLabel,
|
||||
);
|
||||
final error = StateError(connectionState.detailLabel);
|
||||
appendAssistantThreadMessageInternal(
|
||||
currentSessionKey,
|
||||
assistantErrorMessageInternal(error.message),
|
||||
@ -256,6 +254,11 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
currentSessionKey,
|
||||
)?.trim() ??
|
||||
'';
|
||||
final remoteWorkingDirectoryHint =
|
||||
assistantRemoteWorkingDirectoryHintForSessionInternal(
|
||||
currentSessionKey,
|
||||
)?.trim() ??
|
||||
'';
|
||||
if (workingDirectory.isEmpty) {
|
||||
final error = StateError(
|
||||
appText(
|
||||
@ -347,6 +350,7 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
provider: assistantProviderForSession(sessionKey),
|
||||
prompt: message,
|
||||
workingDirectory: workingDirectory,
|
||||
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
|
||||
model: assistantModelForSession(sessionKey),
|
||||
thinking: thinking,
|
||||
selectedSkills: selectedSkillLabels,
|
||||
@ -372,6 +376,11 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
result: result,
|
||||
),
|
||||
latestResolvedRuntimeModel: result.resolvedModel.trim(),
|
||||
lastRemoteWorkingDirectory:
|
||||
result.remoteWorkingDirectory.trim().isNotEmpty
|
||||
? result.remoteWorkingDirectory.trim()
|
||||
: null,
|
||||
lastRemoteWorkspaceRefKind: result.remoteWorkspaceRefKind,
|
||||
lifecycleStatus: 'ready',
|
||||
lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
|
||||
lastResultCode: result.success ? 'success' : 'error',
|
||||
|
||||
@ -114,6 +114,8 @@ Future<void> runMultiAgentCollaborationThreadSessionInternal(
|
||||
);
|
||||
final workingDirectory = controller
|
||||
.assistantWorkingDirectoryForSessionInternal(sessionKey);
|
||||
final remoteWorkingDirectoryHint = controller
|
||||
.assistantRemoteWorkingDirectoryHintForSessionInternal(sessionKey);
|
||||
if (workingDirectory == null || workingDirectory.trim().isEmpty) {
|
||||
final error = StateError(
|
||||
appText(
|
||||
@ -163,6 +165,7 @@ Future<void> runMultiAgentCollaborationThreadSessionInternal(
|
||||
target: controller.assistantExecutionTargetForSession(sessionKey),
|
||||
prompt: composedPrompt,
|
||||
workingDirectory: workingDirectory,
|
||||
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint?.trim() ?? '',
|
||||
model: controller.assistantModelForSession(sessionKey),
|
||||
thinking: 'medium',
|
||||
selectedSkills: selectedSkillLabels,
|
||||
@ -201,6 +204,15 @@ Future<void> runMultiAgentCollaborationThreadSessionInternal(
|
||||
sessionKey,
|
||||
result,
|
||||
);
|
||||
controller.upsertTaskThreadInternal(
|
||||
sessionKey,
|
||||
lastRemoteWorkingDirectory:
|
||||
result.remoteWorkingDirectory.trim().isNotEmpty
|
||||
? result.remoteWorkingDirectory.trim()
|
||||
: null,
|
||||
lastRemoteWorkspaceRefKind: result.remoteWorkspaceRefKind,
|
||||
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
|
||||
);
|
||||
controller.appendLocalSessionMessageInternal(
|
||||
sessionKey,
|
||||
GatewayChatMessage(
|
||||
|
||||
@ -42,7 +42,7 @@ void main() {
|
||||
controller,
|
||||
'session-1',
|
||||
),
|
||||
remoteWorkspace.path,
|
||||
localWorkspace.path,
|
||||
);
|
||||
expect(
|
||||
resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal(
|
||||
@ -51,6 +51,13 @@ void main() {
|
||||
),
|
||||
localWorkspace.path,
|
||||
);
|
||||
expect(
|
||||
assistantRemoteWorkingDirectoryHintForSessionRuntimeInternal(
|
||||
controller,
|
||||
'session-1',
|
||||
),
|
||||
remoteWorkspace.path,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -577,6 +577,41 @@ void main() {
|
||||
expect(capture.requestBody, isNot(contains('"method":"turn/start"')));
|
||||
});
|
||||
|
||||
test(
|
||||
'desktop execution keeps local cwd and sends remote workspace as hint',
|
||||
() async {
|
||||
final capture = await _startAcpHttpServer();
|
||||
addTearDown(capture.close);
|
||||
final client = GatewayAcpClient(
|
||||
endpointResolver: () => capture.baseEndpoint,
|
||||
authorizationResolver: (_) async => 'bridge-token',
|
||||
);
|
||||
|
||||
final transport = ExternalCodeAgentAcpDesktopTransport(
|
||||
client: client,
|
||||
endpointResolver: (_) => capture.baseEndpoint,
|
||||
taskEndpointResolver: (_) => capture.baseEndpoint,
|
||||
);
|
||||
|
||||
await transport.executeTask(
|
||||
_taskRequest(
|
||||
target: AssistantExecutionTarget.agent,
|
||||
provider: SingleAgentProvider.codex,
|
||||
remoteWorkingDirectoryHint: '/owners/local/user/demo/threads/main',
|
||||
),
|
||||
onUpdate: (_) {},
|
||||
);
|
||||
|
||||
expect(capture.requestBody, contains('"workingDirectory":"/tmp"'));
|
||||
expect(
|
||||
capture.requestBody,
|
||||
contains(
|
||||
'"remoteWorkingDirectoryHint":"/owners/local/user/demo/threads/main"',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('multi-agent execution uses session lifecycle methods', () async {
|
||||
final capture = await _startAcpHttpServer();
|
||||
addTearDown(capture.close);
|
||||
@ -668,6 +703,7 @@ GoTaskServiceRequest _taskRequest({
|
||||
required AssistantExecutionTarget target,
|
||||
required SingleAgentProvider provider,
|
||||
bool resumeSession = false,
|
||||
String remoteWorkingDirectoryHint = '',
|
||||
}) {
|
||||
return GoTaskServiceRequest(
|
||||
sessionId: 'session-1',
|
||||
@ -683,6 +719,7 @@ GoTaskServiceRequest _taskRequest({
|
||||
agentId: '',
|
||||
metadata: const <String, dynamic>{},
|
||||
provider: provider,
|
||||
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
|
||||
resumeSession: resumeSession,
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user