diff --git a/lib/app/app_controller_desktop_thread_actions.dart b/lib/app/app_controller_desktop_thread_actions.dart index a89048c7..aa5b610c 100644 --- a/lib/app/app_controller_desktop_thread_actions.dart +++ b/lib/app/app_controller_desktop_thread_actions.dart @@ -461,9 +461,13 @@ extension AppControllerDesktopThreadActions on AppController { localAttachments, ); final taskLoadClass = classifyGatewayTaskLoadInternal(message); + final expectedArtifactExtensions = + expectedGatewayArtifactExtensionsInternal(message); final taskMetadata = Map.unmodifiable({ ...dispatch.metadata, 'taskLoadClass': taskLoadClass, + if (expectedArtifactExtensions.isNotEmpty) + 'expectedArtifactExtensions': expectedArtifactExtensions, }); final executionWorkingDirectory = gatewayExecutionWorkingDirectoryInternal( target: currentTarget, @@ -764,6 +768,42 @@ extension AppControllerDesktopThreadActions on AppController { return 'short_task'; } + List expectedGatewayArtifactExtensionsInternal(String requestText) { + final normalized = requestText.trim().toLowerCase(); + final result = []; + + void add(String value) { + final normalizedValue = value.trim().toLowerCase().replaceFirst( + RegExp(r'^\.'), + '', + ); + if (normalizedValue.isEmpty || result.contains(normalizedValue)) { + return; + } + result.add(normalizedValue); + } + + for (final match in RegExp( + r'\.([a-z0-9]{2,5})\b', + caseSensitive: false, + ).allMatches(normalized)) { + add(match.group(1) ?? ''); + } + for (final match in RegExp( + r'\b([a-z0-9]{2,5})\s*(?:格式|文件|产物|artifact|file|output)', + caseSensitive: false, + ).allMatches(normalized)) { + add(match.group(1) ?? ''); + } + for (final match in RegExp( + r'(?:输出|导出|生成|制作)\s*([a-z0-9]{2,5})', + caseSensitive: false, + ).allMatches(normalized)) { + add(match.group(1) ?? ''); + } + return List.unmodifiable(result); + } + bool usesOpenClawGatewayQueueInternal( AssistantExecutionTarget target, SingleAgentProvider provider, diff --git a/pubspec.yaml b/pubspec.yaml index 134073bb..440a1cb7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,9 +2,9 @@ name: xworkmate description: "XWorkmate desktop-first AI workspace shell." publish_to: 'none' -version: 1.1.3+1 -build-date: 2026-05-28 -build-id: 82d46f5 +version: 1.1.4 +build-date: 2026-05-30 +build-id: 94405c9 environment: sdk: ^3.11.0 diff --git a/test/runtime/assistant_execution_target_test.dart b/test/runtime/assistant_execution_target_test.dart index 452cd17a..6668fc6d 100644 --- a/test/runtime/assistant_execution_target_test.dart +++ b/test/runtime/assistant_execution_target_test.dart @@ -1186,6 +1186,31 @@ void main() { }, ); + test( + 'sendChatMessage declares expected artifacts for complex PDF chains', + () async { + final fakeGoTaskService = _RecordingGoTaskServiceClient(); + final controller = _connectedGatewayController(fakeGoTaskService); + addTearDown(controller.dispose); + + await controller.ensureActiveAssistantThreadInternal(); + await controller.setAssistantExecutionTarget( + AssistantExecutionTarget.gateway, + ); + await controller.sendChatMessage( + '围绕\n\n' + '从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 800-1500字\n' + '拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 ->\n\n' + '最后 输出 PDF文件', + ); + + expect(fakeGoTaskService.requests, hasLength(1)); + final request = fakeGoTaskService.requests.single; + expect(request.metadata['taskLoadClass'], 'complex_long_chain_task'); + expect(request.metadata['expectedArtifactExtensions'], ['pdf']); + }, + ); + test( 'sendChatMessage classifies simple Gateway prompts as short tasks', () async {