fix: export claimed openclaw artifacts
This commit is contained in:
parent
3932c6bd8f
commit
2a86a19677
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@ -249,18 +250,25 @@ func (o *SessionOrchestrator) runOpenClawGatewayChat(
|
||||
if preparedArtifact == nil {
|
||||
preparedArtifact = openClawPreparedArtifactScopeFromPayload(result)
|
||||
}
|
||||
mergeOpenClawArtifactPayload(result, o.openClawArtifactExportForDelivery(
|
||||
artifactDeliveryClaimed := !artifactDeliveryRequired &&
|
||||
openClawArtifactDeliveryRequired(map[string]any{"message": output})
|
||||
artifactPayload := o.openClawArtifactExportForDelivery(
|
||||
gatewayProvider,
|
||||
chatParams,
|
||||
artifactRunID,
|
||||
artifactSinceUnixMs,
|
||||
preparedArtifact,
|
||||
artifactDeliveryRequired || preparedArtifact != nil,
|
||||
artifactDeliveryRequired || artifactDeliveryClaimed || preparedArtifact != nil,
|
||||
artifactDeliveryClaimed,
|
||||
notifyWithCollection,
|
||||
))
|
||||
)
|
||||
if artifactDeliveryClaimed {
|
||||
artifactPayload = filterOpenClawArtifactPayloadByOutput(output, artifactPayload)
|
||||
}
|
||||
mergeOpenClawArtifactPayload(result, artifactPayload)
|
||||
o.server.decorateOpenClawArtifactDownloadURLs(result, shared.StringArg(chatParams, "sessionKey", ""), artifactRunID)
|
||||
stripOpenClawArtifactInlineContent(result)
|
||||
guardOpenClawArtifactResult(result, artifactDeliveryRequired)
|
||||
guardOpenClawArtifactResult(result, artifactDeliveryRequired || artifactDeliveryClaimed)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@ -271,6 +279,7 @@ func (o *SessionOrchestrator) openClawArtifactExportForDelivery(
|
||||
sinceUnixMs int64,
|
||||
preparedArtifact *openClawPreparedArtifactScope,
|
||||
artifactDeliveryRequired bool,
|
||||
latestTaskScopeIfEmpty bool,
|
||||
notify func(map[string]any),
|
||||
) map[string]any {
|
||||
if !artifactDeliveryRequired {
|
||||
@ -281,6 +290,7 @@ func (o *SessionOrchestrator) openClawArtifactExportForDelivery(
|
||||
sinceUnixMs,
|
||||
preparedArtifact,
|
||||
false,
|
||||
false,
|
||||
notify,
|
||||
)
|
||||
}
|
||||
@ -294,6 +304,7 @@ func (o *SessionOrchestrator) openClawArtifactExportForDelivery(
|
||||
sinceUnixMs,
|
||||
preparedArtifact,
|
||||
true,
|
||||
latestTaskScopeIfEmpty,
|
||||
notify,
|
||||
)
|
||||
remoteWorkingDirectory := strings.TrimSpace(shared.StringArg(payload, "remoteWorkingDirectory", ""))
|
||||
@ -518,6 +529,7 @@ func (o *SessionOrchestrator) openClawArtifactExport(
|
||||
sinceUnixMs int64,
|
||||
preparedArtifact *openClawPreparedArtifactScope,
|
||||
latestIfEmpty bool,
|
||||
latestTaskScopeIfEmpty bool,
|
||||
notify func(map[string]any),
|
||||
) map[string]any {
|
||||
sessionKey := strings.TrimSpace(shared.StringArg(chatParams, "sessionKey", ""))
|
||||
@ -538,6 +550,9 @@ func (o *SessionOrchestrator) openClawArtifactExport(
|
||||
if latestIfEmpty {
|
||||
exportParams["latestIfEmpty"] = true
|
||||
}
|
||||
if latestTaskScopeIfEmpty {
|
||||
exportParams["latestTaskScopeIfEmpty"] = true
|
||||
}
|
||||
exportResult := o.server.gateway.RequestByMode(
|
||||
gatewayProvider,
|
||||
"xworkmate.artifacts.export",
|
||||
@ -577,6 +592,58 @@ func guardOpenClawArtifactResult(result map[string]any, artifactDeliveryRequired
|
||||
)
|
||||
}
|
||||
|
||||
func filterOpenClawArtifactPayloadByOutput(output string, payload map[string]any) map[string]any {
|
||||
if payload == nil {
|
||||
return nil
|
||||
}
|
||||
output = strings.ToLower(output)
|
||||
if strings.TrimSpace(output) == "" {
|
||||
return payload
|
||||
}
|
||||
filtered := map[string]any{}
|
||||
for key, value := range payload {
|
||||
filtered[key] = value
|
||||
}
|
||||
matchedAny := false
|
||||
for _, key := range []string{"artifacts", "files", "attachments"} {
|
||||
list := shared.ListArg(payload, key)
|
||||
if len(list) == 0 {
|
||||
continue
|
||||
}
|
||||
filteredList := make([]any, 0, len(list))
|
||||
for _, item := range list {
|
||||
artifact := shared.AsMap(item)
|
||||
relativePath := strings.TrimSpace(shared.StringArg(artifact, "relativePath", ""))
|
||||
if relativePath == "" {
|
||||
relativePath = strings.TrimSpace(shared.StringArg(artifact, "path", ""))
|
||||
}
|
||||
if relativePath == "" {
|
||||
relativePath = strings.TrimSpace(shared.StringArg(artifact, "name", ""))
|
||||
}
|
||||
if openClawOutputMentionsArtifactPath(output, relativePath) {
|
||||
filteredList = append(filteredList, item)
|
||||
matchedAny = true
|
||||
}
|
||||
}
|
||||
filtered[key] = filteredList
|
||||
}
|
||||
if !matchedAny {
|
||||
return payload
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func openClawOutputMentionsArtifactPath(output string, relativePath string) bool {
|
||||
relativePath = strings.TrimSpace(strings.ReplaceAll(relativePath, "\\", "/"))
|
||||
if relativePath == "" {
|
||||
return false
|
||||
}
|
||||
normalizedPath := strings.ToLower(relativePath)
|
||||
base := strings.ToLower(path.Base(normalizedPath))
|
||||
return strings.Contains(output, normalizedPath) ||
|
||||
(base != "." && base != "" && strings.Contains(output, base))
|
||||
}
|
||||
|
||||
func mergeOpenClawArtifactPayload(result map[string]any, source map[string]any) {
|
||||
if result == nil || len(source) == 0 {
|
||||
return
|
||||
@ -901,6 +968,7 @@ func (o *SessionOrchestrator) completeOpenClawScopedArtifactExport(
|
||||
0,
|
||||
preparedArtifact,
|
||||
true,
|
||||
false,
|
||||
nil,
|
||||
))
|
||||
o.server.decorateOpenClawArtifactDownloadURLs(result, sessionKey, runID)
|
||||
|
||||
@ -800,6 +800,97 @@ func TestExecuteSessionTaskGatewayExportsLatestWorkspaceArtifactsWhenScopedDirec
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteSessionMessageGatewayExportsClaimedOpenClawArtifacts(t *testing.T) {
|
||||
gateway := newAcpFakeOpenClawGateway(t)
|
||||
gateway.artifactMode = "workspace-latest"
|
||||
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.message",
|
||||
Params: map[string]any{
|
||||
"sessionId": "session-openclaw-claimed-artifact",
|
||||
"threadId": "thread-openclaw-claimed-artifact",
|
||||
"taskPrompt": "hi hallucinate-files",
|
||||
"workingDirectory": t.TempDir(),
|
||||
"routing": map[string]any{
|
||||
"routingMode": "explicit",
|
||||
"explicitExecutionTarget": "gateway",
|
||||
"preferredGatewayProviderId": "openclaw",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if rpcErr != nil {
|
||||
t.Fatalf("expected claimed artifact response, got rpc error: %#v", rpcErr)
|
||||
}
|
||||
if got := response["success"]; got != true {
|
||||
t.Fatalf("expected successful claimed artifact response, got %#v", response)
|
||||
}
|
||||
artifacts, ok := response["artifacts"].([]map[string]any)
|
||||
if !ok {
|
||||
raw, ok := response["artifacts"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected artifacts payload, got %#v", response["artifacts"])
|
||||
}
|
||||
artifacts = make([]map[string]any, 0, len(raw))
|
||||
for _, item := range raw {
|
||||
artifacts = append(artifacts, shared.AsMap(item))
|
||||
}
|
||||
}
|
||||
if len(artifacts) != 1 {
|
||||
t.Fatalf("expected one claimed artifact, got %#v", artifacts)
|
||||
}
|
||||
if got := artifacts[0]["relativePath"]; got != "existing/report.pdf" {
|
||||
t.Fatalf("expected claimed latest artifact relative path, got %#v", artifacts[0])
|
||||
}
|
||||
if got := strings.TrimSpace(shared.StringArg(artifacts[0], "downloadUrl", "")); got == "" {
|
||||
t.Fatalf("expected bridge downloadUrl on claimed artifact, got %#v", artifacts[0])
|
||||
}
|
||||
exportParams := gateway.LastArtifactExportParams()
|
||||
if got := shared.BoolArg(shared.StringArg(exportParams, "latestIfEmpty", ""), false); !got {
|
||||
t.Fatalf("expected latestIfEmpty export param, got %#v", exportParams)
|
||||
}
|
||||
if got := shared.BoolArg(shared.StringArg(exportParams, "latestTaskScopeIfEmpty", ""), false); !got {
|
||||
t.Fatalf("expected latestTaskScopeIfEmpty export param, got %#v", exportParams)
|
||||
}
|
||||
if _, ok := exportParams["artifactScope"]; ok {
|
||||
t.Fatalf("expected no new prepared artifact scope for claimed follow-up, got %#v", exportParams)
|
||||
}
|
||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
||||
t.Fatalf("expected connect, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOpenClawArtifactPayloadByOutputKeepsMentionedFiles(t *testing.T) {
|
||||
payload := map[string]any{
|
||||
"remoteWorkingDirectory": "/remote/openclaw/workspace",
|
||||
"artifacts": []any{
|
||||
map[string]any{"relativePath": "k8s-networking.pdf"},
|
||||
map[string]any{"relativePath": "k8s-networking.docx"},
|
||||
map[string]any{"relativePath": "generate_all.py"},
|
||||
},
|
||||
}
|
||||
filtered := filterOpenClawArtifactPayloadByOutput(
|
||||
"文件已经生成好了:k8s-networking.pdf, k8s-networking.docx",
|
||||
payload,
|
||||
)
|
||||
artifacts := shared.ListArg(filtered, "artifacts")
|
||||
if len(artifacts) != 2 {
|
||||
t.Fatalf("expected only mentioned artifacts, got %#v", artifacts)
|
||||
}
|
||||
if got := shared.StringArg(shared.AsMap(artifacts[0]), "relativePath", ""); got != "k8s-networking.pdf" {
|
||||
t.Fatalf("expected pdf artifact first, got %#v", artifacts)
|
||||
}
|
||||
if got := shared.StringArg(shared.AsMap(artifacts[1]), "relativePath", ""); got != "k8s-networking.docx" {
|
||||
t.Fatalf("expected docx artifact second, got %#v", artifacts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeResultStripsOpenClawInlineArtifactsAfterRecordNormalization(t *testing.T) {
|
||||
server := NewServer()
|
||||
orchestrator := NewSessionOrchestrator(server)
|
||||
@ -1729,19 +1820,26 @@ func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway {
|
||||
}
|
||||
if fake.artifactMode == "workspace-latest" &&
|
||||
shared.BoolArg(shared.StringArg(params, "latestIfEmpty", ""), false) &&
|
||||
artifactScope != "" &&
|
||||
len(payload["artifacts"].([]any)) == 0 {
|
||||
if artifactScope == "" &&
|
||||
!shared.BoolArg(shared.StringArg(params, "latestTaskScopeIfEmpty", ""), false) {
|
||||
break
|
||||
}
|
||||
if artifactScope == "" {
|
||||
artifactScope = "tasks/" + strings.TrimSpace(shared.StringArg(params, "sessionKey", "main")) + "/previous-run"
|
||||
}
|
||||
payload["scopeKind"] = "workspace-latest"
|
||||
payload["artifacts"] = []any{
|
||||
map[string]any{
|
||||
"relativePath": "existing/report.pdf",
|
||||
"label": "report.pdf",
|
||||
"contentType": "application/pdf",
|
||||
"sizeBytes": 3,
|
||||
"sha256": "latest-sha256",
|
||||
"scopeKind": "workspace-latest",
|
||||
"encoding": "base64",
|
||||
"content": "cGRm",
|
||||
"relativePath": "existing/report.pdf",
|
||||
"label": "report.pdf",
|
||||
"contentType": "application/pdf",
|
||||
"sizeBytes": 3,
|
||||
"sha256": "latest-sha256",
|
||||
"artifactScope": artifactScope,
|
||||
"scopeKind": "workspace-latest",
|
||||
"encoding": "base64",
|
||||
"content": "cGRm",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user