fix(assistant): pin task session on submit

This commit is contained in:
Haitao Pan 2026-05-29 13:31:48 +08:00
parent 8c29172fdf
commit 109dbd219f
4 changed files with 76 additions and 11 deletions

View File

@ -604,12 +604,14 @@ class AppController extends ChangeNotifier {
Future<void> sendChatMessage(
String message, {
String? sessionKey,
String thinking = 'off',
List<GatewayChatAttachmentPayload> attachments = const [],
List<CollaborationAttachment> localAttachments = const [],
List<String> selectedSkillLabels = const [],
}) => AppControllerDesktopThreadActions(this).sendChatMessage(
message,
sessionKey: sessionKey,
thinking: thinking,
attachments: attachments,
localAttachments: localAttachments,

View File

@ -232,6 +232,7 @@ extension AppControllerDesktopThreadActions on AppController {
Future<void> sendChatMessage(
String message, {
String? sessionKey,
String thinking = 'off',
List<GatewayChatAttachmentPayload> attachments =
const <GatewayChatAttachmentPayload>[],
@ -239,20 +240,28 @@ extension AppControllerDesktopThreadActions on AppController {
const <CollaborationAttachment>[],
List<String> selectedSkillLabels = const <String>[],
}) async {
var sessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
var targetSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey ?? sessionsControllerInternal.currentSessionKey,
);
if (!isAppOwnedAssistantSessionKeyInternal(sessionKey)) {
if (!isAppOwnedAssistantSessionKeyInternal(targetSessionKey)) {
if (sessionKey != null && sessionKey.trim().isNotEmpty) {
throw StateError(
appText(
'提交目标会话无效,请重新选择任务后提交。',
'The submit target session is invalid. Select the task again before submitting.',
),
);
}
await ensureActiveAssistantThreadInternal();
sessionKey = normalizedAssistantSessionKeyInternal(
targetSessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
);
}
final resumeSessionHint = shouldResumeGatewaySessionForNextSendInternal(
sessionKey,
targetSessionKey,
);
await dispatchGatewayChatTurnInternal(
sessionKey: sessionKey,
sessionKey: targetSessionKey,
message: message,
thinking: thinking,
attachments: attachments,

View File

@ -129,10 +129,10 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
autoAgent?.name ?? conversationOwnerLabelInternal(controller);
attachmentsInternal = const <ComposerAttachmentInternal>[];
touchTaskSeedInternal(
sessionKey: controller.currentSessionKey,
sessionKey: submittedSessionKey,
title:
taskSeedsInternal[controller.currentSessionKey]?.title ??
fallbackSessionTitleInternal(controller.currentSessionKey),
taskSeedsInternal[submittedSessionKey]?.title ??
fallbackSessionTitleInternal(submittedSessionKey),
preview: rawPrompt,
status: controller.hasAssistantPendingRun || connectionState.connected
? 'running'
@ -140,7 +140,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
owner: autoAgent?.name ?? conversationOwnerLabelInternal(controller),
surface: 'Assistant',
executionTarget: executionTarget,
draft: controller.currentSessionKey.trim().startsWith('draft:'),
draft: submittedSessionKey.trim().startsWith('draft:'),
);
});
inputControllerInternal.clear();
@ -148,6 +148,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
try {
await controller.sendChatMessage(
prompt,
sessionKey: submittedSessionKey,
thinking: thinkingLabelInternal,
attachments: attachmentPayloads,
localAttachments: submittedAttachments
@ -166,7 +167,12 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
if (!mounted) {
rethrow;
}
if (inputControllerInternal.text.trim().isEmpty) {
if (!sessionKeysMatchInternal(
widget.controller.currentSessionKey,
submittedSessionKey,
)) {
composerDraftBySessionKeyInternal[submittedSessionKey] = rawPrompt;
} else if (inputControllerInternal.text.trim().isEmpty) {
inputControllerInternal.value = TextEditingValue(
text: rawPrompt,
selection: TextSelection.collapsed(offset: rawPrompt.length),

View File

@ -1812,6 +1812,54 @@ void main() {
);
});
test('sendChatMessage can pin submit to the captured session', () async {
final fakeGoTaskService = _BlockingGoTaskServiceClient();
final controller = _connectedController(fakeGoTaskService);
addTearDown(controller.dispose);
await controller.switchSession('same-prompt-old-task');
await controller.switchSession('same-prompt-new-task');
final taskFuture = controller.sendChatMessage(
'连续制作7张图片',
sessionKey: 'same-prompt-new-task',
);
await fakeGoTaskService.waitForRequestCount(1);
final request = fakeGoTaskService.requests.single;
expect(request.sessionId, 'same-prompt-new-task');
expect(request.threadId, 'same-prompt-new-task');
expect(request.workingDirectory, endsWith('/same-prompt-new-task'));
expect(
request.remoteWorkingDirectoryHint,
endsWith('/threads/same-prompt-new-task'),
);
fakeGoTaskService.complete(
'same-prompt-new-task',
const GoTaskServiceResult(
success: true,
message: 'new task result',
turnId: 'turn-new',
raw: <String, dynamic>{},
errorMessage: '',
resolvedModel: '',
route: GoTaskServiceRoute.externalAcpSingle,
),
);
await taskFuture;
expect(
controller.localSessionMessagesInternal['same-prompt-new-task']!.map(
(message) => message.text,
),
contains('new task result'),
);
expect(
controller.localSessionMessagesInternal['same-prompt-old-task'],
isNot(contains('new task result')),
);
});
test(
'sendChatMessage queues follow-up turns on the same session',
() async {