From 0e2fe977030f2d0bcc5c8453338c4ea7a7bdeacf Mon Sep 17 00:00:00 2001 From: matevip Date: Mon, 26 Oct 2026 22:04:10 -0400 Subject: [PATCH] release: v3.0.0 --- .env.example | 4 +- CHANGELOG.md | 57 + README.md | 52 +- README_zh.md | 52 +- assets/architecture-biz-en.svg | 2 +- assets/architecture-biz-zh.svg | 2 +- assets/architecture-tech-en.svg | 14 +- assets/architecture-tech-zh.svg | 14 +- docker-compose.yml | 2 +- mateclaw-desktop/electron/main/index.ts | 4 +- .../electron/main/jwtSecret.d.mts | 1 + mateclaw-desktop/electron/main/jwtSecret.mjs | 35 + mateclaw-desktop/package.json | 4 +- .../scripts/__tests__/jwt-secret.test.mjs | 43 + mateclaw-server/Dockerfile | 15 +- mateclaw-server/pom.xml | 96 +- .../java/vip/mate/MateClawApplication.java | 18 +- .../vip/mate/agent/AgentGraphBuilder.java | 567 +- .../java/vip/mate/agent/AgentService.java | 101 +- .../main/java/vip/mate/agent/BaseAgent.java | 88 +- .../vip/mate/agent/GraphEventPublisher.java | 34 +- .../binding/service/AgentBindingService.java | 72 +- .../context/BudgetedCompactionModel.java | 82 + .../agent/context/ChannelHistoryPolicy.java | 94 + .../vip/mate/agent/context/ChatOrigin.java | 7 + .../context/ConversationWindowManager.java | 7 +- .../mate/agent/context/LoopBudgetConfig.java | 39 +- .../agent/context/LoopMessageBudgeter.java | 20 +- .../vip/mate/agent/context/PromptBudget.java | 52 + .../mate/agent/context/TokenEstimator.java | 17 +- .../mate/agent/graph/ModelToolOptions.java | 47 + .../agent/graph/NodeStreamingChatHelper.java | 305 +- .../mate/agent/graph/PayloadReduction.java | 82 + .../agent/graph/StateGraphReActAgent.java | 179 +- .../graph/edge/GoalEvaluationDispatcher.java | 6 +- .../graph/edge/ObservationDispatcher.java | 6 +- .../agent/graph/edge/ReasoningDispatcher.java | 32 +- .../graph/executor/ToolCallDeadline.java | 24 +- .../executor/ToolExecutionCheckpoint.java | 11 + ...lExecutionCheckpointRejectedException.java | 12 + .../graph/executor/ToolExecutionExecutor.java | 381 +- .../graph/executor/ToolResultProperties.java | 2 +- .../graph/executor/ToolResultStorage.java | 25 +- .../graph/guard/ActionCompletionPolicy.java | 66 +- .../guard/TaskFinalizationCoordinator.java | 345 + .../graph/guard/TaskFinalizationGate.java | 164 + .../lifecycle/ReActLifecycleListener.java | 140 - .../vip/mate/agent/graph/node/ActionNode.java | 27 +- .../agent/graph/node/FinalAnswerNode.java | 6 +- .../agent/graph/node/GoalEvaluationNode.java | 69 +- .../agent/graph/node/LimitExceededNode.java | 71 +- .../agent/graph/node/ObservationNode.java | 6 +- .../mate/agent/graph/node/ReasoningNode.java | 623 +- .../agent/graph/node/SummarizingNode.java | 62 +- .../plan/StateGraphPlanExecuteAgent.java | 48 +- .../plan/edge/PlanGenerationDispatcher.java | 6 +- .../plan/edge/StepProgressDispatcher.java | 21 +- .../plan/node/DelegatedStepContract.java | 51 + .../graph/plan/node/DirectAnswerNode.java | 6 +- .../graph/plan/node/PlanGenerationNode.java | 77 +- .../graph/plan/node/PlanSummaryNode.java | 130 +- .../graph/plan/node/StepExecutionNode.java | 509 +- .../graph/plan/state/PlanStateAccessor.java | 10 +- .../graph/state/ActionExecutionLedger.java | 364 +- .../graph/state/MateClawStateAccessor.java | 14 +- .../agent/graph/state/MateClawStateKeys.java | 12 +- .../mate/agent/progress/ProgressLedger.java | 10 +- .../progress/ProgressLedgerProjection.java | 107 + .../vip/mate/agent/runtime/AgentRuntime.java | 20 + .../agent/runtime/WorkerResultContract.java | 40 + .../agentscope/ApplicationGuardedToolkit.java | 331 + .../agentscope/GuardedToolBatchExecutor.java | 13 + .../agentscope/GuardedToolBatchResult.java | 14 + .../agentscope/NativePolicyDriver.java | 558 + .../agentscope/SpringToolSchemaAdapter.java | 54 + .../agentscope/ToolBatchResultAdapter.java | 121 + .../runtime/dsh/DshConversationHistory.java | 29 +- .../agent/runtime/dsh/DshDockerProcess.java | 50 + .../mate/agent/runtime/dsh/DshLaunchSpec.java | 71 + .../runtime/dsh/DshProcessSupervisor.java | 159 + .../agent/runtime/dsh/DshRuntimeService.java | 496 +- .../mate/agent/runtime/dsh/DshSdkProcess.java | 177 + .../mate/agent/runtime/dsh/DshSdkTurn.java | 118 + .../agent/runtime/dsh/DshToolDispatcher.java | 2 + .../dsh/management/DshGenerationStore.java | 333 + .../management/DshManagementController.java | 38 +- .../dsh/management/DshManagementService.java | 117 +- .../dsh/management/DshManagementState.java | 1 + .../dsh/management/DshPinnedInstaller.java | 96 + .../management/DshRuntimeConfigResolver.java | 23 +- .../management/DshRuntimeConfigService.java | 103 +- .../management/DshRuntimeConfiguration.java | 36 +- .../dsh/management/DshUpgradeService.java | 237 + .../runtime/plan/ExplicitPlanRuntime.java | 142 + .../mate/agent/runtime/state/EdgeAction.java | 6 + .../mate/agent/runtime/state/NodeAction.java | 8 + .../mate/agent/runtime/state/RunState.java | 153 + .../vip/mate/agent/runtime/state/RunStep.java | 26 + .../runtime/state/RuntimeExecutionScope.java | 60 + .../agent/service/AgentGenerationService.java | 8 + .../vip/mate/auth/service/AuthService.java | 9 +- .../mate/auth/service/JwtSecretValidator.java | 28 + .../vip/mate/auth/sso/SsoStateService.java | 10 +- .../mate/channel/AbstractChannelAdapter.java | 18 +- .../java/vip/mate/channel/ChannelAdapter.java | 2 + .../mate/channel/ChannelHealthMonitor.java | 4 +- .../java/vip/mate/channel/ChannelManager.java | 4 +- .../java/vip/mate/channel/ChannelTypes.java | 19 + .../channel/feishu/FeishuChannelAdapter.java | 255 +- .../channel/feishu/FeishuWebSocketClient.java | 128 + .../health/ChannelsHealthIndicator.java | 4 +- .../mate/channel/service/ChannelService.java | 10 + .../channel/web/AgentStreamAccumulator.java | 6 + .../vip/mate/channel/web/ChatController.java | 88 +- .../mate/channel/web/ChatStreamTracker.java | 2 +- .../channel/webchat/WebChatController.java | 2 +- .../mate/config/ChannelHistoryProperties.java | 15 + .../vip/mate/config/FlywayRepairConfig.java | 25 +- .../java/vip/mate/config/JacksonConfig.java | 2 +- .../config/PausedShutdownTaskScheduler.java | 27 + .../mate/config/PrefixBudgetProperties.java | 2 +- .../vip/mate/config/ProviderRetryConfig.java | 123 + .../vip/mate/config/SchedulingConfig.java | 2 +- .../mate/config/SecurityStartupValidator.java | 24 +- .../vip/mate/decision/api/DecisionCodes.java | 13 + .../vip/mate/decision/api/DecisionFacts.java | 15 + .../vip/mate/decision/api/DecisionMode.java | 3 + .../mate/decision/api/DecisionOutcome.java | 3 + .../mate/decision/api/DecisionQuestion.java | 32 + .../api/DecisionRecordingException.java | 13 + .../mate/decision/api/DecisionRequest.java | 18 + .../vip/mate/decision/api/DecisionScope.java | 10 + .../vip/mate/decision/api/DecisionTicket.java | 3 + .../vip/mate/decision/api/DecisionType.java | 3 + .../vip/mate/decision/api/DecisionValue.java | 18 + .../decision/config/DecisionProperties.java | 59 + .../mate/decision/core/DecisionService.java | 152 + .../decision/provider/DecisionCapability.java | 22 + .../decision/provider/DecisionProvider.java | 9 + .../decision/provider/DecisionResult.java | 9 + .../provider/LlmDecisionProvider.java | 11 + .../provider/RuleDecisionProvider.java | 73 + .../mate/decision/record/DecisionRecord.java | 10 + .../decision/record/DecisionRecordStore.java | 9 + .../record/JdbcDecisionRecordStore.java | 92 + .../service/ExecutionObservationSink.java | 12 +- .../java/vip/mate/goal/model/GoalEntity.java | 4 +- .../service/GoalContinuationSupervisor.java | 40 +- .../goal/service/GoalDecisionAdapter.java | 113 + .../mate/goal/service/GoalDecisionFacts.java | 149 + .../goal/service/GoalEvaluationService.java | 38 +- .../mate/goal/service/GoalLifecycleHints.java | 27 + .../mate/goal/service/GoalRunCoordinator.java | 109 +- .../mate/goal/service/GoalSegmentRunner.java | 19 +- .../vip/mate/goal/service/GoalService.java | 4 + .../mate/goal/service/GoalServiceImpl.java | 41 + .../service/GoalToolExecutionCheckpoint.java | 167 + .../java/vip/mate/llm/ProviderHttpError.java | 47 + .../cache/AnthropicCacheOptionsFactory.java | 32 +- .../mate/llm/cache/CacheUsageExtractor.java | 12 +- .../mate/llm/chatgpt/ChatGPTChatModel.java | 4 +- .../chatmodel/AnthropicChatModelBuilder.java | 71 +- .../llm/chatmodel/AnthropicSdkChatModel.java | 239 + .../AnthropicSdkWireInterceptor.java | 184 + .../llm/chatmodel/AnthropicThinkingScope.java | 133 + .../mate/llm/chatmodel/ChatModelDefaults.java | 15 + .../chatmodel/ClaudeCodeChatModelBuilder.java | 142 +- .../ClaudeCodeIdentityChatModelDecorator.java | 38 +- .../chatmodel/DashScopeChatModelBuilder.java | 311 +- .../DeepSeekV4ThinkingDecorator.java | 32 +- .../OpenAiCompatibleChatModelBuilder.java | 358 +- .../OpenAiCompatibleHttpInterceptor.java | 98 + .../OpenAiReasoningResponseBody.java | 102 + .../llm/chatmodel/OpenAiRequestRewriter.java | 920 +- .../chatmodel/OpenAiWirePolicyChatModel.java | 151 + .../llm/chatmodel/PinnedOpenAiHttpClient.java | 262 + .../chatmodel/ProviderCallCancellation.java | 30 + .../llm/chatmodel/VllmThinkingDecorator.java | 20 +- .../dashscope/NativeDashScopeChatModel.java | 412 + .../dashscope/NativeDashScopeChatOptions.java | 128 + .../NativeDashScopeConnectionProperties.java | 87 + .../NativeDashScopeEmbeddingModel.java | 195 + .../dashscope/NativeDashScopeException.java | 34 + .../dashscope/NativeDashScopeTransport.java | 325 + .../llm/dashscope/NativeDashScopeUsage.java | 15 + .../llm/embedding/EmbeddingModelFactory.java | 266 +- .../vip/mate/llm/gemini/GeminiChatModel.java | 5 +- .../java/vip/mate/llm/model/ModelFamily.java | 9 +- .../mate/llm/routing/MediaCaptionService.java | 23 +- .../llm/service/ModelDiscoveryService.java | 50 +- .../service/AgentRoutingDecisionAdapter.java | 172 + .../planning/service/PlanningService.java | 40 +- .../runtime/SkillScriptExecutionService.java | 3 + .../mate/skill/service/SkillFileService.java | 60 +- .../skill/workspace/BundledSkillSyncer.java | 36 +- .../mate/skill/workspace/SkillFileSyncer.java | 12 +- .../workspace/bundle/SkillBundleFiles.java | 13 + .../team/service/TeamAnnounceService.java | 25 +- .../team/service/TeamDispatchService.java | 350 +- .../vip/mate/team/service/TeamPlanBridge.java | 26 - .../mate/team/service/TeamResultExcerpt.java | 51 + .../mate/team/service/TeamRunProjector.java | 2 + .../vip/mate/team/service/TeamRunService.java | 75 +- .../team/service/TeamRunSummaryService.java | 184 + .../mate/team/service/TeamRunViewFactory.java | 5 +- .../mate/team/service/TeamTaskService.java | 112 +- ...amWorkerConversationGovernanceService.java | 17 +- .../TeamWorkerInterventionService.java | 12 +- .../team/service/WorkerDecisionAdapter.java | 101 + .../vip/mate/team/tool/TeamTasksTool.java | 97 +- .../main/java/vip/mate/tool/ToolRegistry.java | 36 +- .../vip/mate/tool/builtin/AppendFileTool.java | 11 +- .../mate/tool/builtin/ChatUploadResolver.java | 9 +- .../mate/tool/builtin/CodeExecuteTool.java | 63 +- .../mate/tool/builtin/DelegateAgentTool.java | 8 + .../vip/mate/tool/builtin/EditFileTool.java | 9 +- .../tool/builtin/EnableExtensionTool.java | 2 +- .../mate/tool/builtin/GoalManagementTool.java | 54 +- .../mate/tool/builtin/ProgressLedgerTool.java | 112 +- .../builtin/ProgressiveToolBridgeTool.java | 4 +- .../vip/mate/tool/builtin/ReadFileTool.java | 6 +- .../mate/tool/builtin/ShellExecuteTool.java | 35 + .../tool/builtin/ToolExecutionContext.java | 41 + .../vip/mate/tool/builtin/WriteFileTool.java | 7 +- .../DefaultToolDisclosureService.java | 12 +- .../tool/disclosure/ToolArgumentHints.java | 52 + .../tool/disclosure/ToolCatalogBudget.java | 30 + .../document/GeneratedFileController.java | 5 +- .../document/WorkspaceArtifactSurfacer.java | 5 +- .../mate/tool/guard/WorkspacePathGuard.java | 3 + .../runtime/ApplicationMcpToolCallback.java | 79 + .../tool/mcp/runtime/McpClientManager.java | 13 +- .../mcp/runtime/McpToolResultCapture.java | 65 + .../tool/mcp/runtime/McpWireMetadata.java | 35 + .../runtime/ProgressAwareMcpToolCallback.java | 38 +- .../tool/sandbox/MemberSandboxExecutor.java | 187 + .../main/java/vip/mate/tts/TtsController.java | 19 + .../main/java/vip/mate/tts/TtsService.java | 29 +- .../tts/provider/DashScopeTtsProvider.java | 68 +- .../mate/tts/provider/EdgeTtsProvider.java | 203 +- .../tts/provider/EdgeTtsSecurityToken.java | 169 + .../mate/tts/provider/OpenAiTtsProvider.java | 3 +- .../provider/third-party/edge-tts-LICENSE.txt | 195 + .../provider/third-party/edge-tts-NOTICE.txt | 19 + .../conversation/ConversationService.java | 2 + .../config/WorkspaceSandboxProperties.java | 3 + .../service/ChatUploadLocationResolver.java | 30 + .../core/service/MemberFileAccess.java | 127 + .../core/service/MemberFileIsolation.java | 60 + .../core/service/MemberWorkspaceService.java | 71 + .../main/resources/application-kingbase.yml | 4 +- .../main/resources/application-postgres.yml | 4 +- .../src/main/resources/application.yml | 34 +- .../db/migration/h2/V203__decision_record.sql | 35 + .../kingbase/V203__decision_record.sql | 35 + .../V192__memory_recall_unique_identity.sql | 11 +- .../migration/mysql/V203__decision_record.sql | 35 + .../src/main/resources/docs/en/agents.md | 8 +- .../main/resources/docs/en/architecture.md | 46 +- .../src/main/resources/docs/en/changelog.md | 87 + .../src/main/resources/docs/en/channels.md | 4 + .../src/main/resources/docs/en/chat.md | 4 + .../src/main/resources/docs/en/config.md | 26 +- .../main/resources/docs/en/contributing.md | 34 +- .../main/resources/docs/en/decision-layer.md | 160 + .../resources/docs/en/deepseek-harness.md | 273 +- .../src/main/resources/docs/en/goals.md | 4 + .../src/main/resources/docs/en/index.md | 7 +- .../src/main/resources/docs/en/intro.md | 15 +- .../src/main/resources/docs/en/mcp.md | 78 + .../src/main/resources/docs/en/models.md | 17 + .../src/main/resources/docs/en/multimodal.md | 4 + .../src/main/resources/docs/en/quickstart.md | 5 +- .../src/main/resources/docs/en/releases.md | 1 + .../src/main/resources/docs/en/roadmap.md | 3 + .../src/main/resources/docs/en/security.md | 6 +- .../src/main/resources/docs/en/teams.md | 19 +- .../src/main/resources/docs/en/tools.md | 4 +- .../main/resources/docs/en/wecom-tuning.md | 2 +- .../src/main/resources/docs/en/workspaces.md | 4 + .../src/main/resources/docs/zh/agents.md | 10 +- .../main/resources/docs/zh/architecture.md | 46 +- .../src/main/resources/docs/zh/changelog.md | 87 + .../docs/zh/channel-history-freshness.md | 69 + .../src/main/resources/docs/zh/channels.md | 4 + .../src/main/resources/docs/zh/chat.md | 4 + .../src/main/resources/docs/zh/config.md | 26 +- .../main/resources/docs/zh/contributing.md | 34 +- .../main/resources/docs/zh/decision-layer.md | 159 + .../resources/docs/zh/deepseek-harness.md | 277 +- .../src/main/resources/docs/zh/goals.md | 4 + .../src/main/resources/docs/zh/index.md | 7 +- .../src/main/resources/docs/zh/intro.md | 15 +- .../src/main/resources/docs/zh/mcp.md | 78 + .../src/main/resources/docs/zh/models.md | 17 + .../src/main/resources/docs/zh/multimodal.md | 4 + .../src/main/resources/docs/zh/quickstart.md | 5 +- .../src/main/resources/docs/zh/releases.md | 1 + .../src/main/resources/docs/zh/roadmap.md | 3 + .../src/main/resources/docs/zh/security.md | 6 +- .../src/main/resources/docs/zh/teams.md | 19 +- .../src/main/resources/docs/zh/tools.md | 4 +- .../main/resources/docs/zh/wecom-tuning.md | 2 +- .../src/main/resources/docs/zh/workspaces.md | 4 + .../main/resources/dsh/container/Dockerfile | 16 + .../main/resources/dsh/locks/0.1.7-rc.2.json | 8915 +++++++++++++++ .../main/resources/dsh/locks/0.2.0-rc.1.json | 9124 +++++++++++++++ .../main/resources/dsh/locks/0.2.1-rc.2.json | 9880 +++++++++++++++++ .../src/main/resources/messages.properties | 2 + .../src/main/resources/messages_en.properties | 2 + ...entGraphBuilderFinalizationWiringTest.java | 91 + .../AgentGraphBuilderIdentityBlockTest.java | 5 +- .../AgentGraphBuilderSearchGuidanceTest.java | 87 + .../agent/AgentServiceDshHistoryTest.java | 45 + .../agent/AgentServiceEntryContextTest.java | 315 + .../agent/BaseAgentCarryRecentImageTest.java | 26 + .../agent/BaseAgentChannelHistoryTest.java | 71 + .../BaseAgentImageCaptionPersistTest.java | 6 +- .../Issue664McpExecutionIntegrationTest.java | 303 + .../mate/agent/MemberMediaIsolationTest.java | 118 + .../binding/AgentBindingServiceTest.java | 117 +- .../agent/binding/AgentToolOptOutTest.java | 110 + .../context/BudgetedCompactionModelTest.java | 79 + .../context/ChannelHistoryPolicyTest.java | 87 + .../agent/context/LoopBudgetConfigTest.java | 31 + .../context/LoopMessageBudgeterTest.java | 24 +- .../context/PrefixBudgetPlannerTest.java | 6 +- .../mate/agent/context/PromptBudgetTest.java | 58 + .../context/TokenEstimatorMessagesTest.java | 17 + .../mate/agent/graph/ErrorTypePolicyTest.java | 1 + .../agent/graph/HttpStatusProvenanceTest.java | 48 + .../graph/LaneDPerformanceFixesTest.java | 20 +- .../agent/graph/ModelToolOptionsTest.java | 273 + ...treamingChatHelperBufferedContentTest.java | 234 + ...ngChatHelperCancellationTransportTest.java | 270 + ...reamingChatHelperProviderBoundaryTest.java | 204 + ...odeStreamingChatHelperThinkingCapTest.java | 97 +- .../agent/graph/PayloadReductionTest.java | 75 + .../agent/graph/ReturnDirectEndToEndTest.java | 16 +- ...StateGraphReActAgentInitialBudgetTest.java | 111 + .../graph/StripThinkingBoundaryTest.java | 6 +- .../edge/GoalEvaluationDispatcherTest.java | 8 +- .../graph/edge/ObservationDispatcherTest.java | 22 +- .../ReasoningDispatcherLlmCallCountTest.java | 54 +- .../graph/edge/ReasoningDispatcherTest.java | 14 +- .../MemberIsolationRetentionTest.java | 20 + .../graph/executor/NestedToolContextTest.java | 381 + .../ParallelBatchCancellationTest.java | 497 + .../graph/executor/ToolCallDeadlineTest.java | 177 +- .../executor/ToolCheckpointDeadlineTest.java | 88 + ...xecutionExecutorNameNormalizationTest.java | 7 + ...ExecutionExecutorStructuredResultTest.java | 222 + .../executor/ToolFatalPublicationTest.java | 125 + .../ToolUnknownOutcomeRecoveryTest.java | 210 + .../TaskFinalizationCoordinatorTest.java | 371 + .../graph/guard/TaskFinalizationGateTest.java | 172 + .../node/ActionNodeAutoRecordSkipTest.java | 1 + .../node/ActionNodeBatchBoundaryTest.java | 89 + .../agent/graph/node/FinalAnswerNodeTest.java | 52 +- .../GoalEvaluationNodeContinuationTest.java | 172 +- .../node/LimitExceededFinalizationTest.java | 31 + .../node/LimitExceededNodeFallbackTest.java | 8 +- .../node/LimitExceededNodeProgressTest.java | 76 + .../node/ObservationNodeDuplicateTest.java | 16 +- .../node/ObservationNodeLoopGuardTest.java | 6 +- .../graph/node/ObservationNodeRefundTest.java | 6 +- .../node/PayloadModelViewIntegrationTest.java | 99 + .../node/PayloadWireIntegrationTest.java | 137 + .../node/ReasoningNodeFinalizationTest.java | 482 + .../node/ReasoningNodeOutputClampTest.java | 8 + .../graph/node/ReasoningNodeOutputTest.java | 127 +- .../node/ReasoningNodeProgressBudgetTest.java | 181 + .../node/ReasoningNodeRetryBudgetTest.java | 244 + .../node/ReasoningNodeRetryUsageTest.java | 142 + .../node/ReasoningNodeUserQuestionTest.java | 159 + .../node/SummarizingNodePublicationTest.java | 68 + .../plan/edge/StepProgressDispatcherTest.java | 23 +- .../plan/node/DelegatedStepContractTest.java | 97 + .../plan/node/PlanGenerationAutoGoalTest.java | 17 +- .../plan/node/PlanGenerationRoutingTest.java | 90 + .../plan/node/PlanLifecyclePromptTest.java | 48 + .../plan/node/PlanPayloadRecoveryTest.java | 52 + .../plan/node/PlanSummaryEmptyResultTest.java | 89 +- .../node/PlanSummaryFinalizationTest.java | 168 + .../node/StepExecutionReplanContextTest.java | 4 +- .../node/StepExecutionSkillCatalogTest.java | 4 +- .../graph/plan/node/StepNativeLoopTest.java | 550 + .../state/PlanStateAccessorUsageTest.java | 4 +- .../state/ActionExecutionLedgerTest.java | 409 + ...teClawStateAccessorTerminalAnswerTest.java | 16 +- .../ProgressLedgerProjectionTest.java | 50 + .../runtime/RuntimeEventProjectorTest.java | 54 +- .../RuntimeEventStreamAdapterTest.java | 28 + .../ApplicationGuardedToolkitTest.java | 603 + .../NativeConcurrentIsolationTest.java | 230 + .../agentscope/NativePolicyDriverTest.java | 835 ++ .../ProductCanonicalBoundaryTest.java | 295 + .../SpringToolSchemaAdapterTest.java | 66 + .../ToolBatchResultAdapterTest.java | 199 + .../dsh/DshConversationHistoryTest.java | 38 +- .../runtime/dsh/DshDockerProcessTest.java | 40 + .../dsh/DshDockerRuntimeAcceptanceTest.java | 119 + ...shDockerSharedWorkspaceAcceptanceTest.java | 144 + .../runtime/dsh/DshDockerSupervisorTest.java | 86 + .../agent/runtime/dsh/DshLaunchSpecTest.java | 68 + .../runtime/dsh/DshOwnedProcessTreeTest.java | 39 + .../dsh/DshRealRuntimeAcceptanceTest.java | 135 + .../runtime/dsh/DshRecordedProtocolTest.java | 53 + .../dsh/DshRuntimeModelLimitsTest.java | 19 +- .../runtime/dsh/DshRuntimeServiceTest.java | 63 +- .../runtime/dsh/DshRuntimeTeardownTest.java | 88 + .../agent/runtime/dsh/DshSdkProtocolTest.java | 63 + .../runtime/dsh/DshSdkTransportTest.java | 77 + .../agent/runtime/dsh/DshSdkTurnTest.java | 76 + .../management/DshGenerationStoreTest.java | 98 + .../dsh/management/DshImageSmokeTest.java | 134 + .../management/DshManagementHealthTest.java | 69 + .../management/DshPinnedInstallerTest.java | 41 + .../management/DshRuntimeConfigSaveTest.java | 81 + .../dsh/management/DshUpgradeServiceTest.java | 108 + .../runtime/plan/ExplicitPlanRuntimeTest.java | 126 + .../agent/runtime/state/RunStateTest.java | 202 + .../state/RuntimeExecutionScopeTest.java | 260 + .../service/AgentGenerationServiceTest.java | 86 + .../GoalStateKeyDoubleRegistrationTest.java | 107 +- .../SkillStateKeyDoubleRegistrationTest.java | 78 +- .../StateKeyRegistrationCoverageTest.java | 104 +- .../service/AuthServiceJwtSecretTest.java | 93 + .../mate/auth/sso/SsoStateServiceTest.java | 13 + .../ChannelHealthMonitorQuietTest.java | 50 + .../feishu/FeishuWebSocketLifecycleTest.java | 84 + .../feishu/FeishuWebSocketTransportTest.java | 173 + .../feishu/LocalFeishuSocketServer.java | 96 + .../service/ChannelTypeValidationTest.java | 108 + ...StreamAccumulatorStructuredResultTest.java | 36 + .../web/ChatControllerUploadPathTest.java | 21 + .../WebChatApprovalInteractionTest.java | 8 +- .../webchat/WebChatArchivePinTest.java | 4 +- .../webchat/WebChatAttachmentE2ETest.java | 44 +- .../webchat/WebChatAuditTrailTest.java | 27 +- .../webchat/WebChatCreateSessionTest.java | 4 +- .../webchat/WebChatRegenerateTest.java | 4 +- .../webchat/WebChatSchemaFieldsTest.java | 4 +- .../webchat/WebChatSessionManagementTest.java | 4 +- .../channel/webchat/WebChatSkillListTest.java | 4 +- .../webchat/WebChatStopStreamTest.java | 4 +- .../channel/webchat/WebChatStreamE2ETest.java | 10 +- .../webchat/WebChatTokenRevocationTest.java | 4 +- .../webchat/WebChatWikiPageListTest.java | 4 +- .../mate/config/FlywayRepairConfigTest.java | 174 + .../mate/config/OpenApiExposedAccessTest.java | 24 +- .../config/OpenApiLockedDownAccessTest.java | 44 +- .../mate/config/ProviderRetryConfigTest.java | 123 + .../SchedulingConfigPausedShutdownTest.java | 105 + .../config/SecurityStartupValidatorTest.java | 46 + .../mate/config/ServletJsonContractTest.java | 114 + .../mate/decision/DecisionContractTest.java | 39 + .../mate/decision/DecisionPipelineTest.java | 204 + .../decision/DecisionRecordStoreTest.java | 149 + .../decision/DecisionScenarioPolicyTest.java | 112 + .../mate/decision/DecisionServiceTest.java | 54 + .../vip/mate/decision/DecisionWiringTest.java | 40 + .../decision/RuleDecisionProviderTest.java | 159 + .../OfflineGoalServiceTaskReplayTest.java | 4 +- .../GoalJsonAcceptanceIntegrationTest.java | 4 +- ...alJsonExternalApprovalIntegrationTest.java | 4 +- ...alJsonExternalDatabaseIntegrationTest.java | 4 +- .../goal/GoalJsonGraphIntegrationTest.java | 32 +- .../GoalJsonHttpRuntimeIntegrationTest.java | 40 +- .../mate/goal/GoalJsonProtocolPromptTest.java | 22 + .../goal/GoalPersistenceIntegrationTest.java | 31 +- .../mate/goal/model/GoalEntityBudgetTest.java | 25 + .../service/CrashRecoveryProcessTest.java | 58 + .../goal/service/CrashRecoveryWorker.java | 278 + .../GoalCheckpointTransactionTest.java | 184 + .../GoalContinuationSupervisorTest.java | 54 + .../goal/service/GoalDecisionAdapterTest.java | 282 + .../service/GoalEvaluationServiceTest.java | 17 + .../goal/service/GoalRunCoordinatorTest.java | 168 + .../mate/goal/service/GoalServiceTest.java | 72 + .../goal/service/GoalStreamSafetyTest.java | 324 + .../service/ToolPublicationRecoveryTest.java | 859 ++ .../interop/a2a/A2aJsonRpcControllerTest.java | 4 +- .../vip/mate/llm/ProviderHttpErrorTest.java | 60 + .../llm/cache/PromptCacheStrategyTest.java | 16 +- .../llm/cache/SdkCacheUsageExtractorTest.java | 66 + .../AnthropicBoundaryOptionsTest.java | 86 + .../chatmodel/AnthropicSdkChatModelTest.java | 421 + .../llm/chatmodel/ChatModelDefaultsTest.java | 123 + .../ClaudeCodeChatModelBuilderTest.java | 23 +- .../chatmodel/DashScopeNativeFactoryTest.java | 207 + ...eepSeekFlashFinalizationTransportTest.java | 110 + .../DeepSeekV4ThinkingDecoratorTest.java | 3 +- .../NativeProviderRuntimeToolsTest.java | 194 + .../OpenAiCompatibleChatModelBuilderTest.java | 2 +- .../llm/chatmodel/OpenAiMediaTimeoutTest.java | 263 + .../OpenAiProviderMigrationTest.java | 463 + .../OpenAiReasoningResponseBodyTest.java | 84 + ...penAiToolSchemaNumberPreservationTest.java | 143 +- .../chatmodel/PatchReasoningContentTest.java | 236 +- .../PinnedOpenAiCancellationScopeTest.java | 194 + .../chatmodel/PinnedOpenAiHttpClientTest.java | 193 + .../ProviderSdkCancellationIsolationTest.java | 270 + .../ReasoningEffortSanitizerTest.java | 153 +- .../ReasoningFinalizationTransportTest.java | 113 + .../VllmThinkingCompatibilityTest.java | 21 +- .../NativeDashScopeChatModelTest.java | 351 + .../NativeDashScopeChatOptionsTest.java | 55 + .../NativeDashScopeEmbeddingModelTest.java | 187 + .../NativeDashScopeLifecycleTest.java | 507 + .../NativeDashScopeTransportTest.java | 209 + .../embedding/EmbeddingFactoryNativeTest.java | 90 + .../vip/mate/llm/model/ModelFamilyTest.java | 7 + ...MediaCaptionServiceAttachmentPathTest.java | 38 + ...elDiscoveryServiceProbeValidationTest.java | 147 + .../ModelDiscoveryServiceTestPromptTest.java | 11 +- .../search/SessionSearchIsolationTest.java | 2 +- .../service/MemoryRecallMigrationTest.java | 38 +- .../AgentRoutingDecisionAdapterTest.java | 162 + .../service/AgentRoutingTransactionTest.java | 176 + .../skill/service/SkillBundleBinaryTest.java | 169 + .../workspace/BundledSkillSyncerTest.java | 158 +- .../team/service/TeamAnnounceServiceTest.java | 48 + .../team/service/TeamDispatchServiceTest.java | 237 +- .../mate/team/service/TeamPlanBridgeTest.java | 2 +- .../team/service/TeamResultExcerptTest.java | 49 + .../team/service/TeamRunProjectorTest.java | 4 +- .../mate/team/service/TeamRunServiceTest.java | 40 +- .../service/TeamRunSummaryServiceTest.java | 144 + .../team/service/TeamRunViewFactoryTest.java | 7 + .../team/service/TeamTaskServiceTest.java | 52 + .../TeamWorkerInterventionServiceTest.java | 19 +- .../TeamWorkerOutputPersistenceTest.java | 354 + .../service/WorkerDecisionAdapterTest.java | 70 + .../WorkerDecisionTransactionTest.java | 325 + .../vip/mate/team/tool/TeamTasksToolTest.java | 179 + .../tool/MemberIsolatedToolRegistryTest.java | 75 + .../builtin/CodeExecuteToolOutcomeTest.java | 169 + .../tool/builtin/DelegateAgentToolTest.java | 62 + .../tool/builtin/GoalManagementToolTest.java | 86 + .../builtin/MemberIsolationExecutionTest.java | 46 + .../tool/builtin/ProgressLedgerToolTest.java | 129 +- .../ProgressiveToolBridgeToolTest.java | 62 + .../builtin/ReadFileToolLargeLineTest.java | 5 + .../disclosure/ToolCatalogBudgetTest.java | 22 + .../disclosure/ToolDisclosureServiceTest.java | 17 + .../tool/document/GeneratedFileUrlTest.java | 6 + .../ApplicationMcpToolCallbackTest.java | 144 + .../McpClientManagerDiscoveryTest.java | 85 + .../mcp/runtime/McpToolResultCaptureTest.java | 37 + .../McpWireMetadataSerializationTest.java | 59 + .../ProgressAwareMcpToolCallbackTest.java | 58 +- .../tool/sandbox/MemberSandboxDockerTest.java | 44 + .../sandbox/MemberSandboxExecutorTest.java | 92 + .../vip/mate/tts/TtsVoiceSelectionTest.java | 88 + .../provider/DashScopeTtsProviderTest.java | 78 + .../tts/provider/EdgeTtsProviderTest.java | 95 + .../mate/tts/provider/OpenAiTtsVoiceTest.java | 39 + .../ChatUploadLocationResolverTest.java | 16 + .../service/MemberDirectoryCreationTest.java | 30 + .../core/service/MemberFileAccessTest.java | 33 + .../service/MemberFileIntegrationTest.java | 72 + .../core/service/MemberFileTypeTest.java | 32 + .../service/MemberWorkspaceServiceTest.java | 78 + .../src/test/resources/application.properties | 1 + .../test/resources/dsh/0.1.7-rc.2-sdk.jsonl | 10 + .../test/resources/dsh/0.2.0-rc.1-sdk.jsonl | 10 + .../test/resources/dsh/0.2.1-rc.2-sdk.jsonl | 21 + .../resources/dsh/0.2.1-rc.2-tool-sdk.jsonl | 29 + .../test/resources/dsh/image-bash-smoke.mjs | 54 + mateclaw-ui/package.json | 2 +- mateclaw-ui/src/api/index.ts | 6 +- mateclaw-ui/src/assets/main.css | 5 + .../components/channels/ChannelEditModal.vue | 1 - .../components/channels/ChannelTypePicker.vue | 2 +- .../__tests__/ChannelTypePicker.test.ts | 22 + .../src/components/chat/MessageBubble.vue | 112 +- .../src/components/chat/ToolCallSegment.vue | 9 +- .../MessageBubble.stopIndicator.test.ts | 92 +- .../MessageBubble.structuredHistory.test.ts | 204 + .../ToolCallSegment.structured.test.ts | 55 + .../chat/__tests__/ToolResultChart.test.ts | 49 + .../chat/__tests__/toolResultRegistry.test.ts | 30 + .../chat/tool-results/ToolResultBlock.vue | 13 + .../chat/tool-results/ToolResultChart.vue | 40 + .../chat/tool-results/ToolResultProducts.vue | 12 + .../chat/tool-results/ToolResultTable.vue | 16 + .../chat/tool-results/ToolResultView.vue | 20 + .../components/chat/tool-results/registry.ts | 61 + .../components/chat/tool-results/safety.ts | 34 + .../components/team-run/TeamRunRuntime.vue | 2 +- .../__tests__/toolStructuredContent.test.ts | 50 + mateclaw-ui/src/composables/chat/useChat.ts | 12 +- mateclaw-ui/src/i18n/locales/en-US.ts | 49 +- mateclaw-ui/src/i18n/locales/zh-CN.ts | 49 +- mateclaw-ui/src/types/index.ts | 3 + mateclaw-ui/src/views/Channels.vue | 2 +- .../src/views/Enterprise/EnterpriseOffer.vue | 115 + mateclaw-ui/src/views/Enterprise/Overview.vue | 2 + mateclaw-ui/src/views/Enterprise/index.vue | 2 + .../src/views/Settings/About/index.vue | 8 +- .../Dsh/__tests__/configMapping.test.ts | 17 + .../Dsh/__tests__/healthState.test.ts | 70 + .../src/views/Settings/Dsh/configMapping.ts | 16 +- mateclaw-ui/src/views/Settings/Dsh/index.vue | 107 +- .../Tts/__tests__/voiceSelection.test.ts | 94 + mateclaw-ui/src/views/Settings/Tts/index.vue | 111 +- mateclaw-webchat/package.json | 2 +- pom.xml | 62 +- 609 files changed, 72685 insertions(+), 5901 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 mateclaw-desktop/electron/main/jwtSecret.d.mts create mode 100644 mateclaw-desktop/electron/main/jwtSecret.mjs create mode 100644 mateclaw-desktop/scripts/__tests__/jwt-secret.test.mjs create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/context/BudgetedCompactionModel.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/context/ChannelHistoryPolicy.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/context/PromptBudget.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/ModelToolOptions.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/PayloadReduction.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/executor/ToolExecutionCheckpoint.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/executor/ToolExecutionCheckpointRejectedException.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/guard/TaskFinalizationCoordinator.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/guard/TaskFinalizationGate.java delete mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/lifecycle/ReActLifecycleListener.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/DelegatedStepContract.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/progress/ProgressLedgerProjection.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/AgentRuntime.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/WorkerResultContract.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/agentscope/ApplicationGuardedToolkit.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/agentscope/GuardedToolBatchExecutor.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/agentscope/GuardedToolBatchResult.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/agentscope/NativePolicyDriver.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/agentscope/SpringToolSchemaAdapter.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/agentscope/ToolBatchResultAdapter.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/DshDockerProcess.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/DshLaunchSpec.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/DshProcessSupervisor.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/DshSdkProcess.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/DshSdkTurn.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/management/DshGenerationStore.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/management/DshPinnedInstaller.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/dsh/management/DshUpgradeService.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/plan/ExplicitPlanRuntime.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/state/EdgeAction.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/state/NodeAction.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/state/RunState.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/state/RunStep.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/runtime/state/RuntimeExecutionScope.java create mode 100644 mateclaw-server/src/main/java/vip/mate/auth/service/JwtSecretValidator.java create mode 100644 mateclaw-server/src/main/java/vip/mate/channel/ChannelTypes.java create mode 100644 mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuWebSocketClient.java create mode 100644 mateclaw-server/src/main/java/vip/mate/config/ChannelHistoryProperties.java create mode 100644 mateclaw-server/src/main/java/vip/mate/config/PausedShutdownTaskScheduler.java create mode 100644 mateclaw-server/src/main/java/vip/mate/config/ProviderRetryConfig.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionCodes.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionFacts.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionMode.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionOutcome.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionQuestion.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionRecordingException.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionRequest.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionScope.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionTicket.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionType.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/api/DecisionValue.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/config/DecisionProperties.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/core/DecisionService.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/provider/DecisionCapability.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/provider/DecisionProvider.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/provider/DecisionResult.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/provider/LlmDecisionProvider.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/provider/RuleDecisionProvider.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/record/DecisionRecord.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/record/DecisionRecordStore.java create mode 100644 mateclaw-server/src/main/java/vip/mate/decision/record/JdbcDecisionRecordStore.java create mode 100644 mateclaw-server/src/main/java/vip/mate/goal/service/GoalDecisionAdapter.java create mode 100644 mateclaw-server/src/main/java/vip/mate/goal/service/GoalDecisionFacts.java create mode 100644 mateclaw-server/src/main/java/vip/mate/goal/service/GoalLifecycleHints.java create mode 100644 mateclaw-server/src/main/java/vip/mate/goal/service/GoalToolExecutionCheckpoint.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/ProviderHttpError.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/AnthropicSdkChatModel.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/AnthropicSdkWireInterceptor.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/AnthropicThinkingScope.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/ChatModelDefaults.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiCompatibleHttpInterceptor.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiReasoningResponseBody.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiWirePolicyChatModel.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/PinnedOpenAiHttpClient.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/chatmodel/ProviderCallCancellation.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeChatModel.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeChatOptions.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeConnectionProperties.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeEmbeddingModel.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeException.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeTransport.java create mode 100644 mateclaw-server/src/main/java/vip/mate/llm/dashscope/NativeDashScopeUsage.java create mode 100644 mateclaw-server/src/main/java/vip/mate/planning/service/AgentRoutingDecisionAdapter.java create mode 100644 mateclaw-server/src/main/java/vip/mate/team/service/TeamResultExcerpt.java create mode 100644 mateclaw-server/src/main/java/vip/mate/team/service/TeamRunSummaryService.java create mode 100644 mateclaw-server/src/main/java/vip/mate/team/service/WorkerDecisionAdapter.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/disclosure/ToolArgumentHints.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/disclosure/ToolCatalogBudget.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/ApplicationMcpToolCallback.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/McpToolResultCapture.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/McpWireMetadata.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/sandbox/MemberSandboxExecutor.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tts/provider/EdgeTtsSecurityToken.java create mode 100644 mateclaw-server/src/main/java/vip/mate/tts/provider/third-party/edge-tts-LICENSE.txt create mode 100644 mateclaw-server/src/main/java/vip/mate/tts/provider/third-party/edge-tts-NOTICE.txt create mode 100644 mateclaw-server/src/main/java/vip/mate/workspace/core/service/MemberFileAccess.java create mode 100644 mateclaw-server/src/main/java/vip/mate/workspace/core/service/MemberFileIsolation.java create mode 100644 mateclaw-server/src/main/java/vip/mate/workspace/core/service/MemberWorkspaceService.java create mode 100644 mateclaw-server/src/main/resources/db/migration/h2/V203__decision_record.sql create mode 100644 mateclaw-server/src/main/resources/db/migration/kingbase/V203__decision_record.sql create mode 100644 mateclaw-server/src/main/resources/db/migration/mysql/V203__decision_record.sql create mode 100644 mateclaw-server/src/main/resources/docs/en/changelog.md create mode 100644 mateclaw-server/src/main/resources/docs/en/decision-layer.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/changelog.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/channel-history-freshness.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/decision-layer.md create mode 100644 mateclaw-server/src/main/resources/dsh/container/Dockerfile create mode 100644 mateclaw-server/src/main/resources/dsh/locks/0.1.7-rc.2.json create mode 100644 mateclaw-server/src/main/resources/dsh/locks/0.2.0-rc.1.json create mode 100644 mateclaw-server/src/main/resources/dsh/locks/0.2.1-rc.2.json create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/AgentGraphBuilderFinalizationWiringTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/AgentGraphBuilderSearchGuidanceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/AgentServiceEntryContextTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/BaseAgentChannelHistoryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/Issue664McpExecutionIntegrationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/MemberMediaIsolationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/binding/AgentToolOptOutTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/context/BudgetedCompactionModelTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/context/ChannelHistoryPolicyTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/context/LoopBudgetConfigTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/context/PromptBudgetTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/context/TokenEstimatorMessagesTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/HttpStatusProvenanceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/ModelToolOptionsTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/NodeStreamingChatHelperBufferedContentTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/NodeStreamingChatHelperCancellationTransportTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/NodeStreamingChatHelperProviderBoundaryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/PayloadReductionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/StateGraphReActAgentInitialBudgetTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/MemberIsolationRetentionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/NestedToolContextTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/ParallelBatchCancellationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/ToolCheckpointDeadlineTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/ToolExecutionExecutorStructuredResultTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/ToolFatalPublicationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/executor/ToolUnknownOutcomeRecoveryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/guard/TaskFinalizationCoordinatorTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/guard/TaskFinalizationGateTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/ActionNodeBatchBoundaryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/LimitExceededFinalizationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/LimitExceededNodeProgressTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/PayloadModelViewIntegrationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/PayloadWireIntegrationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/ReasoningNodeFinalizationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/ReasoningNodeProgressBudgetTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/ReasoningNodeRetryBudgetTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/ReasoningNodeRetryUsageTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/ReasoningNodeUserQuestionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/node/SummarizingNodePublicationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/plan/node/DelegatedStepContractTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/plan/node/PlanGenerationRoutingTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/plan/node/PlanLifecyclePromptTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/plan/node/PlanPayloadRecoveryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/plan/node/PlanSummaryFinalizationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/graph/plan/node/StepNativeLoopTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/progress/ProgressLedgerProjectionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/agentscope/ApplicationGuardedToolkitTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/agentscope/NativeConcurrentIsolationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/agentscope/NativePolicyDriverTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/agentscope/ProductCanonicalBoundaryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/agentscope/SpringToolSchemaAdapterTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/agentscope/ToolBatchResultAdapterTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshDockerProcessTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshDockerRuntimeAcceptanceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshDockerSharedWorkspaceAcceptanceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshDockerSupervisorTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshLaunchSpecTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshOwnedProcessTreeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshRealRuntimeAcceptanceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshRecordedProtocolTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshRuntimeTeardownTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshSdkProtocolTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshSdkTransportTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/DshSdkTurnTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/management/DshGenerationStoreTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/management/DshImageSmokeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/management/DshManagementHealthTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/management/DshPinnedInstallerTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/management/DshRuntimeConfigSaveTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/dsh/management/DshUpgradeServiceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/plan/ExplicitPlanRuntimeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/state/RunStateTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/runtime/state/RuntimeExecutionScopeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/service/AgentGenerationServiceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/auth/service/AuthServiceJwtSecretTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/ChannelHealthMonitorQuietTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/feishu/FeishuWebSocketLifecycleTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/feishu/FeishuWebSocketTransportTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/feishu/LocalFeishuSocketServer.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/service/ChannelTypeValidationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/web/AgentStreamAccumulatorStructuredResultTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/config/FlywayRepairConfigTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/config/ProviderRetryConfigTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/config/SchedulingConfigPausedShutdownTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/config/SecurityStartupValidatorTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/config/ServletJsonContractTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/DecisionContractTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/DecisionPipelineTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/DecisionRecordStoreTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/DecisionScenarioPolicyTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/DecisionServiceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/DecisionWiringTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/decision/RuleDecisionProviderTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/model/GoalEntityBudgetTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/service/CrashRecoveryProcessTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/service/CrashRecoveryWorker.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/service/GoalCheckpointTransactionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/service/GoalDecisionAdapterTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/service/GoalStreamSafetyTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/goal/service/ToolPublicationRecoveryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/ProviderHttpErrorTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/cache/SdkCacheUsageExtractorTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/AnthropicBoundaryOptionsTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/AnthropicSdkChatModelTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/ChatModelDefaultsTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/DashScopeNativeFactoryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/DeepSeekFlashFinalizationTransportTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/NativeProviderRuntimeToolsTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/OpenAiMediaTimeoutTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/OpenAiProviderMigrationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/OpenAiReasoningResponseBodyTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/PinnedOpenAiCancellationScopeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/PinnedOpenAiHttpClientTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/ProviderSdkCancellationIsolationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/chatmodel/ReasoningFinalizationTransportTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/dashscope/NativeDashScopeChatModelTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/dashscope/NativeDashScopeChatOptionsTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/dashscope/NativeDashScopeEmbeddingModelTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/dashscope/NativeDashScopeLifecycleTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/dashscope/NativeDashScopeTransportTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/embedding/EmbeddingFactoryNativeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/routing/MediaCaptionServiceAttachmentPathTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/llm/service/ModelDiscoveryServiceProbeValidationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/planning/service/AgentRoutingDecisionAdapterTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/planning/service/AgentRoutingTransactionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/skill/service/SkillBundleBinaryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/team/service/TeamResultExcerptTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/team/service/TeamRunSummaryServiceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/team/service/TeamWorkerOutputPersistenceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/team/service/WorkerDecisionAdapterTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/team/service/WorkerDecisionTransactionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/MemberIsolatedToolRegistryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/builtin/CodeExecuteToolOutcomeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/builtin/MemberIsolationExecutionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/disclosure/ToolCatalogBudgetTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/mcp/runtime/ApplicationMcpToolCallbackTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/mcp/runtime/McpClientManagerDiscoveryTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/mcp/runtime/McpToolResultCaptureTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/mcp/runtime/McpWireMetadataSerializationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/sandbox/MemberSandboxDockerTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/sandbox/MemberSandboxExecutorTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tts/TtsVoiceSelectionTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tts/provider/DashScopeTtsProviderTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tts/provider/EdgeTtsProviderTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tts/provider/OpenAiTtsVoiceTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/workspace/core/service/MemberDirectoryCreationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/workspace/core/service/MemberFileAccessTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/workspace/core/service/MemberFileIntegrationTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/workspace/core/service/MemberFileTypeTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/workspace/core/service/MemberWorkspaceServiceTest.java create mode 100644 mateclaw-server/src/test/resources/application.properties create mode 100644 mateclaw-server/src/test/resources/dsh/0.1.7-rc.2-sdk.jsonl create mode 100644 mateclaw-server/src/test/resources/dsh/0.2.0-rc.1-sdk.jsonl create mode 100644 mateclaw-server/src/test/resources/dsh/0.2.1-rc.2-sdk.jsonl create mode 100644 mateclaw-server/src/test/resources/dsh/0.2.1-rc.2-tool-sdk.jsonl create mode 100644 mateclaw-server/src/test/resources/dsh/image-bash-smoke.mjs create mode 100644 mateclaw-ui/src/components/channels/__tests__/ChannelTypePicker.test.ts create mode 100644 mateclaw-ui/src/components/chat/__tests__/MessageBubble.structuredHistory.test.ts create mode 100644 mateclaw-ui/src/components/chat/__tests__/ToolCallSegment.structured.test.ts create mode 100644 mateclaw-ui/src/components/chat/__tests__/ToolResultChart.test.ts create mode 100644 mateclaw-ui/src/components/chat/__tests__/toolResultRegistry.test.ts create mode 100644 mateclaw-ui/src/components/chat/tool-results/ToolResultBlock.vue create mode 100644 mateclaw-ui/src/components/chat/tool-results/ToolResultChart.vue create mode 100644 mateclaw-ui/src/components/chat/tool-results/ToolResultProducts.vue create mode 100644 mateclaw-ui/src/components/chat/tool-results/ToolResultTable.vue create mode 100644 mateclaw-ui/src/components/chat/tool-results/ToolResultView.vue create mode 100644 mateclaw-ui/src/components/chat/tool-results/registry.ts create mode 100644 mateclaw-ui/src/components/chat/tool-results/safety.ts create mode 100644 mateclaw-ui/src/composables/chat/__tests__/toolStructuredContent.test.ts create mode 100644 mateclaw-ui/src/views/Enterprise/EnterpriseOffer.vue create mode 100644 mateclaw-ui/src/views/Settings/Dsh/__tests__/healthState.test.ts create mode 100644 mateclaw-ui/src/views/Settings/Tts/__tests__/voiceSelection.test.ts diff --git a/.env.example b/.env.example index b9ba79a1..69feb818 100644 --- a/.env.example +++ b/.env.example @@ -36,8 +36,8 @@ TAVILY_API_KEY= # ==================== 安全(强烈建议覆盖) ==================== -# JWT 签名密钥。若留空,服务器会用内置默认值并在启动日志里 WARN。 -# 生产部署必须设置,至少 32 位随机字符串:openssl rand -base64 48 +# 必须设置唯一的随机 JWT 签名密钥:openssl rand -base64 48。 +# 密钥至少 32 个 UTF-8 字节;更换密钥会使现有登录 token 失效。 JWT_SECRET= # CORS 白名单(逗号分隔,如 https://mateclaw.example.com,https://admin.example.com)。 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..e519b054 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +## 3.0.0 — 2026-10-26 + +3.0.0 upgrades to Java 21, Spring Boot 4 and Spring AI 2. AgentScope 2 drives ReAct and plan-step execution loops; MateClaw retains approvals, permissions, Goals, SSE and persistence. + +Release date: **2026-10-26**. These notes cover the 3.0.0 stable release. Source and package versions are `3.0.0`; download published packages from [GitHub Releases](https://github.com/mateaix/mateclaw/releases). + +- MyBatis Plus 3.5.17 with the Boot 4 starter; MCP SDK 0.18.3. +- Native DashScope chat/embedding adapters; native speech SDK retained. +- Owned HTTP cancellation, guarded tool batches and durable Goal checkpoints prevent blind side-effect replay. +- Existing frontend interaction contracts and conversation history remain supported. +- Shared prompt budgets inherit model defaults and count effective tool schemas plus one output reserve; AgentScope remains on stable 2.0.4. +- Latest budget regression: 342 focused tests and 6,509 full-suite tests (50 skipped) with no failures/errors. Real-model acceptance: 18/20 after manual review; tool argument and progress-count failures remain documented in the detailed notes. + +[English details](mateclaw-server/src/main/resources/docs/en/changelog.md) · [中文说明](mateclaw-server/src/main/resources/docs/zh/changelog.md) + +## Finalization, team delivery and deployment + +- Long tasks check completion before returning a final answer and can continue unfinished work within runtime limits. Requests that need user input wait for a reply instead of repeatedly starting action turns (#668). +- When reasoning consumes the response output budget, final-answer generation can retry within runtime limits. DeepSeek Flash thinking controls are applied consistently (#672). +- Team worker progress retains full result evidence and runtime failure causes. A Team Run publishes completion after final synthesis, keeping worker output, progress and the delivered summary consistent. +- DSH keeps its reviewed default SDK version; `0.2.1-rc.2` is an opt-in candidate. Docker runtime images pin Node.js 22 and include bubblewrap for Linux sandbox execution (#670). Candidate checks on macOS arm64 do not establish Linux or Windows acceptance; see [DSH setup and limitations](mateclaw-server/src/main/resources/docs/en/deepseek-harness.md). +- Every backend requires a non-empty `JWT_SECRET` of at least 32 UTF-8 bytes. Desktop local mode generates and persists its secret; source and server deployments must configure and retain theirs. See [configuration](mateclaw-server/src/main/resources/docs/en/config.md). + +## Cumulative changes since 2.3.0 + +3.0.0 includes the former 2.4.0 development cycle; 2.4.0 was not published as a separate stable release. The groups below cover capabilities added before the architecture migration as well. + +### Workspaces and conversations + +- Member file isolation is an opt-in strict sandbox. With `MATECLAW_WORKSPACE_SANDBOX_MEMBER_ISOLATION_ENABLED=true`, authenticated user and workspace identities determine the file root; missing identity denies access without shared-directory fallback. Only six audited builtin file/code tools are available; DSH, MCP/plugin tools, shared skill scripts and unauthenticated channel identities are unavailable. This differs from personal memory-file fallback. (#644). +- Persistent channel conversations expire stale query context. Uploaded images resolve from conversation storage; directory uploads retain directory metadata and validate containment. +- Employee drafts accept JSON following reasoning blocks, and explicit tool opt-out is respected even for general capabilities (#648, #655). + +### Structured decisions, Goals, plans and teams + +- A bounded typed Choice / Boolean / Score decision pipeline adds durable audit, OFF / SHADOW / ACTIVE modes and scenario overrides. SHADOW is the default; suggestions cannot bypass domain constraints. +- Goal continuation, plan employee routing and team worker-result judgment share the decision foundation. Domain transactions commit outcomes and state; delayed decisions validate identity and fences. +- Richer routing context, worker contracts, delegated-step result checks and bounded Goal recovery retain existing completion gates and record applied follow-up suppression. + +### Models and DeepSeek Harness + +- DSH moves to the SDK runtime with pinned versions and controlled upgrades. Failed health checks invalidate cached health and broken settings remain editable (#650). +- Model probes validate completed responses rather than accepting connection success or empty output (#658). + +### Read-aloud, chat and tool presentation + +- Edge and DashScope read-aloud are restored, with provider voice lists, selection and preview (#646). +- Structured MCP results get dedicated presentation below the final answer. Ordinary tool results no longer automatically become final-answer text (#651, #656, #661). + +### Channels and database operations + +- Application-owned Feishu WebSocket readiness and lifecycle release owned resources on shutdown and reconnect (#659). +- Unsupported webhook channels are rejected before configuration persistence (#654). +- Flyway validation failures block startup by default; history repair requires explicit opt-in, with backup and recovery guidance (#657). +- Commercial-edition overview and links are added; commercial modules are not included in the open-source edition. diff --git a/README.md b/README.md index ce89431e..e016aa8f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@
+> **3.0.0 stable release · 2026-10-26**. Java 21 / Spring Boot 4.1.1 / Spring AI 2.0.1 / AgentScope 2.0.4. AgentScope drives execution loops; MateClaw retains tool approvals, permissions, Goals, SSE and persistence. See Releases for stable builds. +> [3.0.0 architecture changelog](CHANGELOG.md) +> Includes all changes since 2.3.0: structured decisions, member file isolation, DSH SDK upgrades, voice preview and structured MCP presentation. + +

