Merge release/v1.1.4 OpenClaw artifact boundary fix

This commit is contained in:
Haitao Pan 2026-06-05 21:23:56 +08:00
commit eb62488dbc
4 changed files with 40 additions and 17 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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}" \