feat: Add persistent workspace volumes, improve workspace directory creation robustness, and enhance trace error display.

This commit is contained in:
viettranx committed 2026-02-24 22:53:09 +07:00
1 parent 9b0dce2aa5
commit 82efb43715
6 files changed
+18 -7

No files matched your search

+1
View File
@@ -32,6 +32,7 @@ services:
- GOCLAW_POSTGRES_DSN=postgres://${POSTGRES_USER:-goclaw}:${POSTGRES_PASSWORD:-goclaw}@postgres:5432/${POSTGRES_DB:-goclaw}?sslmode=disable
volumes:
- goclaw-skills:/app/skills
- goclaw-workspace:/app/.goclaw
volumes:
postgres-data:
+2
View File
@@ -23,6 +23,7 @@ services:
- GOCLAW_CONFIG=/app/data/config.json
volumes:
- goclaw-data:/app/data
- goclaw-workspace:/app/workspace # standalone mode workspace
security_opt:
- no-new-privileges:true
cap_drop:
@@ -40,3 +41,4 @@ services:
volumes:
goclaw-data:
goclaw-workspace:
+3 -1
View File
@@ -341,7 +341,9 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
effectiveWorkspace := l.workspace
if req.UserID != "" {
effectiveWorkspace = filepath.Join(l.workspace, sanitizePathSegment(req.UserID))
os.MkdirAll(effectiveWorkspace, 0755)
if err := os.MkdirAll(effectiveWorkspace, 0755); err != nil {
slog.Warn("failed to create user workspace directory", "workspace", effectiveWorkspace, "user", req.UserID, "error", err)
}
}
ctx = tools.WithToolWorkspace(ctx, effectiveWorkspace)
}
+3 -1
View File
@@ -122,7 +122,9 @@ func NewManagedResolver(deps ResolverDeps) ResolverFunc {
if !filepath.IsAbs(workspace) {
workspace, _ = filepath.Abs(workspace)
}
os.MkdirAll(workspace, 0755)
if err := os.MkdirAll(workspace, 0755); err != nil {
slog.Warn("failed to create agent workspace directory", "workspace", workspace, "agent", agentKey, "error", err)
}
}
// Per-agent custom tools (clone registry if agent has custom tools)
+6 -2
View File
@@ -38,6 +38,9 @@ var contextFileSet = map[string]bool{
// isContextFile checks if a path refers to a workspace-root context file.
// Handles both relative ("SOUL.md") and absolute ("/workspace/SOUL.md") paths.
// Also matches absolute paths under per-user workspace subdirectories
// (e.g. "/workspace/<userID>/USER.md") since context files at any depth
// under the workspace root should be routed to DB in managed mode.
func isContextFile(path, workspace string) (fileName string, ok bool) {
base := filepath.Base(path)
if !contextFileSet[base] {
@@ -50,11 +53,12 @@ func isContextFile(path, workspace string) (fileName string, ok bool) {
return base, true
}
// Absolute path at workspace root: "/path/to/workspace/SOUL.md"
// Absolute path under workspace (includes per-user subdirectories):
// "/workspace/SOUL.md" or "/workspace/<userID>/SOUL.md"
if workspace != "" && filepath.IsAbs(path) {
cleanPath := filepath.Clean(path)
cleanWS := filepath.Clean(workspace)
if filepath.Dir(cleanPath) == cleanWS {
if strings.HasPrefix(filepath.Dir(cleanPath), cleanWS) {
return base, true
}
}
@@ -124,8 +124,8 @@ export function TraceDetailDialog({ traceId, onClose, getTrace }: TraceDetailDia
)}
{trace.error && (
<div className="rounded-md border border-destructive/50 bg-destructive/10 p-3">
<p className="text-sm text-destructive">{trace.error}</p>
<div className="rounded-md border border-red-400/30 bg-red-500/10 p-3">
<p className="break-all text-sm text-red-300">{trace.error}</p>
</div>
)}
@@ -232,7 +232,7 @@ function SpanTreeNode({ node, depth }: { node: SpanNode; depth: number }) {
</div>
)}
{span.error && (
<p className="text-xs text-destructive">{span.error}</p>
<p className="break-all text-xs text-red-300">{span.error}</p>
)}
</div>
)}