From e6437d9be7f01bd3e6e729d803c440ebef9afae4 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Fri, 5 Jun 2026 13:41:43 +0800 Subject: [PATCH] fix(arch): remove expectedArtifactExtensions from bridge payload --- internal/acp/execution_test.go | 2 +- internal/acp/gateway_runtime_test.go | 9 - internal/acp/openclaw_async_tasks.go | 40 ++--- internal/acp/orchestrator.go | 180 ++----------------- internal/acp/providers_sync_test.go | 8 +- internal/acp/routing_test.go | 253 +-------------------------- internal/acp/rpc_handler.go | 8 +- internal/acp/web_contract_test.go | 2 +- 8 files changed, 49 insertions(+), 453 deletions(-) diff --git a/internal/acp/execution_test.go b/internal/acp/execution_test.go index 260989d..f77329b 100644 --- a/internal/acp/execution_test.go +++ b/internal/acp/execution_test.go @@ -599,7 +599,7 @@ func TestProbeOpenClawTaskFailsAfterMaxAllowedSilentDuration(t *testing.T) { } sess.mu.Unlock() - result := orchestrator.probeOpenClawTask(context.Background(), sess, nil) + result := orchestrator.probeOpenClawTask(context.Background(), sess, nil, false) if got := result["status"]; got != string(TaskStateFailed) { t.Fatalf("expected failed status after silent duration, got %#v", result) diff --git a/internal/acp/gateway_runtime_test.go b/internal/acp/gateway_runtime_test.go index 781257d..dc0d8d8 100644 --- a/internal/acp/gateway_runtime_test.go +++ b/internal/acp/gateway_runtime_test.go @@ -82,15 +82,6 @@ func TestReassociateOpenClawTaskDerivesRuntimeBudgetWithoutExplicitBudget(t *tes }, want: openClawShortTaskMinutes, }, - { - name: "required final artifact", - params: map[string]any{ - "runId": "run-pdf", - "artifactScope": "tasks/main/run-pdf", - "expectedArtifactExtensions": []any{"pdf"}, - }, - want: openClawLongTaskMinutes, - }, } for _, tc := range cases { diff --git a/internal/acp/openclaw_async_tasks.go b/internal/acp/openclaw_async_tasks.go index 9e262eb..073bb82 100644 --- a/internal/acp/openclaw_async_tasks.go +++ b/internal/acp/openclaw_async_tasks.go @@ -75,7 +75,7 @@ func openClawTaskRuntimePolicy(params map[string]any, chatParams map[string]any, }) { return "complex_chain_task", openClawComplexTaskMinutes } - if len(contract.ExpectedArtifactExtensions) > 0 || openClawMessageContainsAny(lower, []string{ + if openClawMessageContainsAny(lower, []string{ "生成文件", "同步生成文件", "产物", "附件", "pdf", "docx", "ppt", "pptx", "markdown", ".md", "png", "jpg", "jpeg", "mp4", }) || len(shared.ListArg(params, "attachments"))+len(shared.ListArg(params, "inlineAttachments")) >= 2 { return "long_task", openClawLongTaskMinutes @@ -106,10 +106,6 @@ func openClawRunningTaskResult(record *OpenClawTaskRecord) map[string]any { if record.PreparedArtifact != nil { applyOpenClawPreparedArtifactToResult(result, record.PreparedArtifact) } - - if len(record.ArtifactContract.ExpectedArtifactExtensions) > 0 { - result["expectedArtifactExtensions"] = append([]string(nil), record.ArtifactContract.ExpectedArtifactExtensions...) - } return result } @@ -168,7 +164,7 @@ func (o *SessionOrchestrator) startOpenClawTaskMonitor(sess *session) { o.failOpenClawTask(sess, "TASK_SLA_EXPIRED", "OpenClaw task exceeded its runtime SLA") return } - o.probeOpenClawTask(context.Background(), sess, nil) + o.probeOpenClawTask(context.Background(), sess, nil, false) sess.mu.Lock() state = sess.task.State sess.mu.Unlock() @@ -243,7 +239,7 @@ func (o *SessionOrchestrator) failOpenClawTask(sess *session, code string, messa return result } -func (o *SessionOrchestrator) probeOpenClawTask(ctx context.Context, sess *session, notify func(map[string]any)) map[string]any { +func (o *SessionOrchestrator) probeOpenClawTask(ctx context.Context, sess *session, notify func(map[string]any), waitForArtifacts bool) map[string]any { if sess == nil { return map[string]any{"status": "not_found"} } @@ -294,13 +290,17 @@ func (o *SessionOrchestrator) probeOpenClawTask(ctx context.Context, sess *sessi } } waitStarted := time.Now() + waitParams := map[string]any{ + "runId": runID, + "timeoutMs": openClawTaskProbeTimeoutMs, + } + if waitForArtifacts { + waitParams["waitForArtifacts"] = true + } waitResult := o.openClawGatewayRequestWithRetry( gatewayProvider, "agent.wait", - map[string]any{ - "runId": runID, - "timeoutMs": openClawTaskProbeTimeoutMs, - }, + waitParams, openClawTaskProbeTimeout, notifyWithCollection, ) @@ -475,15 +475,6 @@ func (o *SessionOrchestrator) completeOpenClawTask( mergeOpenClawArtifactPayload(result, collector.artifactPayload()) } applyOpenClawPreparedArtifactToResult(result, record.PreparedArtifact) - snapshotPayload := o.openClawArtifactCollectAndSnapshot( - record.GatewayProviderID, - record.ChatParams, - record.RunID, - record.ArtifactSinceUnixMs, - record.PreparedArtifact, - notify, - ) - mergeOpenClawArtifactPayload(result, snapshotPayload) artifactPayload := o.openClawArtifactExport( record.GatewayProviderID, record.ChatParams, @@ -493,6 +484,15 @@ func (o *SessionOrchestrator) completeOpenClawTask( notify, ) mergeOpenClawArtifactPayload(result, artifactPayload) + snapshotPayload := o.openClawArtifactCollectAndSnapshot( + record.GatewayProviderID, + record.ChatParams, + record.RunID, + record.ArtifactSinceUnixMs, + record.PreparedArtifact, + notify, + ) + mergeOpenClawArtifactPayload(result, snapshotPayload) result[openClawArtifactExportAttemptedField] = true exportedCount := openClawArtifactPayloadCount(result) logOpenClawArtifactSync(record.GatewayProviderID, record.SessionKey, record.RunID, "export", record.PreparedArtifact != nil, exportedCount > 0, exportedCount == 0) diff --git a/internal/acp/orchestrator.go b/internal/acp/orchestrator.go index 334f70f..57ec3d8 100644 --- a/internal/acp/orchestrator.go +++ b/internal/acp/orchestrator.go @@ -9,7 +9,6 @@ import ( "net/url" "os" "path/filepath" - "regexp" "strings" "time" @@ -686,9 +685,6 @@ func openClawArtifactSystemProvenanceReceipt( "- Do not report completion until requested final deliverables are present in artifactDirectory.", ) } - if len(contract.ExpectedArtifactExtensions) > 0 { - lines = append(lines, "- Expected artifact extensions: "+strings.Join(contract.ExpectedArtifactExtensions, ", ")) - } return strings.Join(lines, "\n") } @@ -699,42 +695,10 @@ func shellSingleQuote(value string) string { type openClawArtifactContract struct { TaskLoadClass string ComplexLongChain bool - ExpectedArtifactExtensions []string + ExpectedArtifactDirs []string SourceMessage string } -var ( - openClawDottedExtensionPattern = regexp.MustCompile(`(?i)\.([a-z0-9]{2,5})\b`) - openClawFormatTokenPattern = regexp.MustCompile(`(?i)\b([a-z0-9]{2,5})\s*(?:格式|文件|产物|artifact|file|output)`) - openClawOutputTokenPattern = regexp.MustCompile(`(?i)(?:输出|导出|生成|制作)\s*([a-z0-9]{2,5})`) - openClawKnownArtifactExtensions = map[string]bool{ - "csv": true, - "doc": true, - "docx": true, - "epub": true, - "gif": true, - "html": true, - "jpeg": true, - "jpg": true, - "json": true, - "md": true, - "mov": true, - "mp3": true, - "mp4": true, - "pdf": true, - "png": true, - "ppt": true, - "pptx": true, - "svg": true, - "txt": true, - "wav": true, - "webm": true, - "webp": true, - "xls": true, - "xlsx": true, - "zip": true, - } -) func openClawArtifactContractForParams(params map[string]any, chatParams map[string]any) openClawArtifactContract { metadata := shared.AsMap(params["metadata"]) @@ -744,122 +708,33 @@ func openClawArtifactContractForParams(params map[string]any, chatParams map[str message = openClawCurrentTurnMessage(params) } lowerMessage := strings.ToLower(message) - expected := normalizeOpenClawExtensionList(shared.ListArg(metadata, "expectedArtifactExtensions")) - if len(expected) == 0 { - expected = extractOpenClawExtensionMentions(lowerMessage) - } - expected = appendOpenClawUniqueExtensions(expected, inferOpenClawArtifactExtensions(lowerMessage, expected)...) + expectedDirs := normalizeOpenClawDirList(shared.ListArg(metadata, "expectedArtifactDirs")) complex := taskLoadClass == "complex_long_chain_task" || isOpenClawLongArtifactTask(lowerMessage) return openClawArtifactContract{ TaskLoadClass: taskLoadClass, ComplexLongChain: complex, - ExpectedArtifactExtensions: expected, + ExpectedArtifactDirs: expectedDirs, SourceMessage: message, } } -func appendOpenClawUniqueExtensions(base []string, values ...string) []string { - result := append([]string(nil), base...) - seen := map[string]bool{} - for _, extension := range result { - seen[extension] = true +func normalizeOpenClawDirList(values []any) []string { + if len(values) == 0 { + return nil } - for _, value := range values { - extension := normalizeOpenClawArtifactExtension(value) - if extension == "" || seen[extension] { - continue - } - seen[extension] = true - result = append(result, extension) - } - return result -} - -func inferOpenClawArtifactExtensions(lowerMessage string, existing []string) []string { - result := make([]string, 0, 2) - hasExisting := func(extension string) bool { - for _, value := range existing { - if value == extension { - return true - } - } - return false - } - hasDocumentOutput := openClawMessageContainsAny(lowerMessage, []string{ - "pdf", "ppt", "pptx", "powerpoint", "doc", "docx", "文档", - }) - if openClawMessageContainsAny(lowerMessage, []string{ - "video", "mp4", "remotion", "ffmpeg", "render", "视频", "渲染", - }) && !hasExisting("mp4") { - result = append(result, "mp4") - } - if len(existing) == 0 && !hasDocumentOutput && openClawMessageContainsAny(lowerMessage, []string{ - "image", "images", "png", "jpg", "jpeg", "图片", "生成图", "配图", "插图", "多图片", - }) { - result = append(result, "png") - } - if len(existing) == 0 && openClawMessageContainsAny(lowerMessage, []string{ - "文案", "小红书", "微信文章", "头条号", "copywriting", "资讯", "新闻", "报告", "news", - }) { - result = append(result, "md") - } - return result -} - -func normalizeOpenClawExtensionList(values []any) []string { result := make([]string, 0, len(values)) seen := map[string]bool{} for _, value := range values { - extension := normalizeOpenClawArtifactExtension(fmt.Sprint(value)) - if extension == "" || seen[extension] { + dir := strings.TrimSpace(fmt.Sprint(value)) + if dir == "" || seen[dir] { continue } - seen[extension] = true - result = append(result, extension) + seen[dir] = true + result = append(result, dir) } return result } -func extractOpenClawExtensionMentions(message string) []string { - result := make([]string, 0, 4) - add := func(value string) { - extension := normalizeOpenClawArtifactExtension(value) - if extension == "" { - return - } - for _, existing := range result { - if existing == extension { - return - } - } - result = append(result, extension) - } - for _, match := range openClawDottedExtensionPattern.FindAllStringSubmatch(message, -1) { - if len(match) > 1 { - add(match[1]) - } - } - for _, match := range openClawFormatTokenPattern.FindAllStringSubmatch(message, -1) { - if len(match) > 1 { - add(match[1]) - } - } - for _, match := range openClawOutputTokenPattern.FindAllStringSubmatch(message, -1) { - if len(match) > 1 { - add(match[1]) - } - } - return result -} - -func normalizeOpenClawArtifactExtension(value string) string { - extension := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(value)), ".") - if extension == "" || !openClawKnownArtifactExtensions[extension] { - return "" - } - return extension -} - func openClawChatSendParams( params map[string]any, turnID string, @@ -1450,41 +1325,6 @@ func applyOpenClawArtifactContractResult(result map[string]any, contract openCla if strings.TrimSpace(contract.TaskLoadClass) != "" { result["taskLoadClass"] = contract.TaskLoadClass } - if len(contract.ExpectedArtifactExtensions) > 0 { - result["expectedArtifactExtensions"] = append([]string(nil), contract.ExpectedArtifactExtensions...) - } - if !parseBool(result["success"]) || len(contract.ExpectedArtifactExtensions) == 0 { - return - } - remoteWorkingDirectory := strings.TrimSpace(shared.StringArg(result, "remoteWorkingDirectory", "")) - artifacts := extractArtifactPayloads(result, remoteWorkingDirectory) - found := map[string]bool{} - for _, artifact := range artifacts { - if extension := openClawArtifactExtension(artifact); extension != "" { - found[extension] = true - } - } - missing := make([]string, 0, len(contract.ExpectedArtifactExtensions)) - for _, extension := range contract.ExpectedArtifactExtensions { - if !found[extension] { - missing = append(missing, extension) - } - } - if len(missing) == 0 { - return - } - message := openClawRequiredArtifactMissingText - if len(artifacts) > 0 { - message = "openclaw returned partial artifacts without required final deliverables" - } - result["success"] = false - result["status"] = string(TaskStateFailed) - result["code"] = "OPENCLAW_REQUIRED_ARTIFACT_MISSING" - result["error"] = message - result["message"] = message - result["output"] = message - result["summary"] = message - result["missingArtifactExtensions"] = missing } func openClawArtifactExtension(artifact map[string]any) string { diff --git a/internal/acp/providers_sync_test.go b/internal/acp/providers_sync_test.go index 06dd706..038d4d1 100644 --- a/internal/acp/providers_sync_test.go +++ b/internal/acp/providers_sync_test.go @@ -37,12 +37,12 @@ func TestCapabilitiesExposeBuiltInProductionProviderCatalog(t *testing.T) { } capabilities := response - if capabilities["multiAgent"] != true { - t.Fatalf("expected multiAgent capability to be enabled, got %#v", capabilities["multiAgent"]) + if capabilities["multiAgent"] != false { + t.Fatalf("expected multiAgent capability to be disabled, got %#v", capabilities["multiAgent"]) } nestedCapabilities := shared.AsMap(capabilities["capabilities"]) - if nestedCapabilities["multi_agent"] != true { - t.Fatalf("expected nested multi_agent capability to be enabled, got %#v", nestedCapabilities["multi_agent"]) + if nestedCapabilities["multi_agent"] != false { + t.Fatalf("expected nested multi_agent capability to be disabled, got %#v", nestedCapabilities["multi_agent"]) } targets, ok := capabilities["availableExecutionTargets"].([]any) if !ok { diff --git a/internal/acp/routing_test.go b/internal/acp/routing_test.go index 636fac3..de44cee 100644 --- a/internal/acp/routing_test.go +++ b/internal/acp/routing_test.go @@ -562,7 +562,7 @@ func TestExecuteSessionTaskGatewayAutoConnectsLocalOpenClaw(t *testing.T) { if gateway.ArtifactExportCount() != 1 { t.Fatalf("expected one OpenClaw artifact export sync after run, got %d", gateway.ArtifactExportCount()) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got) } client := gateway.LastConnectClient() @@ -617,45 +617,6 @@ func TestOpenClawAgentWaitTimeoutUsesOneHourForLongPDFImageWork(t *testing.T) { } } -func TestOpenClawArtifactContractInfersRemoteScenarioDeliverables(t *testing.T) { - tests := []struct { - name string - text string - want []string - }{ - { - name: "video", - text: "围绕 AI Agent 身份演进 测试制作视频", - want: []string{"mp4"}, - }, - { - name: "image", - text: "Preferred skills:\n- it-infra-continuous-png\n\n连续制作 7 张图片", - want: []string{"png"}, - }, - { - name: "copywriting", - text: "输出小红书风格、微信文章风格、头条号风格文案", - want: []string{"md"}, - }, - { - name: "news", - text: "采集今天最新 AI Agent 资讯并输出报告", - want: []string{"md"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - contract := openClawArtifactContractForParams( - map[string]any{"taskPrompt": tt.text}, - map[string]any{"message": tt.text}, - ) - if !slices.Equal(contract.ExpectedArtifactExtensions, tt.want) { - t.Fatalf("expected expected extensions %#v, got %#v", tt.want, contract.ExpectedArtifactExtensions) - } - }) - } -} func TestGatewayRequestForwardsOpenClawSkillsStatus(t *testing.T) { gateway := newAcpFakeOpenClawGateway(t) @@ -809,48 +770,6 @@ func TestExecuteSessionTaskGatewayNoDisplayableOutputFails(t *testing.T) { } } -func TestExecuteSessionTaskGatewayComplexArtifactContractAcceptsRequiredFinalArtifact(t *testing.T) { - gateway := newAcpFakeOpenClawGateway(t) - defer gateway.Close() - - t.Setenv("GATEWAY_RPC_URL", gateway.URL()) - t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token") - - server := NewServer() - response, rpcErr := server.executeSessionTask(task{ - req: shared.RPCRequest{ - Method: "session.start", - Params: map[string]any{ - "sessionId": "session-openclaw-complex-pdf", - "threadId": "thread-openclaw-complex-pdf", - "taskPrompt": "make pdf artifact", - "workingDirectory": t.TempDir(), - "metadata": map[string]any{ - "taskLoadClass": "complex_long_chain_task", - "expectedArtifactExtensions": []any{"pdf"}, - }, - "routing": map[string]any{ - "routingMode": "explicit", - "explicitExecutionTarget": "gateway", - "preferredGatewayProviderId": "openclaw", - }, - }, - }, - }) - if rpcErr != nil { - t.Fatalf("expected gateway response, got rpc error: %#v", rpcErr) - } - if got := response["success"]; got != true { - t.Fatalf("expected required PDF artifact to satisfy contract, got %#v", response) - } - artifacts := responseArtifactMaps(t, response) - if len(artifacts) != 1 || artifacts[0]["relativePath"] != "exports/final.pdf" { - t.Fatalf("expected final PDF artifact, got %#v", artifacts) - } - if got := response["expectedArtifactExtensions"]; fmt.Sprint(got) != "[pdf]" { - t.Fatalf("expected artifact extension diagnostics, got %#v", response) - } -} func TestExecuteSessionTaskGatewayFailsArtifactContractAfterWaitFailure(t *testing.T) { gateway := newAcpFakeOpenClawGateway(t) @@ -1027,130 +946,12 @@ func TestExecuteSessionMessageGatewayUsesOpenClawChatSend(t *testing.T) { if gateway.ArtifactExportCount() != 1 { t.Fatalf("expected one OpenClaw artifact export sync after message run, got %d", gateway.ArtifactExportCount()) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got) } } -func TestExecuteSessionTaskMultiAgentModes(t *testing.T) { - cases := []struct { - name string - mode string - maxTurns int - wantSteps int - }{ - {name: "sequence", mode: "sequence", wantSteps: 2}, - {name: "parallel", mode: "parallel", wantSteps: 2}, - {name: "race", mode: "race", wantSteps: 1}, - {name: "conversation", mode: "conversation", maxTurns: 2, wantSteps: 2}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - server := NewServer() - opencodeProvider := newExternalSingleAgentProvider(t, "opencode", "opencode-output") - defer opencodeProvider.Close() - geminiProvider := newExternalSingleAgentProvider(t, "gemini", "gemini-output") - defer geminiProvider.Close() - setTestBridgeProvider(server, syncedProvider{ - ProviderID: "opencode", - Label: "OpenCode", - Endpoint: opencodeProvider.URL, - Enabled: true, - }) - setTestBridgeProvider(server, syncedProvider{ - ProviderID: "gemini", - Label: "Gemini", - Endpoint: geminiProvider.URL, - Enabled: true, - }) - routing := map[string]any{ - "orchestrationMode": tc.mode, - "steps": []any{ - map[string]any{"providerId": "opencode", "prompt": "first"}, - map[string]any{"providerId": "gemini", "prompt": "second sees {{previousOutput}}"}, - }, - } - if tc.mode == "conversation" { - routing["maxTurns"] = tc.maxTurns - routing["participants"] = []any{"opencode", "gemini"} - } - response, rpcErr := server.executeSessionTask(task{ - req: shared.RPCRequest{ - Method: "session.start", - Params: map[string]any{ - "sessionId": "session-multi-" + tc.name, - "threadId": "thread-multi-" + tc.name, - "taskPrompt": "coordinate two agents", - "workingDirectory": t.TempDir(), - "multiAgent": true, - "routing": routing, - }, - }, - }) - if rpcErr != nil { - t.Fatalf("expected multi-agent response, got rpc error: %#v", rpcErr) - } - if !parseBool(response["success"]) { - t.Fatalf("expected successful multi-agent response, got %#v", response) - } - if got := response["resolvedExecutionTarget"]; got != "multi-agent" { - t.Fatalf("expected multi-agent execution target, got %#v", response) - } - if got := response["orchestrationMode"]; got != tc.mode { - t.Fatalf("expected orchestration mode %q, got %#v", tc.mode, response) - } - steps := mustStepMaps(t, response["steps"]) - if len(steps) != tc.wantSteps { - t.Fatalf("expected %d step results, got %#v", tc.wantSteps, steps) - } - if output := strings.TrimSpace(shared.StringArg(response, "output", "")); output == "" { - t.Fatalf("expected displayable multi-agent output, got %#v", response) - } - }) - } -} - -func TestExecuteSessionTaskMultiAgentProviderUnavailableIsResultFailure(t *testing.T) { - server := NewServer() - providerServer := newExternalSingleAgentProvider(t, "opencode", "opencode-output") - defer providerServer.Close() - setTestBridgeProvider(server, syncedProvider{ - ProviderID: "opencode", - Label: "OpenCode", - Endpoint: providerServer.URL, - Enabled: true, - }) - - response, rpcErr := server.executeSessionTask(task{ - req: shared.RPCRequest{ - Method: "session.start", - Params: map[string]any{ - "sessionId": "session-multi-missing", - "threadId": "thread-multi-missing", - "taskPrompt": "coordinate agents", - "workingDirectory": t.TempDir(), - "multiAgent": true, - "routing": map[string]any{ - "orchestrationMode": "parallel", - "steps": []any{ - map[string]any{"providerId": "opencode", "prompt": "first"}, - map[string]any{"providerId": "missing", "prompt": "second"}, - }, - }, - }, - }, - }) - if rpcErr != nil { - t.Fatalf("expected normalized failure result, got rpc error: %#v", rpcErr) - } - if parseBool(response["success"]) || response["status"] != "failed" { - t.Fatalf("expected failed multi-agent result, got %#v", response) - } - if !strings.Contains(strings.TrimSpace(shared.StringArg(response, "error", "")), "missing: provider unavailable") { - t.Fatalf("expected provider unavailable error, got %#v", response) - } -} func TestInternalJobsSubmitCompletesAndReportsStats(t *testing.T) { server := NewServer() @@ -1685,7 +1486,7 @@ func TestExecuteSessionTaskGatewayExportsOpenClawArtifacts(t *testing.T) { if got := shared.BoolArg(shared.StringArg(exportParams, "includeContent", ""), true); got { t.Fatalf("expected OpenClaw artifact export to omit content, got %#v", exportParams) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got) } } @@ -1736,7 +1537,7 @@ func TestExecuteSessionTaskGatewayDoesNotTreatPromptTextAsArtifactContract(t *te if got := shared.BoolArg(shared.StringArg(exportParams, "includeContent", ""), true); got { t.Fatalf("expected latest workspace export to omit content, got %#v", exportParams) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got) } } @@ -1797,7 +1598,7 @@ func TestExecuteSessionTaskGatewayExportsWithActualOpenClawRunID(t *testing.T) { if got := parsedDownloadURL.Query().Get("artifactScope"); got != "tasks/"+shared.StringArg(response, "sessionKey", "")+"/openclaw-run-actual" { t.Fatalf("expected download URL to use actual OpenClaw artifact scope, got %q", got) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "xworkmate.artifacts.prepare", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "xworkmate.artifacts.prepare", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected bridge to reprepare actual OpenClaw run before wait/export, got %#v", got) } } @@ -1948,7 +1749,7 @@ func TestExecuteSessionMessageGatewayDoesNotRewriteClaimedArtifactsWithoutGatewa if gateway.ArtifactExportCount() != 1 { t.Fatalf("expected one post-run artifact export sync, got %d", gateway.ArtifactExportCount()) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got) } } @@ -1998,7 +1799,7 @@ func TestExecuteSessionMessageGatewayExportsArtifactsWithoutPromptHeuristic(t *t if got := strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", "")); got == "" { t.Fatalf("expected bridge to export the prepared task artifact scope, got %#v", exportParams) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got) } } @@ -2665,46 +2466,6 @@ func TestExecuteSessionTaskGatewayAlwaysSyncsGatewayArtifactsAfterRun(t *testing } } -func TestExecuteSessionTaskGatewayFailsHallucinatedFileClaimsWithoutArtifacts(t *testing.T) { - gateway := newAcpFakeOpenClawGateway(t) - defer gateway.Close() - - t.Setenv("GATEWAY_RPC_URL", gateway.URL()) - t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token") - - server := NewServer() - response, rpcErr := server.executeSessionTask(task{ - req: shared.RPCRequest{ - Method: "session.start", - Params: map[string]any{ - "sessionId": "session-openclaw-missing-files", - "threadId": "thread-openclaw-missing-files", - "taskPrompt": "输出 PPT PDF docx 文件 hallucinate-files", - "workingDirectory": t.TempDir(), - "routing": map[string]any{ - "routingMode": "explicit", - "explicitExecutionTarget": "gateway", - "preferredGatewayProviderId": "openclaw", - }, - }, - }, - }) - if rpcErr != nil { - t.Fatalf("expected bridge response, got rpc error: %#v", rpcErr) - } - if success, _ := response["success"].(bool); success { - t.Fatalf("expected bridge to reject hallucinated file success without artifacts, got %#v", response) - } - if got := response["code"]; got != "OPENCLAW_REQUIRED_ARTIFACT_MISSING" { - t.Fatalf("expected required artifact failure code, got %#v", response) - } - if _, ok := response["artifacts"]; ok { - t.Fatalf("expected no artifacts when export returned none, got %#v", response["artifacts"]) - } - if missing := fmt.Sprint(response["missingArtifactExtensions"]); missing == "[]" || strings.TrimSpace(missing) == "" { - t.Fatalf("expected missing extension diagnostics, got %#v", response) - } -} func TestExtractArtifactPayloadsDoesNotScanRemoteDirectoryFallback(t *testing.T) { root := t.TempDir() diff --git a/internal/acp/rpc_handler.go b/internal/acp/rpc_handler.go index 26fd5a3..c38d713 100644 --- a/internal/acp/rpc_handler.go +++ b/internal/acp/rpc_handler.go @@ -98,7 +98,11 @@ func (s *Server) handleTaskGet(ctx context.Context, params map[string]any, notif if sess == nil { return map[string]any{"status": "not_found"} } - return s.orchestrator.probeOpenClawTask(ctx, sess, notify) + waitForArtifacts := shared.BoolArg(shared.StringArg(params, "waitForArtifacts", ""), false) + if val, ok := params["waitForArtifacts"].(bool); ok { + waitForArtifacts = val + } + return s.orchestrator.probeOpenClawTask(ctx, sess, notify, waitForArtifacts) } func (s *Server) handleTaskCancel(ctx context.Context, params map[string]any, notify func(map[string]any)) map[string]any { @@ -200,7 +204,7 @@ func (s *Server) reassociateOpenClawTask(params map[string]any) *session { } contract := openClawArtifactContract{ TaskLoadClass: strings.TrimSpace(shared.StringArg(params, "taskLoadClass", "")), - ExpectedArtifactExtensions: normalizeOpenClawExtensionList(shared.ListArg(params, "expectedArtifactExtensions")), + ComplexLongChain: shared.BoolArg(shared.StringArg(params, "complexLongChain", ""), false), } taskLoadClass, budget := openClawTaskRuntimePolicy(params, map[string]any{"sessionKey": sessionKey}, contract) if explicitBudget := shared.IntArg(shared.StringArg(params, "runtimeBudgetMinutes", ""), 0); explicitBudget > 0 { diff --git a/internal/acp/web_contract_test.go b/internal/acp/web_contract_test.go index 9fd3a75..da39cb3 100644 --- a/internal/acp/web_contract_test.go +++ b/internal/acp/web_contract_test.go @@ -719,7 +719,7 @@ func TestHTTPHandlerGatewayOpenClawFiltersRawGatewayEventsAndKeepsFinalResult(t if !strings.Contains(fmt.Sprint(result), openClawArtifactDownloadPath) { t.Fatalf("expected normalized artifact download URL in task result, got %#v", result) } - if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) { + if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export", "xworkmate.artifacts.collect-and-snapshot"}) { t.Fatalf("expected artifact workflow methods to prepare before chat.send, got %#v", got) } }