Files
goclaw/internal/edition/edition.go
T
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

106 lines
3.6 KiB
Go

// Package edition defines feature tiers for GoClaw.
// Set once at startup via SetCurrent(), read everywhere via Current().
// Adding a new edition = add one preset struct. No code changes elsewhere.
package edition
import "sync/atomic"
// Edition defines the feature limits for a GoClaw instance.
type Edition struct {
Name string `json:"name"` // "standard" or "lite"
MaxAgents int `json:"max_agents"` // 0 = unlimited
MaxTeams int `json:"max_teams"` // 0 = unlimited
MaxTeamMembers int `json:"max_team_members"` // 0 = unlimited
MaxChannels map[string]int `json:"max_channels"` // per channel type, nil = unlimited
MaxSubagentConcurrent int `json:"max_subagent_concurrent"` // 0 = unlimited
MaxSubagentDepth int `json:"max_subagent_depth"` // 0 = use config default
KGEnabled bool `json:"kg_enabled"`
RBACEnabled bool `json:"rbac_enabled"`
TeamFullMode bool `json:"team_full_mode"` // false = lite task actions only
VectorSearch bool `json:"vector_search"` // false = FTS5 only
SupportsPipNpm bool `json:"supports_pip_npm"` // false for Lite desktop
SupportsApk bool `json:"supports_apk"` // false for Lite desktop (no apk on macOS/Windows)
}
// --- Presets ---
// Standard is the default edition: all features enabled, no limits.
var Standard = Edition{
Name: "standard",
KGEnabled: true,
RBACEnabled: true,
TeamFullMode: true,
VectorSearch: true,
SupportsPipNpm: true,
SupportsApk: true,
}
// Lite is the desktop/self-hosted edition with sensible limits.
var Lite = Edition{
Name: "lite",
MaxAgents: 5,
MaxTeams: 1,
MaxTeamMembers: 5,
MaxChannels: map[string]int{"telegram": 1, "discord": 1},
MaxSubagentConcurrent: 2,
MaxSubagentDepth: 1,
KGEnabled: false,
RBACEnabled: false,
TeamFullMode: false,
VectorSearch: false,
SupportsPipNpm: false,
SupportsApk: false,
}
// --- Global state ---
// current holds the active edition. Atomic pointer for safe concurrent reads.
var current atomic.Pointer[Edition]
func init() {
std := Standard
current.Store(&std)
}
// Current returns the active edition. Safe for concurrent use.
func Current() Edition {
return *current.Load()
}
// SetCurrent sets the active edition. Call once at startup.
func SetCurrent(e Edition) {
current.Store(&e)
}
// --- Helpers ---
// IsLimited returns true if the edition enforces resource limits.
func (e Edition) IsLimited() bool {
return e.MaxAgents > 0 || e.MaxTeams > 0
}
// ChannelLimit returns the max instances for a channel type.
// Returns 0 (unlimited) if the channel type is not in MaxChannels.
func (e Edition) ChannelLimit(channelType string) int {
if e.MaxChannels == nil {
return 0
}
return e.MaxChannels[channelType]
}
// AllowsChannels reports whether this edition permits channel-based webhook routes
// (kind="message"). Standard edition allows channels; Lite does not.
func (e Edition) AllowsChannels() bool {
return e.Name == "standard"
}
// ChildRunLimit returns the process-wide cap for executing self-spawn and
// Agent Link child callbacks. It is separate from the per-agent
// maxConcurrent setting and is intentionally not serialized.
func (e Edition) ChildRunLimit() int {
if e.Name == Lite.Name {
return 2
}
return 32
}