MateClaw Logo

@@ -15,7 +20,7 @@ [![Live Demo](https://img.shields.io/badge/Demo-Online-orange.svg?logo=vercel&label=Demo)](https://claw-demo.mate.vip) [![Website](https://img.shields.io/badge/Website-claw.mate.vip-blue.svg?logo=googlechrome&label=Site)](https://claw.mate.vip) [![Java Version](https://img.shields.io/badge/Java-21+-blue.svg?logo=openjdk&label=Java)](https://adoptium.net/) -[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen.svg?logo=springboot)](https://spring.io/projects/spring-boot) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-4.1.1-brightgreen.svg?logo=springboot)](https://spring.io/projects/spring-boot) [![Vue](https://img.shields.io/badge/Vue-3-4FC08D.svg?logo=vuedotjs)](https://vuejs.org/) [![Last Commit](https://img.shields.io/github/last-commit/mateaix/mateclaw)](https://github.com/mateaix/mateclaw) [![License](https://img.shields.io/badge/license-Apache--2.0-red.svg?logo=opensourceinitiative&label=License)](LICENSE) @@ -30,7 +35,7 @@ --- -> **v2.3.0 (released 2026-09-20) — JSON acceptance and execution evidence for persistent Goals.** Users can configure required JSON fields and bind checks to current requirements and immutable artifact versions. This release also adds controlled worker intervention and skill document/folder uploads, with fixes for DSH history and output budgets, vLLM reasoning, and SSE framing. [Read the release notes](https://claw.mate.vip/docs/en/releases/2.3.0). +> **3.0.0 — Governed execution, recoverable long tasks and team delivery.** Java 21, Spring Boot 4, Spring AI 2 and AgentScope 2 power the native runtime. Consistent context budgets, final-answer checks and team synthesis strengthen delivery while preserving approvals and workspace boundaries. [Release and upgrade notes](https://claw.mate.vip/docs/en/releases/3.0.0). Release date: **2026-10-26**. --- @@ -38,7 +43,7 @@ > > Multi-user workspaces. Approval-gated sensitive actions. Full audit trail. Spring Boot Actuator health monitoring. Per-channel error isolation so one chat platform's outage doesn't take down the rest. One JAR in your environment; you control persisted data, and task content is sent only to model, channel, or tool services you explicitly configure. > -> **And underneath, a real Agent Runtime.** An employee is no longer welded to one reasoning loop. Choose the native StateGraph runtime for ReAct, Plan-and-Execute, Goals, and Team Runs, or run DeepSeek Harness as a managed external loop over authenticated JSON-RPC. Both paths converge on the same conversations, workspace boundaries, Tool Guard, event projection, and lifecycle controls. +> **And underneath, a real Agent Runtime.** An employee is no longer welded to one reasoning loop. Choose the native AgentScope runtime for ReAct, Plan-and-Execute, Goals, and Team Runs, or run DeepSeek Harness as a managed external loop over SDK JSON-RPC. Both paths converge on the same conversations, workspace boundaries, Tool Guard, event projection, and lifecycle controls. Most AI tools die when their vendor has a bad day. Most forget you the moment the tab closes. Most give you a chatbox and call it a product. @@ -46,6 +51,18 @@ Most AI tools die when their vendor has a bad day. Most forget you the moment th --- +## What changes in 3.0.0 + +3.0.0 includes the unpublished 2.4.0 development cycle. Release date: **2026-10-26**. Download published packages from Releases. + +- **Native execution:** AgentScope Java **2.0.4** runs ReAct and the tool loop within each Plan-Execute step. MateClaw retains plan scheduling, tool approvals, workspace permissions, cancellation, Goal acceptance and persistence. This path does not require a beta AgentScope Harness API. +- **Consistent context budgets:** request preparation, compaction retries and summary calls account for messages, effective tool schemas and one output reserve. Partial request options inherit model defaults; an explicitly empty tool list disables inherited callbacks. Estimates are conservative, rather than provider billing counts. +- **Governed decisions and recovery:** typed decisions default to SHADOW mode; existing domain constraints and Goal budgets still apply. Durable checkpoints block blind replay when side effects are uncertain. +- **Long tasks and team delivery:** unfinished work can continue within runtime limits, requests needing user input wait for a reply, and Team Runs publish completion after final synthesis. Worker progress preserves full results and failure causes. +- **Everyday improvements:** opt-in strict member file isolation, managed DSH SDK upgrades, voice selection/preview, structured MCP results and stricter model probes. + +Use Java 21 for source builds. Back up the database, workspace, plugins and configuration before migration; use that backup for rollback. [Architecture and upgrade notes](mateclaw-server/src/main/resources/docs/en/changelog.md) · [Model budget rules](mateclaw-server/src/main/resources/docs/en/models.md) + ## Three things that make it different ### 1 · Your AI doesn't die when a model does @@ -86,7 +103,7 @@ Same brain. Same memory. Same tools. Different doors. You hire coworkers, not chat boxes. Each one has a **Role**, a **Goal**, a **Backstory**, a runtime, a pixel-art avatar, and a color of their own — six built-in templates ship ready (General Assistant · Product Assistant · Research Analyst · Customer Support · Data Analyst · Code Reviewer). Employee identity and governance stay stable even when the execution engine changes. ### Agent Runtime: native or DSH (2.2.0+) -The `AgentRuntimeProvider` contract separates an employee from the engine that runs its turn. The **native runtime** keeps ReAct, Plan-and-Execute, persistent Goals, and Team Runs inside MateClaw. The **DSH runtime** manages `dsh-jsonrpc-agent` as an authenticated child process and streams thinking, text, tool calls, usage, completion, and cancellation back as normalized runtime events. DSH owns the external Agent loop; MateClaw still owns the session, workspace, credentials, tools, approvals, messages, and UI projection. Runtime availability and capabilities are validated before startup, and DSH can be installed, verified, connection-tested, enabled, or disabled from the console. [Configure DeepSeek Harness →](https://claw.mate.vip/docs/en/deepseek-harness) +The `AgentRuntimeProvider` contract separates an employee from the engine that runs its turn. The **native runtime** keeps ReAct, Plan-and-Execute, persistent Goals, and Team Runs inside MateClaw. The **DSH runtime** launches the official `dsh --profile sdk` CLI and normalizes its thinking, text, tools, usage, completion, and cancellation events. Managed installation pins `0.2.0-rc.1` (alternative: `0.1.7-rc.2`) with reviewed npm locks and Node.js 22. An opt-in `0.2.1-rc.2` candidate adds SDK compatibility updates; macOS arm64 installation, handshake and short real-model/tool checks passed, while default selection stays unchanged. Upgrades copy runtime homes, check the candidate, and switch a retained generation; rollback restores the previous home/configuration without merging newer sessions. Package and real-model adapter checks passed on macOS arm64; Linux x64 and persisted V3→V4 history migration remain unverified. [Configure DeepSeek Harness →](https://claw.mate.vip/docs/en/deepseek-harness) ### Durable long tasks: checkpoint, restart, continue (2.2.0+) Persistent Goals turn work that takes hours into bounded, recoverable segments. The database preserves the goal checklist, continuation state, attempts, cooldowns, leases, and user input accepted while the worker is busy. After a single backend instance restarts, the supervisor reconciles the interrupted attempt, reads persisted checkpoints and artifacts, and schedules the next safe segment instead of asking you to repeat the task. @@ -162,15 +179,22 @@ Same "whole widget" philosophy. Different center of gravity. ```bash # Backend -cd mateclaw-server -mvn spring-boot:run # http://localhost:18088 +# Java 21 and Maven required. Create a local JWT secret once and reuse it on restart. +[ -f .env.local ] || (umask 077; printf 'JWT_SECRET=%s\n' "$(openssl rand -hex 48)" > .env.local) +set -a +source .env.local +set +a +# From the repository root; use its configured models and workspace +SPRING_DATASOURCE_URL="jdbc:h2:file:$PWD/data/mateclaw;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE" \ +MATECLAW_WORKSPACE_SANDBOX_ROOT="$PWD/data/workspace" \ +mvn -pl mateclaw-server spring-boot:run # http://localhost:18088 -# Frontend +# Frontend (new terminal, from the repository root) cd mateclaw-ui -npm install && npm run dev # http://localhost:5173 +npm install && npm run dev -- --host 127.0.0.1 # http://localhost:5173 ``` -Login: `admin` / `admin123` +On a fresh database, log in with `admin` / `admin123`, change the password, then configure a model in **Settings → Models**. Existing model settings are read from the root `data/` database; starting against `mateclaw-server/data/` opens a different database. Keep `.env.local` private and set a non-empty `JWT_SECRET` of at least 32 UTF-8 bytes. ### Docker @@ -204,7 +228,7 @@ Download from [GitHub Releases](https://github.com/mateaix/mateclaw/releases). B ``` mateclaw/ -├── mateclaw-server/ Spring Boot 3.5 backend (Agent Runtime contract, native StateGraph + DSH) +├── mateclaw-server/ Spring Boot 4.1.1 backend (Agent Runtime contract, native AgentScope + DSH) ├── mateclaw-ui/ Vue 3 + TypeScript admin SPA (built into the server JAR) ├── mateclaw-desktop/ Electron desktop app (local-embedded / remote-centralized) ├── mateclaw-webchat/ Embeddable chat widget (UMD / ES bundles) @@ -222,8 +246,8 @@ Desktop binaries ship via [GitHub Releases](https://github.com/mateaix/mateclaw/ | Layer | Technology | |---|---| -| Backend | Spring Boot 3.5 · Spring AI Alibaba 1.1 · MyBatis Plus · Flyway | -| Agent Runtime | `AgentRuntimeProvider` contract · Native StateGraph (ReAct + Plan-Execute) · managed DSH JSON-RPC runtime · normalized events / lifecycle / usage · Tool Guard | +| Backend | Spring Boot 4.1.1 · Spring AI 2.0.1 · AgentScope Java 2.0.4 · MyBatis Plus · Flyway | +| Agent Runtime | `AgentRuntimeProvider` contract · Native AgentScope (ReAct + Plan-Execute) · managed DSH JSON-RPC runtime · normalized events / lifecycle / usage · Tool Guard | | Orchestration | Workflow (7 step modes · Pebble DSL) · Triggers (6 pattern types · event governance) · Wiki Transformations (1.3.0+) | | Capability Extension | SKILL.md packages · MCP (stdio / SSE / HTTP · per-agent binding) · ACP bridge (Claude Code / Codex) | | Database | H2 (dev) · PostgreSQL 16 (Docker default) · MySQL 8.0+ (supported) · Kingbase (opt-in driver) | @@ -320,8 +344,8 @@ Full story in the [v1.7.0 release notes](https://claw.mate.vip/docs/en/releases/ ```bash git clone https://github.com/mateaix/mateclaw.git cd mateclaw -cd mateclaw-server && mvn clean compile -cd ../mateclaw-ui && npm install && npm run dev +mvn -pl mateclaw-server -am clean compile +cd mateclaw-ui && npm install && npm run dev -- --host 127.0.0.1 ``` --- diff --git a/README_zh.md b/README_zh.md index c2eb7bcb..f85f9950 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,5 +1,10 @@
+> **3.0.0 正式版 · 2026-10-26**。Java 21 / Spring Boot 4.1.1 / Spring AI 2.0.1 / AgentScope 2.0.4。AgentScope 驱动执行循环,MateClaw 保留工具审批、权限、Goal、SSE 与持久化。正式发行版请查看 Releases。 +> [3.0.0 架构升级日志](CHANGELOG.md) +> 包含 2.3.0 之后全部累计更新:结构化决策、成员文件隔离、DSH SDK 升级、朗读音色试听和结构化 MCP 展示。 + +

MateClaw Logo

@@ -15,7 +20,7 @@ [![在线演示](https://img.shields.io/badge/演示-在线-orange.svg?logo=vercel&label=Demo)](https://claw-demo.mate.vip) [![官网](https://img.shields.io/badge/官网-claw.mate.vip-blue.svg?logo=googlechrome&label=Site)](https://claw.mate.vip) [![Java 版本](https://img.shields.io/badge/Java-21+-blue.svg?logo=openjdk&label=Java)](https://adoptium.net/) -[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen.svg?logo=springboot)](https://spring.io/projects/spring-boot) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-4.1.1-brightgreen.svg?logo=springboot)](https://spring.io/projects/spring-boot) [![Vue](https://img.shields.io/badge/Vue-3-4FC08D.svg?logo=vuedotjs)](https://vuejs.org/) [![最后提交](https://img.shields.io/github/last-commit/mateaix/mateclaw)](https://github.com/mateaix/mateclaw) [![许可证](https://img.shields.io/badge/license-Apache--2.0-red.svg?logo=opensourceinitiative&label=License)](LICENSE) @@ -30,7 +35,7 @@ --- -> **v2.3.0(2026-09-20 发布)——持久目标的 JSON 验收与执行证据。** 用户可配置 JSON 必需字段,将检查结果绑定到当前要求和不可变产物版本;同时加入团队成员受控干预、技能文档及文件夹上传,并修复 DSH 历史与输出预算、vLLM 推理内容和 SSE 分帧。 [查看完整更新日志](https://claw.mate.vip/docs/zh/releases/2.3.0)。 +> **3.0.0——受治理的执行、可恢复的长任务与团队交付。** Java 21、Spring Boot 4、Spring AI 2 和 AgentScope 2 驱动原生运行时;统一上下文预算、最终答复检查和团队汇总完善交付,同时保留审批与工作空间边界。[正式版更新与升级说明](https://claw.mate.vip/docs/zh/releases/3.0.0)。发布日期:**2026-10-26**。 --- @@ -38,7 +43,7 @@ > > 多用户工作空间。敏感操作走审批。完整审计日志。Spring Boot Actuator 健康监控。单个渠道挂掉不影响其他渠道的错误隔离。一个 JAR 包跑在自己的环境里;持久化数据由你掌控,任务所需内容只会发送到你主动配置的模型、渠道或工具服务。 > -> **底下是一套真正的 Agent Runtime。** 员工不再焊死在一套推理循环上:可以用原生 StateGraph 运行 ReAct、Plan-and-Execute、Goal 与 Team Run,也可以通过认证 JSON-RPC 把 DeepSeek Harness 作为受管理的外部循环。两条路径最终进入同一套会话、工作空间边界、Tool Guard、事件投影与生命周期控制。 +> **底下是一套真正的 Agent Runtime。** 员工不再焊死在一套推理循环上:可以用原生 AgentScope 运行 ReAct、Plan-and-Execute、Goal 与 Team Run,也可以通过 SDK JSON-RPC 把 DeepSeek Harness 作为受管理的外部循环。两条路径最终进入同一套会话、工作空间边界、Tool Guard、事件投影与生命周期控制。 大多数 AI 工具一到厂商抽风那天就两手一摊。关一次标签页就忘了你是谁。给你一个聊天框,就敢叫产品。 @@ -46,6 +51,18 @@ --- +## 3.0.0 更新概览 + +3.0.0 发布于 **2026-10-26**,包含未单独正式发布的 2.4.0 开发周期更新。发行包请从 Releases 下载。 + +- **原生执行循环**:AgentScope Java **2.0.4** 驱动 ReAct 与 Plan-Execute 每个步骤内部的工具循环。计划调度、工具审批、工作空间权限、取消、Goal 验收和持久化继续由 MateClaw 管理;这条路径不依赖 beta 版 AgentScope Harness API。 +- **统一上下文预算**:请求准备、压缩重试和摘要调用统一计算消息、实际生效的工具 schema 与一份输出预留。请求只覆盖部分选项时继承模型默认值,显式空工具列表关闭默认回调。估算偏保守,不等同于供应商计费 token。 +- **受治理的决策与恢复**:类型化决策默认 SHADOW 模式,继续遵守领域约束和 Goal 预算;持久检查点在副作用结果不确定时阻止盲目重放。 +- **长任务与团队交付**:未完成工作可在运行时限制内继续,需要用户补充信息时等待回复;Team Run 在最终汇总完成后再发布完成状态,成员进度保留完整结果与失败原因。 +- **日常能力完善**:可选严格成员文件隔离、受管理的 DSH SDK 升级、音色选择与试听、MCP 结构化展示及更严格的模型探测。 + +源码构建使用 Java 21。升级前备份数据库、workspace、插件与配置,回滚使用升级前备份。[架构与升级说明](mateclaw-server/src/main/resources/docs/zh/changelog.md) · [模型预算规则](mateclaw-server/src/main/resources/docs/zh/models.md) + ## 三件让它与众不同的事 ### 1 · 模型挂了,AI 不挂 @@ -86,7 +103,7 @@ MateClaw 的 **LLM Wiki** 把它消化成结构化页面,页面之间自己长 你雇佣员工,不是开聊天框。每位有**角色**、**目标**、**背景故事**、运行时、像素艺术头像与专属配色——6 个内置模板(通用助手 · 产品助理 · 研究分析师 · 客服助理 · 数据分析师 · 代码审查员)开箱可用。即使更换执行引擎,员工身份和治理边界仍保持不变。 ### Agent Runtime:Native 或 DSH(2.2.0+) -`AgentRuntimeProvider` contract 把员工与实际执行回合的引擎分开。**Native Runtime** 在 MateClaw 内运行 ReAct、Plan-and-Execute、Persistent Goal 与 Team Run;**DSH Runtime** 把 `dsh-jsonrpc-agent` 作为认证子进程管理,并将思考、文本、工具调用、用量、完成与取消统一映射为 runtime event。DSH 掌管外部 Agent loop,MateClaw 继续掌管 session、workspace、凭证、工具、审批、消息和 UI 投影。启动前会校验 runtime 可用性与能力;控制台可完成 DSH 的安装、配置、校验、连接测试和启停。[配置 DeepSeek Harness →](https://claw.mate.vip/docs/zh/deepseek-harness) +`AgentRuntimeProvider` contract 将员工与执行引擎分离。**Native Runtime** 在 MateClaw 内运行 ReAct、Plan-and-Execute、Persistent Goal 与 Team Run;**DSH Runtime** 启动官方 `dsh --profile sdk` CLI,统一映射思考、文本、工具、用量、完成与取消事件。受管理安装要求 Node.js 22,使用受审查 npm 锁文件固定 `0.2.0-rc.1`(另选 `0.1.7-rc.2`)。新增可选 `0.2.1-rc.2` 候选,macOS arm64 安装、握手和短模型/工具任务已验证,默认选择保持不变。升级复制 home、检查候选并切换保留的代次;回滚恢复旧 home/配置,不合并升级后的新会话。实际包及真实模型适配器检查已在 macOS arm64 通过;Linux x64 和持久化 V3→V4 历史迁移仍待验证。[配置 DeepSeek Harness →](https://claw.mate.vip/docs/zh/deepseek-harness) ### 持久长任务:检查点、重启、继续(2.2.0+) Persistent Goal 把需要数小时的工作拆成有界、可恢复的执行段。数据库会保存目标清单、continuation 状态、attempt、冷却、lease,以及员工忙碌期间已经接收的用户输入。单后端实例重启后,supervisor 会先核对被中断的 attempt,读取持久检查点和已有产物,再调度下一段安全工作,不要求用户重新描述任务。 @@ -162,15 +179,22 @@ RBAC + JWT。**Personal Access Token** 给无人值守脚本和 CI 使用。**We ```bash # 后端 -cd mateclaw-server -mvn spring-boot:run # http://localhost:18088 +# 需要 Java 21 与 Maven。首次生成本地 JWT 密钥,重启时复用。 +[ -f .env.local ] || (umask 077; printf 'JWT_SECRET=%s\n' "$(openssl rand -hex 48)" > .env.local) +set -a +source .env.local +set +a +# 从仓库根目录运行,使用根目录下已配置的模型数据库与工作空间 +SPRING_DATASOURCE_URL="jdbc:h2:file:$PWD/data/mateclaw;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE" \ +MATECLAW_WORKSPACE_SANDBOX_ROOT="$PWD/data/workspace" \ +mvn -pl mateclaw-server spring-boot:run # http://localhost:18088 -# 前端 +# 前端(新终端,从仓库根目录进入) cd mateclaw-ui -npm install && npm run dev # http://localhost:5173 +npm install && npm run dev -- --host 127.0.0.1 # http://localhost:5173 ``` -默认登录:`admin` / `admin123` +新数据库默认登录:`admin` / `admin123`。登录后修改密码,并在 **设置 → 模型** 配置模型。已有模型配置从根目录 `data/` 数据库读取;使用 `mateclaw-server/data/` 会打开另一份数据库。不要提交 `.env.local`,其中 `JWT_SECRET` 必须非空且至少包含 32 个 UTF-8 字节。 ### Docker 部署 @@ -204,7 +228,7 @@ docker compose up -d # http://localhost:18080 ``` mateclaw/ -├── mateclaw-server/ Spring Boot 3.5 后端(Agent Runtime contract · Native StateGraph + DSH) +├── mateclaw-server/ Spring Boot 4.1.1 后端(Agent Runtime contract · Native AgentScope + DSH) ├── mateclaw-ui/ Vue 3 + TypeScript 管理 SPA(构建产物打进后端 JAR) ├── mateclaw-desktop/ Electron 桌面端(本地内嵌 / 远程集中双模式) ├── mateclaw-webchat/ 网页嵌入式聊天组件(UMD / ES bundle) @@ -222,8 +246,8 @@ mateclaw/ | 层次 | 技术 | |---|---| -| 后端 | Spring Boot 3.5 · Spring AI Alibaba 1.1 · MyBatis Plus · Flyway | -| Agent Runtime | `AgentRuntimeProvider` contract · Native StateGraph(ReAct + Plan-Execute)· 受管理的 DSH JSON-RPC runtime · 统一事件 / 生命周期 / 用量 · Tool Guard | +| 后端 | Spring Boot 4.1.1 · Spring AI 2.0.1 · AgentScope Java 2.0.4 · MyBatis Plus · Flyway | +| Agent Runtime | `AgentRuntimeProvider` contract · Native AgentScope(ReAct + Plan-Execute)· 受管理的 DSH JSON-RPC runtime · 统一事件 / 生命周期 / 用量 · Tool Guard | | 业务编排 | 工作流(7 step mode · Pebble DSL)· 触发器(6 pattern type · 事件治理)· Wiki 加工器(1.3.0+)| | 能力扩展 | SKILL.md 包 · MCP(stdio / SSE / HTTP · per-agent 绑定)· ACP 桥接(Claude Code / Codex) | | 数据库 | H2(开发)· PostgreSQL 16(Docker 默认)· MySQL 8.0+(支持)· Kingbase(按需驱动)| @@ -320,8 +344,8 @@ mateclaw/ ```bash git clone https://github.com/mateaix/mateclaw.git cd mateclaw -cd mateclaw-server && mvn clean compile -cd ../mateclaw-ui && npm install && npm run dev +mvn -pl mateclaw-server -am clean compile +cd mateclaw-ui && npm install && npm run dev -- --host 127.0.0.1 ``` --- diff --git a/assets/architecture-biz-en.svg b/assets/architecture-biz-en.svg index d20d11a1..76330ea6 100644 --- a/assets/architecture-biz-en.svg +++ b/assets/architecture-biz-en.svg @@ -33,7 +33,7 @@ Digital Employee - Identity · Goal · Governance + Identity · Goal · Decision Native Runtime · DSH Runtime One policy + event plane diff --git a/assets/architecture-biz-zh.svg b/assets/architecture-biz-zh.svg index 1938471d..d1923871 100644 --- a/assets/architecture-biz-zh.svg +++ b/assets/architecture-biz-zh.svg @@ -34,7 +34,7 @@ 数字员工 - 身份 · 目标 · 治理 + 身份 · 目标 · 决策 Native Runtime · DSH Runtime 同一策略与事件平面 diff --git a/assets/architecture-tech-en.svg b/assets/architecture-tech-en.svg index c57f0088..c2b6a1b0 100644 --- a/assets/architecture-tech-en.svg +++ b/assets/architecture-tech-en.svg @@ -81,20 +81,20 @@ Native Runtime - StateGraph · ReAct - Plan-Execute · Goals · Teams + AgentScope 2 · ReAct + Plan-step execution DSH Runtime Managed JSON-RPC Process - DeepSeek Harness · Cordis + SDK · Pinned upgrades Host Governance Workspace · Tool Guard - Approval · Credentials + Approval · Checkpoints @@ -110,7 +110,7 @@ - Spring Boot 3.5 + Spring Boot 4.1 App Framework Spring MVC + SSE @@ -158,7 +158,7 @@ - H2 / MySQL 8 + H2 · PG · MySQL Relational Storage @@ -194,7 +194,7 @@ - Java 21+ · Spring Boot 3.5 · Spring AI Alibaba · Vue 3 · TypeScript · Vite · Electron + 3.0.0 · Java 21 · Boot 4 · Spring AI 2 · AgentScope 2 · Vue 3 · Electron claw.mate.vip diff --git a/assets/architecture-tech-zh.svg b/assets/architecture-tech-zh.svg index 2ab7a073..4d2d17ee 100644 --- a/assets/architecture-tech-zh.svg +++ b/assets/architecture-tech-zh.svg @@ -84,20 +84,20 @@ Native Runtime - StateGraph · ReAct - Plan-Execute · Goal · Team + AgentScope 2 · ReAct + 计划单步执行 DSH Runtime 受管理 JSON-RPC 进程 - DeepSeek Harness · Cordis + SDK · Pinned upgrades 宿主治理 Workspace · Tool Guard - 审批 · 凭证隔离 + 审批 · 检查点 · 决策 @@ -113,7 +113,7 @@ - Spring Boot 3.5 + Spring Boot 4.1 应用框架 Spring MVC + SSE @@ -161,7 +161,7 @@ - H2 / MySQL 8 + H2 · PG · MySQL 关系型存储 @@ -198,7 +198,7 @@ - Java 21+ · Spring Boot 3.5 · Spring AI Alibaba · Vue 3 · TypeScript · Vite · Electron + 3.0.0 · Java 21 · Boot 4 · Spring AI 2 · AgentScope 2 · Vue 3 · Electron claw.mate.vip diff --git a/docker-compose.yml b/docker-compose.yml index 6a8a0411..e79589f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,7 +107,7 @@ services: # Keys are stored in mate_model_provider and hot-reloaded. SERPER_API_KEY: ${SERPER_API_KEY:-} TAVILY_API_KEY: ${TAVILY_API_KEY:-} - JWT_SECRET: ${JWT_SECRET:-} + JWT_SECRET: "${JWT_SECRET:?Set JWT_SECRET in .env; generate one with openssl rand -base64 48}" MATECLAW_CORS_ALLOWED_ORIGINS: ${MATECLAW_CORS_ALLOWED_ORIGINS:-} # SearXNG: tell the app where to reach the sidecar container SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://searxng:8080} diff --git a/mateclaw-desktop/electron/main/index.ts b/mateclaw-desktop/electron/main/index.ts index 5e288468..f9de8468 100644 --- a/mateclaw-desktop/electron/main/index.ts +++ b/mateclaw-desktop/electron/main/index.ts @@ -20,6 +20,7 @@ import { expandPath, type LocalToolsConfig, } from './localToolsConfig' +import { getLocalJwtSecret } from './jwtSecret.mjs' import { LocalBridge } from './localBridge' // ─── Constants ─────────────────────────────────────────────────────────────── @@ -227,6 +228,7 @@ async function startJavaBackend(): Promise { // Prepare environment variables — inherit current env + override const env = { ...process.env, + JWT_SECRET: getLocalJwtSecret(app.getPath('userData'), process.env.JWT_SECRET), // Ensure H2 database is stored in userData SPRING_DATASOURCE_URL: `jdbc:h2:file:${join(workingDir, 'data', 'mateclaw')};MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE`, } @@ -812,7 +814,7 @@ function showAboutDialog(): void { detail: [ `Version: ${app.getVersion()}`, '', - 'Your intelligent AI assistant powered by Spring AI Alibaba.', + 'Your intelligent AI assistant powered by Spring AI 2 + AgentScope 2.', '', `Copyright © 2026 MateClaw Team`, ].join('\n'), diff --git a/mateclaw-desktop/electron/main/jwtSecret.d.mts b/mateclaw-desktop/electron/main/jwtSecret.d.mts new file mode 100644 index 00000000..f27ed35c --- /dev/null +++ b/mateclaw-desktop/electron/main/jwtSecret.d.mts @@ -0,0 +1 @@ +export function getLocalJwtSecret(userDataPath: string, explicitSecret?: string): string diff --git a/mateclaw-desktop/electron/main/jwtSecret.mjs b/mateclaw-desktop/electron/main/jwtSecret.mjs new file mode 100644 index 00000000..0714d280 --- /dev/null +++ b/mateclaw-desktop/electron/main/jwtSecret.mjs @@ -0,0 +1,35 @@ +import { randomBytes } from 'node:crypto' +import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' + +const SECRET_FILE = 'jwt-secret' + +function readSecret(path) { + const secret = readFileSync(path, 'utf8') + chmodSync(path, 0o600) + return secret +} + +export function getLocalJwtSecret(userDataPath, explicitSecret) { + if (explicitSecret !== undefined) return explicitSecret + + mkdirSync(userDataPath, { recursive: true, mode: 0o700 }) + const secretPath = join(userDataPath, SECRET_FILE) + + try { + return readSecret(secretPath) + } catch (error) { + if (error.code !== 'ENOENT') throw error + } + + const secret = randomBytes(48).toString('base64url') + + try { + writeFileSync(secretPath, secret, { encoding: 'utf8', flag: 'wx', mode: 0o600 }) + chmodSync(secretPath, 0o600) + return secret + } catch (error) { + if (error.code !== 'EEXIST') throw error + return readSecret(secretPath) + } +} diff --git a/mateclaw-desktop/package.json b/mateclaw-desktop/package.json index 1753a344..05560460 100644 --- a/mateclaw-desktop/package.json +++ b/mateclaw-desktop/package.json @@ -1,7 +1,7 @@ { "name": "mateclaw-desktop", - "version": "2.3.0", - "description": "MateClaw Desktop - AI Assistant powered by Spring AI Alibaba", + "version": "3.0.0", + "description": "MateClaw Desktop - AI Assistant powered by Spring AI 2 + AgentScope 2", "author": "MateClaw Team", "license": "Apache-2.0", "main": "dist-electron/main/index.js", diff --git a/mateclaw-desktop/scripts/__tests__/jwt-secret.test.mjs b/mateclaw-desktop/scripts/__tests__/jwt-secret.test.mjs new file mode 100644 index 00000000..3fb83798 --- /dev/null +++ b/mateclaw-desktop/scripts/__tests__/jwt-secret.test.mjs @@ -0,0 +1,43 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' +import { getLocalJwtSecret } from '../../electron/main/jwtSecret.mjs' + +function makeUserDataPath(t) { + const userDataPath = fs.mkdtempSync(path.join(os.tmpdir(), 'mateclaw-jwt-secret-')) + t.after(() => fs.rmSync(userDataPath, { recursive: true, force: true })) + return userDataPath +} + +test('creates a random per-installation secret with private file permissions', (t) => { + const userDataPath = makeUserDataPath(t) + const secretPath = path.join(userDataPath, 'jwt-secret') + const secret = getLocalJwtSecret(userDataPath) + + assert.match(secret, /^[A-Za-z0-9_-]{64}$/) + assert.equal(Buffer.from(secret, 'base64url').length, 48) + assert.equal(fs.readFileSync(secretPath, 'utf8'), secret) + + if (process.platform !== 'win32') { + assert.equal(fs.statSync(secretPath).mode & 0o777, 0o600) + } +}) + +test('reuses the persisted secret after restart', (t) => { + const userDataPath = makeUserDataPath(t) + const first = getLocalJwtSecret(userDataPath) + const afterRestart = getLocalJwtSecret(userDataPath) + + assert.equal(afterRestart, first) +}) + +test('preserves explicit overrides, including invalid values for server-side rejection', (t) => { + const userDataPath = makeUserDataPath(t) + + assert.equal(getLocalJwtSecret(userDataPath, 'explicit-secret-with-more-than-32-bytes'), 'explicit-secret-with-more-than-32-bytes') + assert.equal(getLocalJwtSecret(userDataPath, ''), '') + assert.equal(getLocalJwtSecret(userDataPath, 'short'), 'short') + assert.equal(fs.existsSync(path.join(userDataPath, 'jwt-secret')), false) +}) diff --git a/mateclaw-server/Dockerfile b/mateclaw-server/Dockerfile index 6c5b672b..b0bc3abb 100644 --- a/mateclaw-server/Dockerfile +++ b/mateclaw-server/Dockerfile @@ -67,6 +67,10 @@ COPY --from=frontend-builder /static mateclaw-server/src/main/resources/static RUN mvn -pl mateclaw-server -am package -Dmaven.test.skip=true -q ${MAVEN_FLAGS} +# Node/npm for the managed dsh installer. Keep the runtime on Node 22 even +# when Playwright changes its bundled Node major. Bookworm glibc runs on Noble. +FROM node:22-bookworm-slim AS dsh-node + # Stage 3 — Runtime # # Uses Microsoft's official Playwright image (Ubuntu Noble, glibc) with all three @@ -78,9 +82,16 @@ RUN mvn -pl mateclaw-server -am package -Dmaven.test.skip=true -q ${MAVEN_FLAGS} # bump the Java dependency, bump this tag in lockstep — Microsoft rebuilds each # tag with the matching driver, so mismatched versions cause the java driver to # re-download browsers at runtime (defeating the whole point of this image). -FROM mcr.microsoft.com/playwright:v1.62.0-noble +FROM mcr.microsoft.com/playwright:v1.62.0-noble AS runtime-base WORKDIR /app +COPY --from=dsh-node /usr/local/bin/node /usr/local/bin/node +COPY --from=dsh-node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/ +RUN ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \ + && ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \ + && node -e 'if (process.versions.node.split(".")[0] !== "22") throw Error("dsh requires Node 22")' \ + && npm --version + # JDK 21 is NOT part of the base image (it ships Node for the JS driver). # Install openjdk-21 explicitly and add CJK fonts so Chinese pages render # correctly in screenshots and snapshots. @@ -111,6 +122,7 @@ RUN apt-get update \ python-is-python3 \ python3-dev \ build-essential \ + bubblewrap \ && rm -rf /var/lib/apt/lists/* # Remove PEP 668's EXTERNALLY-MANAGED marker so pip can install packages @@ -135,6 +147,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \ # silently pin the profile regardless of what compose passes. ENV SPRING_PROFILES_ACTIVE=mysql +FROM runtime-base AS runtime COPY --from=builder /build/mateclaw-server/target/*.jar app.jar EXPOSE 18088 diff --git a/mateclaw-server/pom.xml b/mateclaw-server/pom.xml index f25d542c..847f0c1d 100644 --- a/mateclaw-server/pom.xml +++ b/mateclaw-server/pom.xml @@ -15,7 +15,7 @@ jar MateClaw Server - MateClaw - Java+Vue Personal AI Assistant powered by Spring AI Alibaba + MateClaw - Java+Vue Personal AI Assistant powered by Spring AI and AgentScope @@ -24,10 +24,44 @@ mateclaw-plugin-api - + org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-webclient + + + + org.springframework.boot + spring-boot-jackson2 + + + org.springframework.boot + spring-boot-starter-flyway + + + io.agentscope + agentscope-core + ${agentscope.version} + + + com.squareup.okhttp3 + okhttp-jvm + + + + + com.networknt + json-schema-validator + ${networknt-schema.version} + + + org.springframework.retry + spring-retry + 2.0.12 @@ -36,27 +70,23 @@ spring-boot-starter-actuator - - + - com.alibaba.cloud.ai - spring-ai-alibaba-starter-dashscope - + com.alibaba + dashscope-sdk-java + 2.22.2 + - org.springframework.boot - spring-boot-starter-webflux + org.slf4j + slf4j-simple - - com.alibaba.cloud.ai - spring-ai-alibaba-graph-core + org.springframework.ai + spring-ai-client-chat @@ -71,19 +101,10 @@ spring-ai-anthropic - - + - org.springframework.ai - spring-ai-starter-mcp-client - - - org.springframework.boot - spring-boot-starter-webflux - - + io.modelcontextprotocol.sdk + mcp @@ -103,7 +124,7 @@ com.baomidou - mybatis-plus-spring-boot3-starter + mybatis-plus-spring-boot4-starter @@ -197,6 +218,10 @@ net.dv8tion JDA + + com.squareup.okhttp3 + okhttp-jvm + club.minnced @@ -382,6 +407,19 @@ test + + com.squareup.okhttp3 + mockwebserver + 4.12.0 + test + + + com.squareup.okhttp3 + okhttp-tls + 4.12.0 + test + + + +
+ +
+ @@ -587,13 +595,15 @@ import { buildGeneratedFileNameMap, linkifyGeneratedFileUrls } from '@/utils/gen import { ensureModelViewer } from '@/utils/lazyModelViewer' import { useAuthenticatedAttachment } from '@/composables/useAuthenticatedAttachment' import { useToolLabel } from '@/composables/useToolLabel' -import { http } from '@/api' +import { http, fetchAuthenticatedBlob } from '@/api' import { copyToClipboard } from '@/utils/clipboard' import TypingCursor from './TypingCursor.vue' import { previewKindOf } from './preview/previewKind' import { openFilePreview } from './preview/previewBus' import BrowserTimeline from './BrowserTimeline.vue' import ToolCallSegment from './ToolCallSegment.vue' +import ToolResultView from './tool-results/ToolResultView.vue' +import { hasToolResultUi } from './tool-results/registry' import ThinkingSegment from './ThinkingSegment.vue' import ContentSegment from './ContentSegment.vue' import GoalAvatarRing from '@/components/goal/GoalAvatarRing.vue' @@ -853,59 +863,59 @@ function copyMessage() { // --- TTS 朗读 --- const ttsState = ref<'idle' | 'loading' | 'playing'>('idle') let ttsAudio: HTMLAudioElement | null = null +let ttsBlobUrl: string | null = null +let ttsRequestVersion = 0 + +function stopTts() { + ttsAudio?.pause() + if (ttsAudio) { ttsAudio.onended = null; ttsAudio.onerror = null } + ttsAudio = null + if (ttsBlobUrl) URL.revokeObjectURL(ttsBlobUrl) + ttsBlobUrl = null + ttsState.value = 'idle' +} async function handleTts() { if (ttsState.value === 'playing') { - // 停止播放 - ttsAudio?.pause() - ttsAudio = null - ttsState.value = 'idle' + stopTts() return } - + if (ttsState.value === 'loading') return const text = displayContent.value || props.message.content || '' - if (!text) return - const conversationId = props.message.conversationId - if (!conversationId) return + if (!text || !conversationId) return + const version = ++ttsRequestVersion ttsState.value = 'loading' try { - const res: any = await http.post('/tts/synthesize', { - conversationId, - text, - }) - if (res.data?.success && res.data?.audioUrl) { - // 通过认证 fetch 获取音频 blob - const audioRes = await fetch(res.data.audioUrl, { - headers: { Authorization: `Bearer ${localStorage.getItem('token') || ''}` }, - }) - const blob = await audioRes.blob() - const blobUrl = URL.createObjectURL(blob) - ttsAudio = new Audio(blobUrl) - ttsAudio.onended = () => { - ttsState.value = 'idle' - URL.revokeObjectURL(blobUrl) - ttsAudio = null - } - ttsAudio.onerror = () => { - ttsState.value = 'idle' - URL.revokeObjectURL(blobUrl) - ttsAudio = null - } - ttsState.value = 'playing' - await ttsAudio.play() - } else { - ttsState.value = 'idle' + // The shared interceptor already unwraps the raw TTS response body. + const result: any = await http.post('/tts/synthesize', { conversationId, text }, { timeout: 240_000 }) + if (version !== ttsRequestVersion) return + if (!result?.success || !result.audioUrl) { + throw new Error(result?.error || t('chat.ttsFailed')) } - } catch { - ttsState.value = 'idle' + const blob = await fetchAuthenticatedBlob(result.audioUrl) + if (version !== ttsRequestVersion) return + ttsBlobUrl = URL.createObjectURL(blob) + ttsAudio = new Audio(ttsBlobUrl) + ttsAudio.onended = stopTts + ttsAudio.onerror = () => { + stopTts() + mcToast.error(t('chat.ttsFailed')) + } + await ttsAudio.play() + if (version === ttsRequestVersion && ttsAudio) ttsState.value = 'playing' + } catch (error) { + if (version !== ttsRequestVersion) return + stopTts() + mcToast.error(error instanceof Error ? error.message : t('chat.ttsFailed')) } } onBeforeUnmount(() => { if (copyTimer) clearTimeout(copyTimer) - if (ttsAudio) { ttsAudio.pause(); ttsAudio = null } + ++ttsRequestVersion + stopTts() revokeAll() }) @@ -1195,9 +1205,12 @@ const segments = computed(() => { const toolCalls = meta?.toolCalls || [] toolCalls.forEach((tc: ToolCallMeta, i: number) => { segs.push({ - id: `tc-${i}`, type: 'tool_call', status: 'completed', + id: `tc-${i}`, type: 'tool_call', + status: tc.success === false ? 'error' + : (!tc.status || tc.status === 'completed' ? 'completed' : 'running'), toolName: tc.name, toolArgs: tc.arguments, toolResult: tc.result, toolSuccess: tc.success, + toolCallId: tc.toolCallId, structuredContent: tc.structuredContent, }) }) if (props.message.content) { @@ -1206,9 +1219,24 @@ const segments = computed(() => { return segs }) +// Use the same deduplicated source for live and reloaded messages. Never +// concatenate toolCalls with segments: they are two copies of the same calls. +const answerToolResults = computed(() => { + const seen = new Set() + return segments.value.filter(seg => { + if (seg.type !== 'tool_call' || seg.status !== 'completed' + || seg.toolSuccess === false || !hasToolResultUi(seg.structuredContent)) return false + const key = seg.toolCallId || seg.id + if (seen.has(key)) return false + seen.add(key) + return true + }) +}) + /** * Use segmented rendering when there are multiple segments, OR when the turn - * contains a delegation segment. Delegations live in `segments` but not in + * contains structured output or a delegation segment. Structured output must + * remain visible even when it is the only segment. Delegations live in `segments` but not in * `metadata.toolCalls`, so the fallback path (which only reads toolCalls) * renders nothing for them — a single-step plan that delegates to a subagent * would otherwise show the subagent call as completely invisible. Forcing @@ -1216,7 +1244,7 @@ const segments = computed(() => { */ const useSegmentedView = computed(() => segments.value.length > 1 || - segments.value.some(s => s.type === 'tool_call' && (s.toolName || '').startsWith('→')) + segments.value.some(s => s.type === 'tool_call' && (s.structuredContent || (s.toolName || '').startsWith('→'))) ) /** diff --git a/mateclaw-ui/src/components/chat/ToolCallSegment.vue b/mateclaw-ui/src/components/chat/ToolCallSegment.vue index b2642d7a..f8c59505 100644 --- a/mateclaw-ui/src/components/chat/ToolCallSegment.vue +++ b/mateclaw-ui/src/components/chat/ToolCallSegment.vue @@ -5,10 +5,13 @@ import { useToolLabel } from '@/composables/useToolLabel' import type { MessageSegment } from '@/types' import DelegationNodeView from './DelegationNodeView.vue' import ExecutionDetailDialog from './ExecutionDetailDialog.vue' +import ToolResultView from './tool-results/ToolResultView.vue' -const props = defineProps<{ +const props = withDefaults(defineProps<{ segment: MessageSegment -}>() + /** MessageBubble presents rich output in the answer instead of the timeline. */ + showStructuredResult?: boolean +}>(), { showStructuredResult: true }) const { getToolLabel } = useToolLabel() @@ -199,6 +202,8 @@ const detailStatus = computed<'running' | 'completed' | 'error'>(() => {
+ + ({ useStreamingMarkdown: (content: unknown) => ({ @@ -29,7 +31,7 @@ vi.mock('@/composables/useMcToast', () => ({ vi.mock('@/composables/useToolLabel', () => ({ useToolLabel: () => ({ getToolLabel: (name: string) => name }), })) -vi.mock('@/api', () => ({ http: { get: vi.fn(), post: vi.fn() } })) +vi.mock('@/api', () => ({ http: { get: vi.fn(), post: vi.fn() }, fetchAuthenticatedBlob: vi.fn() })) vi.mock('@/utils/clipboard', () => ({ copyToClipboard: vi.fn() })) vi.mock('@/utils/generatedFileLinks', () => ({ buildGeneratedFileNameMap: vi.fn(() => new Map()), @@ -58,6 +60,8 @@ vi.mock('../UserMessageContent.vue', () => ({ })) import MessageBubble from '../MessageBubble.vue' +import { http, fetchAuthenticatedBlob } from '@/api' +import { mcToast } from '@/composables/useMcToast' const apps: Array> = [] @@ -72,12 +76,16 @@ function mountMessage(message: Message, locale = 'zh-CN') { messages: { 'zh-CN': { chat: { + incompleteTitle: zhCN.chat.incompleteTitle, + incompleteDescription: zhCN.chat.incompleteDescription, stopped: '已被用户手动中止', interrupted: '已中断并继续处理下一条消息', }, }, en: { chat: { + incompleteTitle: enUS.chat.incompleteTitle, + incompleteDescription: enUS.chat.incompleteDescription, stopped: 'Stopped manually by user', interrupted: 'Interrupted and continued with the next message', }, @@ -92,6 +100,9 @@ function mountMessage(message: Message, locale = 'zh-CN') { afterEach(() => { apps.splice(0).forEach(app => app.unmount()) document.body.innerHTML = '' + vi.restoreAllMocks() + vi.unstubAllGlobals() + vi.clearAllMocks() }) describe('MessageBubble stop indicator', () => { @@ -130,3 +141,82 @@ describe('MessageBubble stop indicator', () => { expect(host.querySelector('.stopped-indicator--interrupted')).not.toBeNull() }) }) + + +describe('MessageBubble TTS (#646)', () => { + function clickReadAloud() { + const host = mountMessage({ id: 'tts', conversationId: 'conv', role: 'assistant', + content: 'Hello', contentParts: [], status: 'completed' } as Message) + const button = host.querySelector('[title="chat.ttsPlay"]')! + expect(button).not.toBeNull() + button.click() + return button + } + + it('plays the unwrapped synthesis response and releases audio on stop', async () => { + vi.mocked(http.post).mockResolvedValue({ success: true, audioUrl: '/api/v1/chat/files/conv/tts.mp3' } as never) + vi.mocked(fetchAuthenticatedBlob).mockResolvedValue(new Blob(['audio'])) + const play = vi.fn().mockResolvedValue(undefined) + const pause = vi.fn() + vi.stubGlobal('Audio', class { play = play; pause = pause }) + vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:tts') + const revoke = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}) + const button = clickReadAloud() + await vi.waitFor(() => expect(play).toHaveBeenCalledOnce()) + expect(fetchAuthenticatedBlob).toHaveBeenCalledWith('/api/v1/chat/files/conv/tts.mp3') + button.click() + expect(pause).toHaveBeenCalledOnce() + expect(revoke).toHaveBeenCalledWith('blob:tts') + }) + + it('shows backend synthesis errors instead of silently doing nothing', async () => { + vi.mocked(http.post).mockResolvedValue({ success: false, error: 'DashScope: invalid voice' } as never) + clickReadAloud() + await vi.waitFor(() => expect(mcToast.error).toHaveBeenCalledWith('DashScope: invalid voice')) + }) + + it('shows audio download failures and resets the loading state', async () => { + vi.mocked(http.post).mockResolvedValue({ success: true, audioUrl: '/audio' } as never) + vi.mocked(fetchAuthenticatedBlob).mockRejectedValue(new Error('Fetch failed: 403')) + const button = clickReadAloud() + await vi.waitFor(() => expect(mcToast.error).toHaveBeenCalledWith('Fetch failed: 403')) + expect(button.disabled).toBe(false) + }) + it('releases the audio URL when playback is rejected', async () => { + vi.mocked(http.post).mockResolvedValue({ success: true, audioUrl: '/audio' } as never) + vi.mocked(fetchAuthenticatedBlob).mockResolvedValue(new Blob(['audio'])) + const pause = vi.fn() + vi.stubGlobal('Audio', class { pause = pause; play = vi.fn().mockRejectedValue(new Error('Playback blocked')) }) + vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:rejected') + const revoke = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}) + clickReadAloud() + await vi.waitFor(() => expect(mcToast.error).toHaveBeenCalledWith('Playback blocked')) + expect(pause).toHaveBeenCalledOnce() + expect(revoke).toHaveBeenCalledWith('blob:rejected') + }) + + it('does not start playback when the component unmounts during synthesis', async () => { + let finish!: (value: any) => void + vi.mocked(http.post).mockReturnValue(new Promise(resolve => { finish = resolve })) + clickReadAloud() + apps.pop()!.unmount() + finish({ success: true, audioUrl: '/audio' }) + await Promise.resolve() + await Promise.resolve() + expect(fetchAuthenticatedBlob).not.toHaveBeenCalled() + }) + +}) + + +describe('MessageBubble incomplete answer (#660)', () => { + it.each(['zh-CN', 'en'])('does not diagnose token-budget exhaustion as repetition (%s)', locale => { + const host = mountMessage({ id: 'budget', conversationId: 'conv', role: 'assistant', + content: '模型在输出最终答案前已耗尽输出 token 预算', contentParts: [], + status: 'completed', metadata: { finishReason: 'incomplete' } } as Message, locale) + const title = host.querySelector('.incomplete-card__title')?.textContent || '' + expect(title).not.toMatch(/重复|repeated|repetition/i) + expect(host.textContent).toContain('token') + expect(host.querySelector('.incomplete-card__retry')).not.toBeNull() + }) +}) diff --git a/mateclaw-ui/src/components/chat/__tests__/MessageBubble.structuredHistory.test.ts b/mateclaw-ui/src/components/chat/__tests__/MessageBubble.structuredHistory.test.ts new file mode 100644 index 00000000..51c0af97 --- /dev/null +++ b/mateclaw-ui/src/components/chat/__tests__/MessageBubble.structuredHistory.test.ts @@ -0,0 +1,204 @@ +import { createApp, defineComponent, h, reactive, nextTick } from 'vue' +import { createI18n } from 'vue-i18n' +import { createPinia } from 'pinia' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { Message } from '@/types' + +vi.mock('/logo/mateclaw_logo_s.png', () => ({ default: '/logo/mateclaw_logo_s.png' })) +vi.mock('@/composables/useStreamingMarkdown', () => ({ + useStreamingMarkdown: (content: unknown) => ({ + renderedContent: content, + copyCode: vi.fn(), + }), +})) +vi.mock('@/composables/useAuthenticatedAttachment', () => ({ + useAuthenticatedAttachment: () => ({ + blobUrls: {}, + loadAllImages: vi.fn(), + loadAllVideos: vi.fn(), + loadAllAudios: vi.fn(), + loadAllModels: vi.fn(), + downloadFile: vi.fn(), + openImage: vi.fn(), + getDisplayUrl: vi.fn((url: string) => url), + revokeAll: vi.fn(), + }), +})) +vi.mock('@/composables/useMcToast', () => ({ + mcToast: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() }, +})) +vi.mock('@/composables/useToolLabel', () => ({ + useToolLabel: () => ({ getToolLabel: (name: string) => name }), +})) +vi.mock('@/api', () => ({ http: { get: vi.fn(), post: vi.fn() }, fetchAuthenticatedBlob: vi.fn() })) +vi.mock('@/utils/clipboard', () => ({ copyToClipboard: vi.fn() })) +vi.mock('@/utils/generatedFileLinks', () => ({ + buildGeneratedFileNameMap: vi.fn(() => new Map()), + linkifyGeneratedFileUrls: vi.fn((content: string) => content), +})) +vi.mock('@/utils/lazyModelViewer', () => ({ ensureModelViewer: vi.fn() })) +vi.mock('../preview/previewKind', () => ({ previewKindOf: vi.fn(() => null) })) +vi.mock('../preview/previewBus', () => ({ openFilePreview: vi.fn() })) +vi.mock('@/components/goal/GoalAvatarRing.vue', () => ({ + default: defineComponent({ setup: (_, { slots }) => () => h('div', slots.default?.()) }), +})) +vi.mock('../ThinkingSegment.vue', () => ({ + default: defineComponent({ props: ['segment'], setup: props => () => h('div', props.segment.thinkingText) }), +})) +vi.mock('../ContentSegment.vue', () => ({ + default: defineComponent({ props: ['segment'], setup: props => () => h('div', props.segment.text) }), +})) +vi.mock('../PlanStepsPanel.vue', () => ({ default: defineComponent({ setup: () => () => null }) })) +vi.mock('../BrowserTimeline.vue', () => ({ default: defineComponent({ setup: () => () => null }) })) +vi.mock('../TypingCursor.vue', () => ({ default: defineComponent({ setup: () => () => null }) })) +vi.mock('../UserMessageContent.vue', () => ({ + default: defineComponent({ props: ['content'], setup: props => () => h('div', props.content) }), +})) + +import MessageBubble from '../MessageBubble.vue' +import { http, fetchAuthenticatedBlob } from '@/api' +import { mcToast } from '@/composables/useMcToast' + +const apps: Array> = [] + +function mountMessage(message: Message, locale = 'zh-CN') { + const host = document.createElement('div') + document.body.appendChild(host) + const app = createApp(MessageBubble, { message }) + app.use(createPinia()) + for (const name of ['el-icon', 'el-popover', 'model-viewer']) app.component(name, { template: '' }) + app.use(createI18n({ + legacy: false, + missingWarn: false, + fallbackWarn: false, + locale, + messages: { + 'zh-CN': { + chat: { + stopped: '已被用户手动中止', + interrupted: '已中断并继续处理下一条消息', + }, + }, + en: { + chat: { + stopped: 'Stopped manually by user', + interrupted: 'Interrupted and continued with the next message', + }, + }, + }, + })) + app.mount(host) + apps.push(app) + return host +} + +afterEach(() => { + apps.splice(0).forEach(app => app.unmount()) + document.body.innerHTML = '' + vi.restoreAllMocks() + vi.unstubAllGlobals() + vi.clearAllMocks() +}) + +describe('structured tool history', () => { + it.each(['segments', 'toolCalls'])('renders saved %s data as the same visible table', (source) => { + const structuredContent = { mateclawUi: { version: 1, blocks: [{ type: 'table', data: { columns: [{ key: 'a', label: 'A' }], rows: [{ a: 'Saved result' }] } }] } } + const metadata = source === 'segments' + ? { segments: [{ id: 'tool', type: 'tool_call', status: 'completed', toolName: 'search', structuredContent }] } + : { toolCalls: [{ name: 'search', status: 'completed', success: true, structuredContent }] } + const host = mountMessage({ id: 'saved', conversationId: 'conv', role: 'assistant', content: '', contentParts: [], status: 'completed', metadata } as Message) + expect(host.querySelector('td')?.textContent).toBe('Saved result') + expect(host.querySelector('.seg-tool__body')).toBeNull() + expect(host.querySelector('.seg-tool .tool-result-view')).toBeNull() + expect(host.querySelector('.answer-tool-results td')?.textContent).toBe('Saved result') + }) +}) + +const richResult = { mateclawUi: { version: 1, blocks: [{ type: 'table', data: { + columns: [{ key: 'a', label: 'A' }], rows: [{ a: 'Quarterly sales' }], +} }] } } +const successfulTool = (id: string) => ({ id, toolCallId: id, type: 'tool_call', status: 'completed', + toolName: 'sales', toolSuccess: true, structuredContent: richResult }) +const answerMessage = (status = 'completed', metadata: unknown = {}) => ({ + id: 'answer', conversationId: 'conv', role: 'assistant', content: 'Sales grew 50%', + contentParts: [], status, metadata, +}) as Message + +const weatherResult = { + success: true, + result: { city: '广州', weather: '阵雨', temperature: 28, humidity: 85 }, + error: null, +} + +it('does not append ordinary MCP JSON when a live turn completes, while retaining tool details', async () => { + const result = JSON.stringify(weatherResult) + const tool = { ...successfulTool('weather'), toolName: 'get_weather', + toolResult: result, structuredContent: weatherResult } + const message = reactive(answerMessage('generating', { segments: [tool, + { id: 'text', type: 'content', status: 'running', text: '广州有阵雨,28°C。' }], + })) + const host = mountMessage(message) + message.status = 'completed' + await nextTick() + expect(host.querySelector('.segments-view')?.textContent).toContain('广州有阵雨,28°C。') + expect(host.querySelector('.answer-tool-results')).toBeNull() + host.querySelector('.seg-tool__header')!.click() + await nextTick() + expect(host.querySelector('.seg-tool pre')?.textContent).toBe(result) +}) + +it.each(['segments', 'toolCalls'])('does not append ordinary MCP JSON from saved %s alongside rich output', (source) => { + const metadata = source === 'segments' + ? { segments: [{ ...successfulTool('weather'), structuredContent: weatherResult }, successfulTool('rich')] } + : { toolCalls: [ + { name: 'get_weather', toolCallId: 'weather', status: 'completed', success: true, structuredContent: weatherResult }, + { name: 'sales', toolCallId: 'rich', status: 'completed', success: true, structuredContent: richResult }, + ] } + const host = mountMessage(answerMessage('completed', JSON.stringify(metadata))) + expect(host.querySelectorAll('.answer-tool-results .tool-result-view')).toHaveLength(1) + expect(host.querySelector('.answer-tool-results td')?.textContent).toBe('Quarterly sales') + expect(host.querySelector('.answer-tool-results pre')).toBeNull() +}) + +it('shows rich results once after the final content when a live turn completes', async () => { + const message = reactive(answerMessage('generating', { segments: [successfulTool('a'), + { id: 'text', type: 'content', status: 'running', text: 'Sales grew 50%' }], + toolCalls: [{ name: 'sales', toolCallId: 'a', status: 'completed', structuredContent: richResult }], + })) + const host = mountMessage(message) + expect(host.querySelector('.tool-result-view')).toBeNull() + message.status = 'completed' + await nextTick() + expect(host.querySelectorAll('.tool-result-view')).toHaveLength(1) + const results = host.querySelector('.answer-tool-results')! + const timeline = host.querySelector('.segments-view')! + expect(timeline.textContent).toContain('Sales grew 50%') + expect(timeline.compareDocumentPosition(results) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() + expect(host.querySelector('.seg-tool .tool-result-view')).toBeNull() +}) + +it.each(['completed', 'stopped', 'interrupted', 'failed'])('retains successful output for %s turns', (status) => { + const metadata = { segments: [successfulTool('a'), + { ...successfulTool('b'), status: 'error', toolSuccess: false }, + { ...successfulTool('c'), status: 'running' }], + } + const host = mountMessage(answerMessage(status, JSON.stringify(metadata))) + expect(host.querySelectorAll('.answer-tool-results table')).toHaveLength(1) +}) + +it('deduplicates repeated IDs but preserves separate calls of the same tool', () => { + const host = mountMessage(answerMessage('completed', { segments: [ + successfulTool('a'), successfulTool('a'), successfulTool('b'), + ] })) + expect(host.querySelectorAll('.answer-tool-results table')).toHaveLength(2) +}) + +it('does not promote unfinished or failed legacy toolCalls to successful results', () => { + const host = mountMessage(answerMessage('stopped', { toolCalls: [ + { name: 'running', status: 'running', structuredContent: richResult }, + { name: 'approval', status: 'awaiting_approval', structuredContent: richResult }, + { name: 'failed', status: 'completed', success: false, structuredContent: richResult }, + { name: 'done', status: 'completed', success: true, structuredContent: richResult }, + ] })) + expect(host.querySelectorAll('.answer-tool-results table')).toHaveLength(1) +}) diff --git a/mateclaw-ui/src/components/chat/__tests__/ToolCallSegment.structured.test.ts b/mateclaw-ui/src/components/chat/__tests__/ToolCallSegment.structured.test.ts new file mode 100644 index 00000000..7766a1bb --- /dev/null +++ b/mateclaw-ui/src/components/chat/__tests__/ToolCallSegment.structured.test.ts @@ -0,0 +1,55 @@ +import { createApp, nextTick, defineComponent } from 'vue' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { MessageSegment } from '@/types' +vi.mock('@/composables/useToolLabel', () => ({ useToolLabel: () => ({ getToolLabel: (name: string) => name }) })) +vi.mock('../ExecutionDetailDialog.vue', () => ({ default: { template: '
' } })) +import ToolCallSegment from '../ToolCallSegment.vue' +import { registerToolResultRenderer } from '../tool-results/registry' +const apps: ReturnType[] = [] +afterEach(() => { apps.splice(0).forEach(app => app.unmount()); document.body.innerHTML = '' }) +function mount(segment: MessageSegment) { + const host = document.createElement('div') + document.body.append(host) + const app = createApp(ToolCallSegment, { segment }) + app.config.globalProperties.$t = (key: string) => key + app.component('el-icon', { template: '' }) + app.mount(host) + apps.push(app) + return host +} +const table = { mateclawUi: { version: 1, blocks: [{ type: 'table', title: '', data: { columns: [{ key: 'value', label: 'Value' }], rows: [{ value: '' }] } }] } } +const segment = (status: MessageSegment['status'] = 'completed'): MessageSegment => ({ id: 'a', type: 'tool_call', toolName: 'search', status, toolResult: 'plain result', structuredContent: table }) +describe('structured tool cards', () => { + it('shows a table immediately while raw details stay collapsed, and escapes text', async () => { + const host = mount(segment()) + expect(host.querySelector('table')).not.toBeNull() + expect(host.querySelector('.seg-tool__body')).toBeNull() + expect(host.querySelector('td')?.textContent).toBe('') + expect(host.querySelector('script, img')).toBeNull() + ;(host.querySelector('.seg-tool__header') as HTMLElement).click() + await nextTick() + expect(host.querySelector('table')).not.toBeNull() + expect(host.querySelector('.seg-tool__body')?.textContent).toContain('plain result') + }) + it('isolates a failing trusted renderer and shows escaped JSON', async () => { + registerToolResultRenderer('broken', { component: defineComponent({ setup() { throw new Error('Broken renderer') } }), validate: () => true }) + const host = mount({ ...segment(), structuredContent: { mateclawUi: { version: 1, blocks: [{ type: 'broken', data: '' }] } } }) + await nextTick() + expect(host.querySelector('pre')?.textContent).toContain('') + expect(host.querySelector('img')).toBeNull() + }) + it('does not display rich results for running or failed calls', () => { + for (const value of [segment('running'), segment('error'), { ...segment(), toolSuccess: false }]) expect(mount(value).querySelector('table')).toBeNull() + }) + it('escapes fallback JSON and renders safe product media without dangerous navigation', () => { + const value = segment() + value.structuredContent = { mateclawUi: { version: 1, blocks: [{ type: 'unknown', data: '' }, { type: 'product-cards', data: [{ name: '', url: 'javascript:alert(1)', imageUrl: 'data:image/svg+xml,bad', price: 12 }, { name: 'Good', url: 'https://shop.test/p', imageUrl: 'https://shop.test/p.png' }] }] } } + const host = mount(value) + expect(host.querySelector('pre')?.textContent).toContain('') + expect(host.querySelector('script')).toBeNull() + expect(host.querySelectorAll('img')).toHaveLength(1) + expect(host.querySelectorAll('a')).toHaveLength(1) + expect(host.querySelector('a')?.getAttribute('rel')).toContain('noopener') + expect(host.querySelector('a')?.getAttribute('href')).toBe('https://shop.test/p') + }) +}) diff --git a/mateclaw-ui/src/components/chat/__tests__/ToolResultChart.test.ts b/mateclaw-ui/src/components/chat/__tests__/ToolResultChart.test.ts new file mode 100644 index 00000000..a6a4708d --- /dev/null +++ b/mateclaw-ui/src/components/chat/__tests__/ToolResultChart.test.ts @@ -0,0 +1,49 @@ +import { createApp, nextTick } from 'vue' +import { createPinia } from 'pinia' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { useThemeStore } from '@/stores/useThemeStore' +const mock = vi.hoisted(() => ({ init: vi.fn(), setOption: vi.fn(), dispose: vi.fn(), resize: vi.fn(), disconnect: vi.fn() })) +vi.mock('echarts', () => ({ init: mock.init })) +import ToolResultChart from '../tool-results/ToolResultChart.vue' +let app: ReturnType | undefined +let host: HTMLElement +function mount() { + vi.stubGlobal('localStorage', { getItem: () => null, setItem: () => {} }) + vi.stubGlobal('ResizeObserver', class { observe() {} disconnect = mock.disconnect }) + mock.init.mockReturnValue({ setOption: mock.setOption, dispose: mock.dispose, resize: mock.resize }) + host = document.createElement('div') + document.body.append(host) + app = createApp(ToolResultChart, { data: { tooltip: { formatter: '' }, series: [{ type: 'bar', data: [1] }] } }) + const pinia = createPinia() + app.use(pinia) + app.mount(host) + return useThemeStore(pinia) +} +afterEach(() => { app?.unmount(); app = undefined; document.body.innerHTML = ''; vi.clearAllMocks(); vi.unstubAllGlobals() }) +describe('tool chart lifecycle', () => { + it('owns one canvas instance, sanitizes options, and disposes on unmount', async () => { + mount() + await vi.waitFor(() => expect(mock.setOption).toHaveBeenCalledOnce()) + expect(host.querySelector('.echarts-block')).toBeNull() + expect(mock.init).toHaveBeenCalledOnce() + expect(mock.setOption.mock.calls[0][0].tooltip).toEqual({ renderMode: 'richText', confine: true }) + app!.unmount(); app = undefined + expect(mock.dispose).toHaveBeenCalledOnce() + expect(mock.disconnect).toHaveBeenCalledOnce() + }) + it('does not mount after unmount while lazy loading', async () => { + mount() + app!.unmount(); app = undefined + await nextTick() + await new Promise(resolve => setTimeout(resolve, 0)) + expect(mock.init).not.toHaveBeenCalled() + }) + it('shows readable JSON when chart option rendering fails', async () => { + mock.setOption.mockImplementationOnce(() => { throw new Error('Unsupported chart') }) + mount() + await vi.waitFor(() => expect(host.querySelector('pre')).not.toBeNull()) + expect(host.querySelector('pre')?.textContent).toContain('series') + expect(host.querySelector('img')).toBeNull() + expect(mock.dispose).toHaveBeenCalledOnce() + }) +}) diff --git a/mateclaw-ui/src/components/chat/__tests__/toolResultRegistry.test.ts b/mateclaw-ui/src/components/chat/__tests__/toolResultRegistry.test.ts new file mode 100644 index 00000000..02f4af1b --- /dev/null +++ b/mateclaw-ui/src/components/chat/__tests__/toolResultRegistry.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest' +import { defineComponent } from 'vue' +import { resolveToolResult, registerToolResultRenderer, safeHttpUrl, safeChartOption } from '../tool-results/registry' +const payload = (type: string, data: unknown) => ({ mateclawUi: { version: 1, blocks: [{ type, data }] } }) +describe('tool result contract', () => { + it('recognizes table data and trusted registered components', () => { + expect(resolveToolResult(payload('table', { columns: [{ key: 'name', label: 'Name' }], rows: [{ name: 'A' }] }))[0].component).toBeTruthy() + const component = defineComponent({ template: '' }) + registerToolResultRenderer('custom-test', { component, validate: data => data === 42 }) + expect(resolveToolResult(payload('custom-test', 42))[0].component).toBe(component) + expect(resolveToolResult(payload('custom-test', 41))[0].fallback).toContain('41') + }) + it('falls back for unknown versions, types, malformed and oversized payloads', () => { + for (const input of [{ mateclawUi: { version: 2, blocks: [] } }, payload('unknown', ''), payload('table', {}), payload('table', { columns: [], rows: Array(201).fill({}) }), { mateclawUi: { version: 1, blocks: Array(9).fill({ type: 'unknown' }) } }, payload('unknown', '汉'.repeat(40000))]) { + const blocks = resolveToolResult(input) + expect(blocks[0].fallback).toBeTruthy() + expect(blocks.every(b => !b.component)).toBe(true) + } + }) + it('allows only absolute http(s) product URLs', () => { + for (const url of ['javascript:alert(1)', 'data:image/svg+xml,x', '//evil.test', 'https:\n//evil.test', 'https://user:pass@evil.test']) expect(safeHttpUrl(url)).toBeUndefined() + expect(safeHttpUrl('https://shop.test/a')).toBe('https://shop.test/a') + }) + it('removes chart HTML, links, graphics, image symbols and formatter features recursively', () => { + const option = safeChartOption({ title: { text: '', link: 'javascript:alert(1)' }, tooltip: { renderMode: 'html', formatter: '', extraCssText: 'bad' }, graphic: { type: 'image' }, visualMap: { inRange: { symbol: ['image://https://bad.test/x'] } }, series: [{ type: 'bar', symbol: 'image://https://bad.test/x', data: [1], tooltip: { formatter: ' + + diff --git a/mateclaw-ui/src/components/chat/tool-results/ToolResultChart.vue b/mateclaw-ui/src/components/chat/tool-results/ToolResultChart.vue new file mode 100644 index 00000000..9f391c35 --- /dev/null +++ b/mateclaw-ui/src/components/chat/tool-results/ToolResultChart.vue @@ -0,0 +1,40 @@ + + + diff --git a/mateclaw-ui/src/components/chat/tool-results/ToolResultProducts.vue b/mateclaw-ui/src/components/chat/tool-results/ToolResultProducts.vue new file mode 100644 index 00000000..e54d84d6 --- /dev/null +++ b/mateclaw-ui/src/components/chat/tool-results/ToolResultProducts.vue @@ -0,0 +1,12 @@ + + diff --git a/mateclaw-ui/src/components/chat/tool-results/ToolResultTable.vue b/mateclaw-ui/src/components/chat/tool-results/ToolResultTable.vue new file mode 100644 index 00000000..a40300de --- /dev/null +++ b/mateclaw-ui/src/components/chat/tool-results/ToolResultTable.vue @@ -0,0 +1,16 @@ + + + diff --git a/mateclaw-ui/src/components/chat/tool-results/ToolResultView.vue b/mateclaw-ui/src/components/chat/tool-results/ToolResultView.vue new file mode 100644 index 00000000..a4e4fd7a --- /dev/null +++ b/mateclaw-ui/src/components/chat/tool-results/ToolResultView.vue @@ -0,0 +1,20 @@ + + + diff --git a/mateclaw-ui/src/components/chat/tool-results/registry.ts b/mateclaw-ui/src/components/chat/tool-results/registry.ts new file mode 100644 index 00000000..37a52339 --- /dev/null +++ b/mateclaw-ui/src/components/chat/tool-results/registry.ts @@ -0,0 +1,61 @@ +export { safeHttpUrl, safeChartOption } from './safety' +import { markRaw, type Component } from 'vue' +import ToolResultTable from './ToolResultTable.vue' +import ToolResultProducts from './ToolResultProducts.vue' +import ToolResultChart from './ToolResultChart.vue' + +export const MAX_TOOL_UI_BYTES = 100 * 1024 +export const MAX_TABLE_ROWS = 200 +export const MAX_PRODUCTS = 24 +interface Renderer { component: Component; validate: (data: unknown) => boolean } +export interface ResolvedToolBlock { title?: string; component?: Component; data?: unknown; fallback?: string } +const registry = new Map() +const record = (value: unknown): value is Record => !!value && typeof value === 'object' && !Array.isArray(value) + +/** Only explicit UI payloads belong below the assistant answer; ordinary MCP data stays in tool details. */ +export function hasToolResultUi(input: unknown): boolean { + const ui = record(input) ? input.mateclawUi : undefined + return record(ui) && ui.version === 1 && Array.isArray(ui.blocks) && ui.blocks.length <= 8 +} + +/** Registration is limited to trusted application code; payloads only name a renderer. */ +export function registerToolResultRenderer(type: string, renderer: Renderer): void { + registry.set(type, { ...renderer, component: markRaw(renderer.component) }) +} + +export function resolveToolResult(input: unknown): ResolvedToolBlock[] { + let json: string + try { json = JSON.stringify(input, null, 2) ?? String(input) } catch { return [{ fallback: 'Invalid structured tool result' }] } + const bounded = (text: string) => text.length > MAX_TOOL_UI_BYTES ? text.slice(0, MAX_TOOL_UI_BYTES) + '\n… [truncated]' : text + const fallback = () => [{ fallback: bounded(json) }] + if (new TextEncoder().encode(JSON.stringify(input)).length > MAX_TOOL_UI_BYTES) return fallback() + const ui = record(input) ? input.mateclawUi : undefined + if (!hasToolResultUi(input)) return fallback() + return ui.blocks.map((block: unknown) => { + if (!record(block)) return { fallback: bounded(JSON.stringify(block, null, 2)) } + const title = typeof block.title === 'string' ? block.title : undefined + const renderer = registry.get(block.type) + try { + if (renderer && renderer.validate(block.data)) return { title, component: renderer.component, data: block.data, fallback: bounded(JSON.stringify(block, null, 2)) } + } catch { /* A faulty trusted validator must not interrupt the conversation. */ } + return { title, fallback: bounded(JSON.stringify(block, null, 2)) } + }) +} + +registerToolResultRenderer('table', { + component: ToolResultTable, + validate: data => record(data) && Array.isArray(data.columns) && data.columns.length > 0 && data.columns.length <= 32 + && data.columns.every((c: unknown) => record(c) && typeof c.key === 'string' && typeof c.label === 'string') + && Array.isArray(data.rows) && data.rows.length <= MAX_TABLE_ROWS && data.rows.every(record), +}) +registerToolResultRenderer('product-cards', { + component: ToolResultProducts, + validate: data => Array.isArray(data) && data.length <= MAX_PRODUCTS && data.every(item => record(item) + && typeof item.name === 'string' + && ['url', 'imageUrl', 'platformLabel', 'shopName', 'purchaseAdvice'].every(k => item[k] == null || typeof item[k] === 'string') + && ['price', 'originalPrice', 'lowestPrice'].every(k => item[k] == null || typeof item[k] === 'string' || typeof item[k] === 'number')), +}) +registerToolResultRenderer('echarts', { + component: ToolResultChart, + validate: data => record(data) && (record(data.series) || (Array.isArray(data.series) && data.series.length > 0 && data.series.every(record))), +}) diff --git a/mateclaw-ui/src/components/chat/tool-results/safety.ts b/mateclaw-ui/src/components/chat/tool-results/safety.ts new file mode 100644 index 00000000..80a1ca27 --- /dev/null +++ b/mateclaw-ui/src/components/chat/tool-results/safety.ts @@ -0,0 +1,34 @@ +const record = (value: unknown): value is Record => !!value && typeof value === 'object' && !Array.isArray(value) + +export function safeHttpUrl(value: unknown): string | undefined { + if (typeof value !== 'string' || !/^https?:\/\//i.test(value) || /[\s\u0000-\u001f\u007f]/.test(value)) return + try { + const url = new URL(value) + if (!url.username && !url.password && url.hostname) return value + } catch { /* Invalid URLs are rendered without navigation or media. */ } +} + +const CHART_KEYS = new Set(['title', 'legend', 'xAxis', 'yAxis', 'series', 'grid', 'color', 'dataset', 'radar', 'polar', 'angleAxis', 'radiusAxis', 'visualMap', 'tooltip']) +const UNSAFE_CHART_KEYS = new Set(['__proto__', 'constructor', 'prototype', 'formatter', 'valueFormatter', 'renderItem', 'link', 'sublink', 'target', 'sublinkTarget', 'image', 'graphic', 'rich', 'extraCssText', 'className', 'appendTo', 'appendToBody', 'renderMode', 'backgroundColor', 'borderColor']) +/** Use canvas text tooltips and remove all executable, HTML, navigation and image hooks. */ +export function safeChartOption(input: Record): Record { + const clean = (value: any): any => { + if (Array.isArray(value)) return value.map(clean) + if (record(value)) { + const result: Record = {} + for (const [key, child] of Object.entries(value)) { + if (UNSAFE_CHART_KEYS.has(key) || typeof child === 'function') continue + if (key === 'tooltip') { result[key] = { renderMode: 'richText', confine: true }; continue } + if (typeof child === 'string' && (child.includes('image://') || child.trimStart().startsWith('function'))) continue + result[key] = clean(child) + } + return result + } + if (typeof value === 'string' && /image:\/\//i.test(value)) return '' + return value + } + const option = clean(Object.fromEntries(Object.entries(input).filter(([key]) => CHART_KEYS.has(key)))) + option.tooltip = { renderMode: 'richText', confine: true } + return option +} + diff --git a/mateclaw-ui/src/components/team-run/TeamRunRuntime.vue b/mateclaw-ui/src/components/team-run/TeamRunRuntime.vue index b6ae595f..0f936010 100644 --- a/mateclaw-ui/src/components/team-run/TeamRunRuntime.vue +++ b/mateclaw-ui/src/components/team-run/TeamRunRuntime.vue @@ -1,3 +1,3 @@ - + diff --git a/mateclaw-ui/src/composables/chat/__tests__/toolStructuredContent.test.ts b/mateclaw-ui/src/composables/chat/__tests__/toolStructuredContent.test.ts new file mode 100644 index 00000000..4af820d8 --- /dev/null +++ b/mateclaw-ui/src/composables/chat/__tests__/toolStructuredContent.test.ts @@ -0,0 +1,50 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createPinia, setActivePinia } from 'pinia' + +const streamMock = vi.hoisted(() => { + const handlers = new Map void>>() + return { + handlers, + connect: vi.fn().mockResolvedValue(undefined), + disconnect: vi.fn(), + resetDedup: vi.fn(), + on: vi.fn((event: string, handler: (data?: any) => void) => { + const listeners = handlers.get(event) ?? new Set() + listeners.add(handler) + handlers.set(event, listeners) + return () => listeners.delete(handler) + }), + } +}) + +vi.mock('../useStream', () => ({ + useStream: () => streamMock, +})) + +import { useChat } from '../useChat' + +describe('tool structured completion', () => { + beforeEach(() => { + streamMock.handlers.clear() + setActivePinia(createPinia()) + vi.stubGlobal('localStorage', { getItem: () => null, setItem: () => {}, removeItem: () => {} }) + }) + afterEach(() => vi.unstubAllGlobals()) + const emit = (name: string, data: any) => streamMock.handlers.get(name)?.forEach(h => h(data)) + it('pairs explicit IDs exactly, including duplicate completion, and persists structured content', async () => { + const chat = useChat({ baseUrl: '' }) + await chat.sendMessage('tools', { conversationId: 'conv', agentId: 'agent' }) + emit('tool_call_started', { toolName: 'search', toolCallId: 'a' }) + emit('tool_call_started', { toolName: 'search', toolCallId: 'b' }) + const structuredContent = { mateclawUi: { version: 1, blocks: [] } } + emit('tool_call_completed', { toolName: 'search', toolCallId: 'a', success: true, result: 'ok', structuredContent }) + emit('tool_call_completed', { toolName: 'search', toolCallId: 'a', success: true, result: 'ok', structuredContent }) + emit('tool_call_completed', { toolName: 'search', toolCallId: 'unknown', success: true, result: 'bad' }) + const meta = chat.messages.value.at(-1)?.metadata as any + expect(meta.toolCalls[0].structuredContent).toEqual(structuredContent) + expect(meta.segments[0].structuredContent).toEqual(structuredContent) + expect(meta.toolCalls[1].status).toBe('running') + expect(meta.segments[1].status).toBe('running') + }) +}) diff --git a/mateclaw-ui/src/composables/chat/useChat.ts b/mateclaw-ui/src/composables/chat/useChat.ts index 251d9d63..de4218d4 100644 --- a/mateclaw-ui/src/composables/chat/useChat.ts +++ b/mateclaw-ui/src/composables/chat/useChat.ts @@ -1000,15 +1000,16 @@ export function useChat(options: UseChatOptions): UseChatReturn { // Match by toolCallId when available, fall back to "first running" for legacy events. let target = -1 if (data.toolCallId) { - target = toolCalls.findIndex((tc: any) => tc.toolCallId === data.toolCallId && tc.status === 'running') + target = toolCalls.findIndex((tc: any) => tc.toolCallId === data.toolCallId) } - if (target < 0) { + if (!data.toolCallId) { target = toolCalls.findIndex((tc: any) => tc.status === 'running' && tc.name === data.toolName) } if (target >= 0) { toolCalls[target] = { ...toolCalls[target], result: data.result, + structuredContent: data.structuredContent, success: data.success, status: 'completed' } @@ -1030,20 +1031,21 @@ export function useChat(options: UseChatOptions): UseChatReturn { metadata: { ...metadata, toolCalls, runningToolName: undefined, generatedFiles } } as any) } - // Segments: prefer toolCallId match, fall back to first-running by toolName. + // Explicit IDs also match replayed completions; only ID-less events use the running-name fallback. const segs = currentSegments.value let toolSeg: MessageSegment | undefined if (data.toolCallId) { toolSeg = segs.find((s: MessageSegment) => - s.type === 'tool_call' && s.status === 'running' && s.toolCallId === data.toolCallId) + s.type === 'tool_call' && s.toolCallId === data.toolCallId) } - if (!toolSeg) { + if (!data.toolCallId) { toolSeg = segs.find((s: MessageSegment) => s.type === 'tool_call' && s.status === 'running' && s.toolName === data.toolName) } if (toolSeg) { toolSeg.status = data.success !== false ? 'completed' : 'error' toolSeg.toolResult = data.result + toolSeg.structuredContent = data.structuredContent toolSeg.toolSuccess = data.success } flushSegmentsToMessage() diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 8478ca45..f79cca48 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -497,6 +497,7 @@ export default { }, }, }, + ttsFailed: 'Speech synthesis or playback failed', ttsPlay: 'Read Aloud', ttsStop: 'Stop Reading', conversations: 'Conversations', @@ -698,8 +699,8 @@ export default { pendingReply: 'Preparing a reply…', expand: 'Expand', // INCOMPLETE truncation card (finishReason=incomplete) - incompleteTitle: 'Answer auto-truncated after repeated output was detected', - incompleteDescription: 'After the visible text above, the model started repeating itself (or stalled in thinking with no output) and was cut short to avoid wasted tokens. Click below to regenerate the full answer, or refine your prompt to focus on the missing parts.', + incompleteTitle: 'Answer is incomplete', + incompleteDescription: 'This answer did not finish. Check the message for the reason: repeated output, a reasoning safety limit, or an exhausted output token budget. Regenerate, adjust thinking and output budgets, or split the task to continue.', incompleteRetry: 'Regenerate', // EVIDENCE_INSUFFICIENT info card (finishReason=evidence_insufficient) evidenceTitle: 'Run completed — some source references could not be verified', @@ -1549,6 +1550,16 @@ export default { model3dDesc: 'Configure AI 3D model generation with Tencent Hunyuan 3D (HY-3D-3.1 / HY-3D-3.0 / HY-3D-Express)', model3dProviderOptions: { auto: 'Auto Select', hunyuan: 'Tencent Hunyuan 3D' }, model3dProviderTags: { requiresKey: 'Requires SecretId:SecretKey' }, + ttsVoiceLabel: 'Voice', + ttsVoiceHint: 'Voices depend on the provider. Select again after switching providers. Preview uses unsaved settings without provider fallback.', + ttsAutoVoiceProvider: 'Current automatic provider: {provider}', + ttsVoiceDefault: 'Use default voice', + ttsVoiceUnavailable: 'Configure this provider’s API key before previewing.', + ttsPreview: 'Preview', + ttsPreviewLoading: 'Generating…', + ttsPreviewStop: 'Stop preview', + ttsPreviewText: 'Hello, welcome to MateClaw. This is a preview of the selected voice.', + ttsPreviewFailed: 'Voice preview failed. Check your provider settings.', ttsTitle: 'Text-to-Speech', ttsDesc: 'Configure TTS with Edge TTS (free), OpenAI TTS, and DashScope CosyVoice', ttsProviderOptions: { @@ -1825,7 +1836,7 @@ export default { toolsTagline: 'A tool is one call, one thing. The LLM decides when to invoke each tool autonomously.', toolsHint: 'Select tools this agent can use. Leave empty to use all enabled tools.', disableAllTools: 'This agent uses no user-pickable tools', - disableAllToolsHint: 'Saving with this on clears the agent\'s tool bindings and marks it as "explicitly no tools": neither user-pickable tools nor any enabled MCP tools enter the allowlist. System-level primitives (structured memory, workspace memory files, delegation, etc.) remain available so the agent can still operate. When off, picking nothing still falls back to "inherit global default".', + disableAllToolsHint: 'Saving clears tool bindings and stops automatically providing general file access, code or shell execution, network access, delegation, document generation and MCP tools. Only internal memory, time and task-state capabilities remain. Enabled skills can still contribute their declared tools; also turn on Disable all skills to disable those. When off, picking nothing inherits global defaults.', disableAllToolsBadge: 'Off', searchSkills: 'Search skill name, description, or version', searchTools: 'Search tool name, description, source, or group', @@ -3657,8 +3668,38 @@ export default { failed: 'Login failed. Please check your credentials.', }, enterprise: { + offer: { + badge: "PRIVATE DEPLOYMENT · ENTERPRISE GOVERNANCE", + scenarioLabel: "USE CASES", + roadmapLabel: "View roadmap", + scenarioPolicy: "Policy Q&A", + scenarioBi: "Chat-BI", + scenarioContract: "Contract review", + scenarioTender: "Tender analysis", + scenarioOps: "IT operations", + scenarioDocs: "Document collaboration", + title: "Taiyi Agents · SOE Edition", + description: "A privately deployed AI agent platform for enterprises and state-owned organizations, connecting models, knowledge, agents, and business systems with collaboration, approvals, and traceability.", + website: "Explore Taiyi Enterprise", + demo: "Request a demo", + scope: "These capabilities describe Taiyi Enterprise as presented on its website. The console contains demonstration scenario data; contact sales for an enterprise solution.", + roadmap: "Website roadmap: identity federation, dedicated domestic-stack adaptation, MLPS Level-3 certification, and MCP ecosystem integration.", + scenarios: "Solutions: policy Q&A, Chat-BI, contract review, tender analysis, IT operations assistance, and collaborative document editing.", + engineTitle: "Agent engine", + engineDesc: "Multi-turn ReAct and tool execution with streaming, runaway guards, and cancellation.", + governanceTitle: "Governance and audit", + governanceDesc: "Tool policies, human review, signed audit trails, replay, and outbound data masking.", + knowledgeTitle: "Permission-aware knowledge", + knowledgeDesc: "Hybrid retrieval with document and chunk access controls and source citations.", + workflowTitle: "Workflows and approvals", + workflowDesc: "DAG orchestration, branching, parallel execution, and human approval with suspend/resume.", + memoryTitle: "Organizational memory", + memoryDesc: "Profiles, evolving Soul, and facts scoped to workspaces, agents, users, and conversations.", + securityTitle: "Cryptography and private deployment", + securityDesc: "SM2/SM3/SM4 encryption and signing, sandboxing, and tenant isolation.", + }, eyebrow: 'Enterprise', - title: 'Enterprise AI Workbench', + title: 'Taiyi Agents · SOE Edition', subtitle: 'Verifiable judgment with cited evidence, owned approvals, and a complete audit trail — AI inside enterprise workflows, not yet another chatbox.', tabs: { overview: 'Overview', contract: 'Contract Review', account: 'Account Intel', approvals: 'Approvals', audit: 'Audit' }, risk: { high: 'High', medium: 'Medium', low: 'Low' }, diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 39a7c772..c778ff5b 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -497,6 +497,7 @@ export default { }, }, }, + ttsFailed: '语音合成或播放失败', ttsPlay: '朗读', ttsStop: '停止朗读', conversations: '会话列表', @@ -698,8 +699,8 @@ export default { pendingReply: '正在准备回复…', expand: '展开', // INCOMPLETE 截断卡片(finishReason=incomplete) - incompleteTitle: '回答因检测到重复输出已被自动截断', - incompleteDescription: '系统在你看到的部分之后检测到模型开始重复同一段内容(或在思考阶段无产出),已自动截断以避免无效输出。点下方按钮重新生成完整回答,或在输入框补充提示让模型聚焦剩余内容。', + incompleteTitle: '回答尚未完成', + incompleteDescription: '本次回答未完成,具体原因请查看消息内容:可能是重复输出触发保护、思考达到安全上限,或模型耗尽输出 token 预算。可重新生成、调整思考和输出预算,或拆分任务后继续。', incompleteRetry: '重新生成', // EVIDENCE_INSUFFICIENT 提示卡(finishReason=evidence_insufficient) evidenceTitle: '任务已完成,但部分源码引用未被验证', @@ -1394,6 +1395,16 @@ export default { model3dDesc: '配置 AI 3D 模型生成,支持腾讯混元 3D(HY-3D-3.1 / HY-3D-3.0 / HY-3D-Express)', model3dProviderOptions: { auto: '自动选择', hunyuan: '腾讯混元 3D' }, model3dProviderTags: { requiresKey: '需配置 SecretId:SecretKey' }, + ttsVoiceLabel: '音色', + ttsVoiceHint: '音色随提供商变化;切换提供商后重新选择。试听无需保存,也不会切换到其他提供商。', + ttsAutoVoiceProvider: '自动模式当前优先使用:{provider}', + ttsVoiceDefault: '使用默认音色', + ttsVoiceUnavailable: '请先配置该提供商的 API Key,再试听。', + ttsPreview: '试听', + ttsPreviewLoading: '正在合成…', + ttsPreviewStop: '停止试听', + ttsPreviewText: '你好,欢迎使用 MateClaw。这是当前音色的试听效果。', + ttsPreviewFailed: '音色试听失败,请检查提供商配置。', ttsTitle: '语音合成', ttsDesc: '配置 TTS 语音合成,支持 Edge TTS(免费)、OpenAI TTS、DashScope CosyVoice', ttsProviderOptions: { @@ -1670,7 +1681,7 @@ export default { toolsTagline: '工具 = 一次调用,做一件事。由 LLM 自主决定何时调用。', toolsHint: '选择此智能体可使用的工具。留空则使用所有已启用的工具。', disableAllTools: '此智能体不使用任何用户可选工具', - disableAllToolsHint: '开启后保存会清空该智能体的工具绑定,并标记为「显式无工具」:用户可选工具与已启用的 MCP 工具都不会进入 allowlist。系统级内核工具(结构化记忆、工作区记忆文件、委派等)仍保留以保证基本运行。关闭后,未勾选任何工具仍按「继承全局默认」处理。', + disableAllToolsHint: '开启后保存会清空工具绑定,不再自动提供通用文件读写、代码或命令执行、网络访问、委派、文档生成及 MCP 工具。仅保留记忆、时间和任务状态等内部能力。仍启用的技能可提供其声明的工具;如需一并禁用,请同时开启「禁用全部技能」。关闭后,未勾选任何工具仍按「继承全局默认」处理。', disableAllToolsBadge: '已禁用', searchSkills: '搜索技能名称、描述或版本', searchTools: '搜索工具名称、描述、来源或分组', @@ -3669,8 +3680,38 @@ export default { failed: '登录失败,请检查账号密码', }, enterprise: { + offer: { + badge: "私有化部署 · 企业级治理", + scenarioLabel: "落地场景", + roadmapLabel: "查看后续规划", + scenarioPolicy: "制度政策问答", + scenarioBi: "Chat-BI 问数", + scenarioContract: "合同智能审核", + scenarioTender: "招投标解析", + scenarioOps: "IT 运维助手", + scenarioDocs: "公文协同编辑", + title: "太一智能体央国企版", + description: "面向企业与央国企的私有化 AI 智能体平台,将模型、知识、智能体与业务系统连接起来,支持企业流程中的协作、审批与追溯。", + website: "了解太一企业版", + demo: "预约演示", + scope: "以下为太一企业版官网能力介绍。当前控制台中的场景数据用于演示,企业方案请联系商务咨询。", + roadmap: "官网规划中:统一身份联邦、国产软硬件专项深度适配、等保三级认证及 MCP 生态接入。", + scenarios: "场景方案:制度政策问答、Chat-BI 问数、合同智能审核、招投标解析、IT 运维助手、公文协同编辑。", + engineTitle: "智能体引擎", + engineDesc: "多轮 ReAct 编排与工具调用,支持流式响应、失控保护和协作取消。", + governanceTitle: "治理与审计", + governanceDesc: "工具策略、危险操作人审、签名验签与轨迹回放,以及出域脱敏。", + knowledgeTitle: "权限感知知识库", + knowledgeDesc: "向量与关键词检索,文档和片段权限隔离,回答附带出处引用。", + workflowTitle: "工作流与人工审批", + workflowDesc: "DAG 编排、分支与并行执行,人工审批挂起和恢复。", + memoryTitle: "组织长期记忆", + memoryDesc: "组织画像、Soul 演进与事实库,按工作空间、员工、用户及会话隔离。", + securityTitle: "国密与私有部署", + securityDesc: "SM2 / SM3 / SM4 加密与签名、安全沙箱及多租户隔离。", + }, eyebrow: '企业场景', - title: '企业 AI 工作台', + title: '太一智能体央国企版', subtitle: '把 AI 判断、证据引用、审批责任一体化 —— 让 AI 进入企业流程,而不是又一个聊天框。', tabs: { overview: '总览', diff --git a/mateclaw-ui/src/types/index.ts b/mateclaw-ui/src/types/index.ts index 11c703f3..af3f3d69 100644 --- a/mateclaw-ui/src/types/index.ts +++ b/mateclaw-ui/src/types/index.ts @@ -141,6 +141,8 @@ export interface ChatAttachment { } export interface ToolCallMeta { + toolCallId?: string + structuredContent?: Record name: string arguments?: string status: 'running' | 'completed' | 'awaiting_approval' @@ -223,6 +225,7 @@ export interface MessageSegment { toolName?: string toolArgs?: string toolResult?: string + structuredContent?: Record toolSuccess?: boolean /** LLM-provided tool call id, used to pair tool_call_started ↔ tool_call_completed */ toolCallId?: string diff --git a/mateclaw-ui/src/views/Channels.vue b/mateclaw-ui/src/views/Channels.vue index 864a6658..33223faa 100644 --- a/mateclaw-ui/src/views/Channels.vue +++ b/mateclaw-ui/src/views/Channels.vue @@ -204,7 +204,7 @@ const wizardType = ref('') // existing channels (where 3-step would feel like ceremony). const WIZARD_TYPES = new Set([ 'telegram', 'discord', 'slack', 'qq', - 'web', 'webchat', 'webhook', + 'web', 'webchat', 'wecom', 'weixin', 'dingtalk', 'feishu', ]) diff --git a/mateclaw-ui/src/views/Enterprise/EnterpriseOffer.vue b/mateclaw-ui/src/views/Enterprise/EnterpriseOffer.vue new file mode 100644 index 00000000..4a3c5e6d --- /dev/null +++ b/mateclaw-ui/src/views/Enterprise/EnterpriseOffer.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/mateclaw-ui/src/views/Enterprise/Overview.vue b/mateclaw-ui/src/views/Enterprise/Overview.vue index 910b2c1b..283284c6 100644 --- a/mateclaw-ui/src/views/Enterprise/Overview.vue +++ b/mateclaw-ui/src/views/Enterprise/Overview.vue @@ -1,5 +1,6 @@