fix openclaw artifact workspace resolution

This commit is contained in:
Haitao Pan 2026-06-06 12:17:27 +08:00
parent 9180e9258d
commit 9de1e70687
2 changed files with 143 additions and 11 deletions

View File

@ -323,11 +323,7 @@ func (o *SessionOrchestrator) startOpenClawGatewayTask(
}
sessionKey := o.openClawSessionKey(params, turnID)
params = withOpenClawWritableWorkspace(params, openClawAppThreadKey(params))
chatParams, rpcErr := openClawChatSendParamsWithSessionKey(params, turnID, sessionKey)
if rpcErr != nil {
return nil, rpcErr
}
artifactContract := openClawArtifactContractForParams(params, chatParams)
artifactContract := openClawArtifactContractForParams(params, nil)
preparedArtifact, prepareErr := o.openClawArtifactPrepare(
gatewayProvider,
params,
@ -340,6 +336,11 @@ func (o *SessionOrchestrator) startOpenClawGatewayTask(
return nil, prepareErr
}
logOpenClawArtifactSync(gatewayProvider, sessionKey, turnID, "prepare", true, false, false)
params = withOpenClawPreparedArtifactWorkspace(params, preparedArtifact)
chatParams, rpcErr := openClawChatSendParamsWithSessionKey(params, turnID, sessionKey)
if rpcErr != nil {
return nil, rpcErr
}
applyOpenClawPreparedArtifactToChatParams(chatParams, preparedArtifact, sessionKey, turnID, artifactContract)
sendStarted := time.Now()
sendResult := o.openClawGatewayRequestWithRetry(
@ -623,14 +624,14 @@ func openClawSessionPrepareParams(params map[string]any, openClawSessionKey stri
}
func openClawArtifactWorkspaceDir(params map[string]any) string {
for _, key := range []string{"workspaceDir", "remoteWorkingDirectoryHint", "remoteWorkingDirectory"} {
if value := strings.TrimSpace(shared.StringArg(params, key, "")); value != "" {
if value := strings.TrimSpace(shared.StringArg(params, "workspaceDir", "")); value != "" {
return value
}
for _, key := range []string{"remoteWorkingDirectoryHint", "remoteWorkingDirectory", "workingDirectory"} {
if value := strings.TrimSpace(shared.StringArg(params, key, "")); isOpenClawWorkspacePath(value) {
return value
}
}
if workingDirectory := strings.TrimSpace(shared.StringArg(params, "workingDirectory", "")); isOpenClawWorkspacePath(workingDirectory) {
return workingDirectory
}
if configured := strings.TrimSpace(os.Getenv("OPENCLAW_WORKSPACE_DIR")); configured != "" {
return configured
}
@ -880,6 +881,66 @@ func withOpenClawWritableWorkspace(params map[string]any, appThreadKey string) m
return next
}
func withOpenClawPreparedArtifactWorkspace(params map[string]any, prepared *openClawPreparedArtifactScope) map[string]any {
if prepared == nil {
return params
}
artifactDirectory := strings.TrimSpace(prepared.ArtifactDirectory)
if artifactDirectory == "" {
return params
}
replacements := openClawWorkspacePromptReplacementValues(params)
next := make(map[string]any, len(params)+2)
for key, value := range params {
next[key] = value
}
next["workingDirectory"] = artifactDirectory
next["remoteWorkingDirectoryHint"] = artifactDirectory
for _, key := range []string{"taskPrompt", "prompt", "message"} {
value, ok := next[key].(string)
if !ok || strings.TrimSpace(value) == "" {
continue
}
next[key] = rewriteOpenClawWorkspaceReferences(value, artifactDirectory, replacements)
}
return next
}
func openClawWorkspacePromptReplacementValues(params map[string]any) []string {
values := []string{
shared.StringArg(params, "workingDirectory", ""),
shared.StringArg(params, "remoteWorkingDirectoryHint", ""),
shared.StringArg(params, "remoteWorkingDirectory", ""),
}
metadata := shared.AsMap(params["metadata"])
contract := shared.AsMap(metadata["xworkmateTaskArtifactContract"])
values = append(values,
shared.StringArg(contract, "currentTaskWorkspace", ""),
shared.StringArg(contract, "remoteWorkspaceHint", ""),
)
result := make([]string, 0, len(values))
seen := map[string]bool{}
for _, value := range values {
trimmed := strings.TrimSpace(value)
if trimmed == "" || seen[trimmed] {
continue
}
seen[trimmed] = true
result = append(result, trimmed)
}
return result
}
func rewriteOpenClawWorkspaceReferences(message string, artifactDirectory string, replacements []string) string {
result := message
for _, value := range replacements {
if value != artifactDirectory {
result = strings.ReplaceAll(result, value, artifactDirectory)
}
}
return result
}
func firstOwnerScopedWorkspace(values ...string) string {
for _, value := range values {
trimmed := strings.TrimSpace(value)
@ -893,7 +954,7 @@ func firstOwnerScopedWorkspace(values ...string) string {
func openClawWritableWorkspaceForOwnerPath(ownerPath string, sessionKey string) string {
root := strings.TrimSpace(os.Getenv("OPENCLAW_WRITABLE_WORKSPACE_ROOT"))
if root == "" {
root = "/home/ubuntu/.openclaw/workspace/task_artifacts"
return ""
}
root = strings.TrimRight(filepath.Clean(root), string(os.PathSeparator))
if root == "" || root == "." || root == string(os.PathSeparator) {

View File

@ -2417,8 +2417,79 @@ func TestOpenClawChatSendParamsMapsOwnerScopedWorkspaceToWritableRoot(t *testing
}
}
func TestOpenClawWritableWorkspaceDoesNotDefaultToLegacyTaskArtifacts(t *testing.T) {
t.Setenv("OPENCLAW_WRITABLE_WORKSPACE_ROOT", "")
params := withOpenClawWritableWorkspace(map[string]any{
"sessionId": "draft-1",
"threadId": "draft-1",
"taskPrompt": "write into /owners/local/device/demo/threads/draft-1",
"workingDirectory": "/owners/local/device/demo/threads/draft-1",
"remoteWorkingDirectoryHint": "/owners/local/device/demo/threads/draft-1",
}, "draft-1")
if got := shared.StringArg(params, "workingDirectory", ""); strings.Contains(got, "task_artifacts") {
t.Fatalf("must not synthesize legacy task_artifacts workspace, got %q", got)
}
if got := shared.StringArg(params, "remoteWorkingDirectoryHint", ""); strings.Contains(got, "task_artifacts") {
t.Fatalf("must not synthesize legacy task_artifacts remote hint, got %q", got)
}
}
func TestOpenClawArtifactWorkspaceDirIgnoresOwnerScopedRemoteHint(t *testing.T) {
t.Setenv("OPENCLAW_WORKSPACE_DIR", "")
got := openClawArtifactWorkspaceDir(map[string]any{
"workingDirectory": "/owners/local/device/demo/threads/draft-1",
"remoteWorkingDirectoryHint": "/owners/local/device/demo/threads/draft-1",
})
if got != "~/.openclaw/workspace" {
t.Fatalf("expected default OpenClaw workspace root, got %q", got)
}
}
func TestOpenClawPreparedArtifactWorkspaceRewritesOwnerPromptToArtifactDirectory(t *testing.T) {
ownerWorkspace := "/owners/local/device/demo/threads/draft-1"
artifactDirectory := "/home/ubuntu/.openclaw/workspace/tasks/agent-main-draft-1/turn-1"
params := withOpenClawPreparedArtifactWorkspace(map[string]any{
"taskPrompt": "" +
"TaskThread workspace context:\n" +
"- remoteWorkspaceHint: " + ownerWorkspace + "\n" +
"- currentTaskWorkspace: " + ownerWorkspace + "\n" +
"User request:\nwrite a report",
"workingDirectory": ownerWorkspace,
"remoteWorkingDirectoryHint": ownerWorkspace,
"metadata": map[string]any{
"xworkmateTaskArtifactContract": map[string]any{
"currentTaskWorkspace": ownerWorkspace,
"remoteWorkspaceHint": ownerWorkspace,
},
},
}, &openClawPreparedArtifactScope{
ArtifactDirectory: artifactDirectory,
ArtifactScope: "tasks/agent-main-draft-1/turn-1",
})
if got := shared.StringArg(params, "workingDirectory", ""); got != artifactDirectory {
t.Fatalf("expected workingDirectory rewritten to artifact directory, got %q", got)
}
if got := shared.StringArg(params, "remoteWorkingDirectoryHint", ""); got != artifactDirectory {
t.Fatalf("expected remote hint rewritten to artifact directory, got %q", got)
}
message := shared.StringArg(params, "taskPrompt", "")
if strings.Contains(message, ownerWorkspace) {
t.Fatalf("prompt must not keep owner-scoped workspace, got %q", message)
}
if count := strings.Count(message, artifactDirectory); count != 2 {
t.Fatalf("expected artifact directory to replace both workspace prompt references, count=%d message=%q", count, message)
}
}
func TestExecuteSessionTaskGatewayRejectsOversizedInlineAttachmentBeforeChatSend(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)
gateway.artifactWorkspaceRoot = t.TempDir()
defer gateway.Close()
t.Setenv("GATEWAY_RPC_URL", gateway.URL())