From 2ace2722de27af96eca73d53f9541fb645bbd13d Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Fri, 5 Jun 2026 21:23:19 +0800 Subject: [PATCH] Fix OpenClaw artifact dirs protocol boundary --- internal/acp/openclaw_async_tasks.go | 2 ++ internal/acp/orchestrator.go | 26 +++++++------------ internal/acp/routing_test.go | 26 +++++++++++++++++++ .../validate-openclaw-session.sh | 3 ++- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/internal/acp/openclaw_async_tasks.go b/internal/acp/openclaw_async_tasks.go index d535109..c74b93d 100644 --- a/internal/acp/openclaw_async_tasks.go +++ b/internal/acp/openclaw_async_tasks.go @@ -484,6 +484,7 @@ func (o *SessionOrchestrator) completeOpenClawTask( artifactPayload := o.openClawArtifactExport( record.GatewayProviderID, record.ChatParams, + record.ArtifactContract, record.RunID, record.ArtifactSinceUnixMs, record.PreparedArtifact, @@ -493,6 +494,7 @@ func (o *SessionOrchestrator) completeOpenClawTask( snapshotPayload := o.openClawArtifactCollectAndSnapshot( record.GatewayProviderID, record.ChatParams, + record.ArtifactContract, record.RunID, record.ArtifactSinceUnixMs, record.PreparedArtifact, diff --git a/internal/acp/orchestrator.go b/internal/acp/orchestrator.go index 4d8f757..6c08652 100644 --- a/internal/acp/orchestrator.go +++ b/internal/acp/orchestrator.go @@ -707,7 +707,8 @@ func openClawArtifactContractForParams(params map[string]any, chatParams map[str message = openClawCurrentTurnMessage(params) } lowerMessage := strings.ToLower(message) - expectedDirs := normalizeOpenClawDirList(shared.ListArg(metadata, "expectedArtifactDirs")) + contract := shared.AsMap(metadata["xworkmateTaskArtifactContract"]) + expectedDirs := normalizeOpenClawDirList(shared.ListArg(contract, "expectedArtifactDirs")) complex := taskLoadClass == "complex_long_chain_task" || isOpenClawLongArtifactTask(lowerMessage) return openClawArtifactContract{ TaskLoadClass: taskLoadClass, @@ -755,17 +756,6 @@ func openClawChatSendParamsWithSessionKey( "message": message, "idempotencyKey": turnID, } - if expectedDirs, ok := params["expectedArtifactDirs"]; ok { - chatParams["expectedArtifactDirs"] = expectedDirs - } else if metadata := shared.AsMap(params["metadata"]); len(metadata) > 0 { - if expectedDirs, ok := metadata["expectedArtifactDirs"]; ok { - chatParams["expectedArtifactDirs"] = expectedDirs - } else if contract := shared.AsMap(metadata["xworkmateTaskArtifactContract"]); len(contract) > 0 { - if expectedDirs, ok := contract["expectedArtifactDirs"]; ok { - chatParams["expectedArtifactDirs"] = expectedDirs - } - } - } attachments := openClawNonEmptyPathAttachments(params) inlineAttachments, rpcErr := materializeOpenClawInlineAttachments(params, turnID) if rpcErr != nil { @@ -1194,6 +1184,7 @@ func fallbackOpenClawSessionKey(params map[string]any, turnID string) string { func (o *SessionOrchestrator) openClawArtifactExport( gatewayProvider string, chatParams map[string]any, + artifactContract openClawArtifactContract, runID string, sinceUnixMs int64, preparedArtifact *openClawPreparedArtifactScope, @@ -1214,8 +1205,8 @@ func (o *SessionOrchestrator) openClawArtifactExport( if preparedArtifact != nil && strings.TrimSpace(preparedArtifact.ArtifactScope) != "" { exportParams["artifactScope"] = strings.TrimSpace(preparedArtifact.ArtifactScope) } - if expectedDirs, ok := chatParams["expectedArtifactDirs"]; ok { - exportParams["expectedArtifactDirs"] = expectedDirs + if len(artifactContract.ExpectedArtifactDirs) > 0 { + exportParams["expectedArtifactDirs"] = append([]string(nil), artifactContract.ExpectedArtifactDirs...) } payload := o.openClawArtifactExportRequest(gatewayProvider, exportParams, notify) return payload @@ -1224,6 +1215,7 @@ func (o *SessionOrchestrator) openClawArtifactExport( func (o *SessionOrchestrator) openClawArtifactCollectAndSnapshot( gatewayProvider string, chatParams map[string]any, + artifactContract openClawArtifactContract, runID string, sinceUnixMs int64, preparedArtifact *openClawPreparedArtifactScope, @@ -1242,8 +1234,8 @@ func (o *SessionOrchestrator) openClawArtifactCollectAndSnapshot( if strings.TrimSpace(preparedArtifact.ArtifactScope) != "" { snapshotParams["artifactScope"] = strings.TrimSpace(preparedArtifact.ArtifactScope) } - if expectedDirs, ok := chatParams["expectedArtifactDirs"]; ok { - snapshotParams["expectedArtifactDirs"] = expectedDirs + if len(artifactContract.ExpectedArtifactDirs) > 0 { + snapshotParams["expectedArtifactDirs"] = append([]string(nil), artifactContract.ExpectedArtifactDirs...) } snapshotResult := o.openClawGatewayRequestWithRetry( gatewayProvider, @@ -1802,9 +1794,11 @@ func (o *SessionOrchestrator) completeOpenClawScopedArtifactExport( sessionKey := o.openClawSessionKey(params, turnID) runID := strings.TrimSpace(shared.StringArg(result, "runId", turnID)) chatParams := map[string]any{"sessionKey": sessionKey} + artifactContract := openClawArtifactContractForParams(params, chatParams) mergeOpenClawArtifactPayload(result, o.openClawArtifactExport( gatewayProvider, chatParams, + artifactContract, runID, 0, preparedArtifact, diff --git a/internal/acp/routing_test.go b/internal/acp/routing_test.go index fe7cb47..0e4e3a3 100644 --- a/internal/acp/routing_test.go +++ b/internal/acp/routing_test.go @@ -492,6 +492,11 @@ func TestExecuteSessionTaskGatewayAutoConnectsLocalOpenClaw(t *testing.T) { "threadId": "thread-openclaw", "taskPrompt": "say pong", "workingDirectory": t.TempDir(), + "metadata": map[string]any{ + "xworkmateTaskArtifactContract": map[string]any{ + "expectedArtifactDirs": []any{"assets/images/", "reports/"}, + }, + }, "routing": map[string]any{ "routingMode": "explicit", "explicitExecutionTarget": "gateway", @@ -527,6 +532,7 @@ func TestExecuteSessionTaskGatewayAutoConnectsLocalOpenClaw(t *testing.T) { "remoteWorkingDirectory", "remoteWorkspaceRefKind", "xworkmateArtifacts", + "expectedArtifactDirs", } { if _, ok := chatParams[key]; ok { t.Fatalf("expected chat.send params to omit bridge artifact/workspace field %q, got %#v", key, chatParams) @@ -561,6 +567,14 @@ func TestExecuteSessionTaskGatewayAutoConnectsLocalOpenClaw(t *testing.T) { if gateway.ArtifactExportCount() != 1 { t.Fatalf("expected one OpenClaw artifact export sync after run, got %d", gateway.ArtifactExportCount()) } + exportParams := gateway.LastArtifactExportParams() + if got := shared.ListArg(exportParams, "expectedArtifactDirs"); !sameAnyStringSlice(got, []string{"assets/images/", "reports/"}) { + t.Fatalf("expected artifact export to receive expectedArtifactDirs from contract, got %#v", exportParams) + } + snapshotParams := gateway.LastArtifactSnapshotParams() + if got := shared.ListArg(snapshotParams, "expectedArtifactDirs"); !sameAnyStringSlice(got, []string{"assets/images/", "reports/"}) { + t.Fatalf("expected artifact snapshot to receive expectedArtifactDirs from contract, got %#v", snapshotParams) + } 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) } @@ -3307,6 +3321,18 @@ func sameMethods(got []string, want []string) bool { return true } +func sameAnyStringSlice(got []any, want []string) bool { + if len(got) != len(want) { + return false + } + for index := range got { + if fmt.Sprint(got[index]) != want[index] { + return false + } + } + return true +} + func waitForCondition(t *testing.T, condition func() bool) { t.Helper() deadline := time.Now().Add(5 * time.Second) diff --git a/scripts/github-actions/validate-openclaw-session.sh b/scripts/github-actions/validate-openclaw-session.sh index 1dd4935..1155b4f 100755 --- a/scripts/github-actions/validate-openclaw-session.sh +++ b/scripts/github-actions/validate-openclaw-session.sh @@ -3,6 +3,7 @@ set -euo pipefail BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}" AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}" +REQUEST_ORIGIN="${OPENCLAW_SMOKE_ORIGIN:-https://xworkmate.svc.plus}" RPC_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_RPC_TIMEOUT_SECONDS:-180}" POLL_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_POLL_TIMEOUT_SECONDS:-120}" POLL_INTERVAL_SECONDS="${OPENCLAW_SMOKE_POLL_INTERVAL_SECONDS:-2}" @@ -49,7 +50,7 @@ echo "OpenClaw smoke -> POST ${rpc_url} (session=${session_id})" curl --http1.1 --fail --silent --show-error --no-buffer --max-time "${RPC_TIMEOUT_SECONDS}" \ -H "Authorization: Bearer ${AUTH_TOKEN}" \ - -H "Origin: https://xworkmate-app.svc.plus" \ + -H "Origin: ${REQUEST_ORIGIN}" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ --data "${request_body}" \