Files
Duc Nguyenandntduc bb7712a9ff fix(collaboration): harden delegated task isolation (#1486)
* feat(collaboration): isolate delegated artifacts and child runs

Isolate delegated inputs and outputs behind secure artifact exchange lifecycles. Scope Agent Link tasks by tenant and root agent, and enforce delegation spawn-tree boundaries. Add process-wide child-run admission and preserve logical media paths across native, MCP, and sandbox execution.

* fix(collaboration): harden delegated task isolation

Enforce tenant and root-agent task scope across migrations and stores. Add exactly-once async completion delivery, delegated sandbox boundaries, and confined artifact and media recovery across runtime surfaces.

* fix(collaboration): recover interrupted async tasks

* fix(collaboration): normalize persisted child-run status

---------

Co-authored-by: ntduc <ntduc@cpp.ai.vn>
2026-07-30 14:17:40 +07:00

105 lines
3.4 KiB
Go

// Package workspace provides unified workspace resolution for agent runs.
// Replaces dual ctxWorkspace + ctxTeamWorkspace with a single immutable context.
//
// V3 design: Phase 1B — foundation interface.
package workspace
import "context"
// Scope defines workspace access boundary.
type Scope string
const (
ScopePersonal Scope = "personal" // single user, isolated
ScopeTeam Scope = "team" // team context, shared or isolated
ScopeDelegate Scope = "delegate" // delegated task, scoped access
)
// WorkspaceContext is resolved ONCE at run start, immutable for the entire run.
// Eliminates dual ctxWorkspace + ctxTeamWorkspace confusion.
type WorkspaceContext struct {
// ActivePath is THE path for all file operations (read/write/list/exec).
ActivePath string
// Scope describes the access boundary type.
Scope Scope
// TeamPath is the team workspace root (nil if not in team context).
TeamPath *string
// MemoryScope determines memory isolation.
// Defaults to workspace scope. "shared" = all users in agent see same memory.
MemoryScope string
// KGScope determines knowledge graph isolation.
KGScope string
// OwnerID identifies who owns this workspace context (user ID or chat ID).
OwnerID string
// EnforcementLabel is injected into system prompt verbatim.
EnforcementLabel string
}
// Resolver produces a WorkspaceContext from request parameters.
// Called once at ContextStage. Result is immutable.
type Resolver interface {
Resolve(ctx context.Context, params ResolveParams) (*WorkspaceContext, error)
}
// ResolveParams captures all inputs needed to determine workspace.
type ResolveParams struct {
AgentID string
AgentType string // "open" | "predefined"
UserID string
ChatID string
TenantID string
TenantSlug string // human-readable tenant name for path composition
PeerKind string // "direct" | "group"
TeamID *string
TeamConfig *TeamWorkspaceConfig
BaseDir string
}
// TeamWorkspaceConfig maps to team.settings JSON.
// WorkspaceScope uses "shared"/"isolated" string to match existing DB schema.
type TeamWorkspaceConfig struct {
WorkspaceScope string `json:"workspace_scope"`
WorkspacePath string `json:"workspace_path,omitempty"`
}
// IsShared returns true when workspace_scope is "shared".
func (c *TeamWorkspaceConfig) IsShared() bool {
return c != nil && c.WorkspaceScope == "shared"
}
// DefaultEnforcementLabel returns a human-readable workspace description
// for system prompt injection based on scope and sharing mode.
func DefaultEnforcementLabel(scope Scope, shared bool) string {
switch scope {
case ScopeDelegate:
return "You are working on a delegated task. Only access files in your designated workspace."
case ScopeTeam:
if shared {
return "You are working in a shared team workspace. Other members can see your files."
}
return "You are working in an isolated team workspace."
default:
return "You are working in the user's personal workspace."
}
}
// context key for WorkspaceContext propagation.
type ctxKeyWorkspace struct{}
// FromContext extracts WorkspaceContext from context.
func FromContext(ctx context.Context) *WorkspaceContext {
wc, _ := ctx.Value(ctxKeyWorkspace{}).(*WorkspaceContext)
return wc
}
// WithContext stores WorkspaceContext in context.
func WithContext(ctx context.Context, wc *WorkspaceContext) context.Context {
return context.WithValue(ctx, ctxKeyWorkspace{}, wc)
}