Map OpenClaw owner workspaces to writable root

This commit is contained in:
Haitao Pan 2026-05-26 13:54:44 +08:00
parent 66c686bc43
commit 85fe904352
2 changed files with 99 additions and 0 deletions

View File

@ -320,6 +320,7 @@ func (o *SessionOrchestrator) runOpenClawGatewayChat(
}
}
sessionKey := openClawSessionKey(params, turnID)
params = withOpenClawWritableWorkspace(params, sessionKey)
chatParams, rpcErr := openClawChatSendParams(params, turnID)
if rpcErr != nil {
return nil, rpcErr
@ -649,6 +650,61 @@ func openClawChatSendParams(
return chatParams, nil
}
func withOpenClawWritableWorkspace(params map[string]any, sessionKey string) map[string]any {
workingDirectory := strings.TrimSpace(shared.StringArg(params, "workingDirectory", ""))
remoteHint := strings.TrimSpace(shared.StringArg(params, "remoteWorkingDirectoryHint", ""))
ownerScoped := firstOwnerScopedWorkspace(workingDirectory, remoteHint)
if ownerScoped == "" {
return params
}
writable := openClawWritableWorkspaceForOwnerPath(ownerScoped, sessionKey)
if writable == "" || writable == ownerScoped {
return params
}
next := make(map[string]any, len(params)+1)
for key, value := range params {
next[key] = value
}
if workingDirectory == ownerScoped {
next["workingDirectory"] = writable
}
if remoteHint == ownerScoped {
next["remoteWorkingDirectoryHint"] = writable
}
for _, key := range []string{"taskPrompt", "prompt", "message"} {
if value, ok := next[key].(string); ok && strings.Contains(value, ownerScoped) {
next[key] = strings.ReplaceAll(value, ownerScoped, writable)
}
}
return next
}
func firstOwnerScopedWorkspace(values ...string) string {
for _, value := range values {
trimmed := strings.TrimSpace(value)
if strings.HasPrefix(filepath.Clean(trimmed), "/owners/") {
return trimmed
}
}
return ""
}
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"
}
root = strings.TrimRight(filepath.Clean(root), string(os.PathSeparator))
if root == "" || root == "." || root == string(os.PathSeparator) {
return ""
}
leaf := safeOpenClawAttachmentPathSegment(sessionKey, "task")
if leaf == "task" {
leaf = safeOpenClawAttachmentPathSegment(filepath.Base(filepath.Clean(ownerPath)), "task")
}
return filepath.Join(root, leaf)
}
func openClawNonEmptyPathAttachments(params map[string]any) []any {
rawAttachments := shared.ListArg(params, "attachments")
if len(rawAttachments) == 0 {

View File

@ -2066,6 +2066,49 @@ func TestOpenClawChatSendParamsMaterializesInlineAttachmentsInRemoteHint(t *test
}
}
func TestOpenClawChatSendParamsMapsOwnerScopedWorkspaceToWritableRoot(t *testing.T) {
writableRoot := t.TempDir()
t.Setenv("OPENCLAW_WRITABLE_WORKSPACE_ROOT", writableRoot)
ownerWorkspace := "/owners/local/device/demo/threads/draft-1"
params := withOpenClawWritableWorkspace(map[string]any{
"sessionId": "draft-1",
"threadId": "draft-1",
"taskPrompt": "write into currentTaskWorkspace: " + ownerWorkspace,
"workingDirectory": ownerWorkspace,
"remoteWorkingDirectoryHint": ownerWorkspace,
"inlineAttachments": []any{
map[string]any{
"name": "note.txt",
"mimeType": "text/plain",
"content": base64.StdEncoding.EncodeToString([]byte("note body")),
},
},
}, "draft-1")
chatParams, rpcErr := openClawChatSendParams(params, "turn-owner-workspace")
if rpcErr != nil {
t.Fatalf("expected chat params, got rpc error: %#v", rpcErr)
}
writableWorkspace := filepath.Join(writableRoot, "draft-1")
if got := shared.StringArg(params, "workingDirectory", ""); got != writableWorkspace {
t.Fatalf("expected writable working directory %q, got %q", writableWorkspace, got)
}
if got := shared.StringArg(params, "remoteWorkingDirectoryHint", ""); got != writableWorkspace {
t.Fatalf("expected writable remote hint %q, got %q", writableWorkspace, got)
}
message := shared.StringArg(chatParams, "message", "")
if strings.Contains(message, "/owners/") {
t.Fatalf("message must not reference owner-scoped workspace, got %q", message)
}
if !strings.Contains(message, writableWorkspace) {
t.Fatalf("message should reference writable workspace %q, got %q", writableWorkspace, message)
}
path := shared.StringArg(shared.AsMap(shared.ListArg(chatParams, "attachments")[0]), "path", "")
if !strings.HasPrefix(path, writableWorkspace) {
t.Fatalf("expected materialized attachment under writable workspace %q, got %q", writableWorkspace, path)
}
}
func TestExecuteSessionTaskGatewayRejectsOversizedInlineAttachmentBeforeChatSend(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)
defer gateway.Close()