mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-05 02:19:44 +00:00
refactor(store): introduce typed RunContext for consolidated context injection
Add store.RunContext struct that consolidates 27 loop-injected context values into a single typed struct. RunContext is built once in loop_context.go and injected via store.WithRunContext(ctx, rc). 24 accessor functions (10 in store/context.go, 14 in tools/context_keys.go) updated with RunContext fallback — check individual key first, then RunContext. Consumers (170+ files) require zero changes. This enables adding new scope fields (e.g. ProjectID) by adding a single struct field instead of a new context key + With/From function pair.
This commit is contained in:
@@ -241,6 +241,42 @@ func (l *Loop) injectContext(ctx context.Context, req *RunRequest) (contextSetup
|
||||
)
|
||||
}
|
||||
|
||||
// Build RunContext from all resolved values and inject as single context key.
|
||||
// This provides a typed, inspectable snapshot of all loop-injected context.
|
||||
// Individual With* keys above remain for backward compat during transition.
|
||||
providerName := ""
|
||||
if l.provider != nil {
|
||||
providerName = l.provider.Name()
|
||||
}
|
||||
rc := &store.RunContext{
|
||||
AgentID: l.agentUUID,
|
||||
AgentKey: l.id,
|
||||
TenantID: l.tenantID,
|
||||
UserID: req.UserID,
|
||||
AgentType: l.agentType,
|
||||
SenderID: req.SenderID,
|
||||
SelfEvolve: l.selfEvolve,
|
||||
SharedMemory: store.IsSharedMemory(ctx),
|
||||
SharedKG: store.IsSharedKG(ctx),
|
||||
RestrictToWorkspace: l.restrictToWs != nil && *l.restrictToWs,
|
||||
BuiltinToolSettings: l.builtinToolSettings,
|
||||
ChannelType: req.ChannelType,
|
||||
SubagentsCfg: l.subagentsCfg,
|
||||
ParentModel: l.model,
|
||||
ParentProvider: providerName,
|
||||
MemoryCfg: l.memoryCfg,
|
||||
SandboxCfg: l.sandboxCfg,
|
||||
ShellDenyGroups: l.shellDenyGroups,
|
||||
Workspace: tools.ToolWorkspaceFromCtx(ctx),
|
||||
TeamWorkspace: tools.ToolTeamWorkspaceFromCtx(ctx),
|
||||
TeamID: tools.ToolTeamIDFromCtx(ctx),
|
||||
WorkspaceChannel: req.WorkspaceChannel,
|
||||
WorkspaceChatID: req.WorkspaceChatID,
|
||||
TeamTaskID: req.TeamTaskID,
|
||||
AgentToolKey: l.id,
|
||||
}
|
||||
ctx = store.WithRunContext(ctx, rc)
|
||||
|
||||
return contextSetupResult{
|
||||
ctx: ctx,
|
||||
resolvedTeamSettings: resolvedTeamSettings,
|
||||
|
||||
+50
-14
@@ -48,8 +48,13 @@ func WithShellDenyGroups(ctx context.Context, groups map[string]bool) context.Co
|
||||
|
||||
// ShellDenyGroupsFromContext returns shell deny group overrides from the context, or nil.
|
||||
func ShellDenyGroupsFromContext(ctx context.Context) map[string]bool {
|
||||
v, _ := ctx.Value(ShellDenyGroupsKey).(map[string]bool)
|
||||
return v
|
||||
if v, _ := ctx.Value(ShellDenyGroupsKey).(map[string]bool); v != nil {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.ShellDenyGroups
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithUserID returns a new context with the given user ID.
|
||||
@@ -59,9 +64,12 @@ func WithUserID(ctx context.Context, id string) context.Context {
|
||||
|
||||
// UserIDFromContext extracts the user ID from context. Returns "" if not set.
|
||||
func UserIDFromContext(ctx context.Context) string {
|
||||
if v, ok := ctx.Value(UserIDKey).(string); ok {
|
||||
if v, ok := ctx.Value(UserIDKey).(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.UserID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -72,9 +80,12 @@ func WithAgentID(ctx context.Context, id uuid.UUID) context.Context {
|
||||
|
||||
// AgentIDFromContext extracts the agent UUID from context. Returns uuid.Nil if not set.
|
||||
func AgentIDFromContext(ctx context.Context) uuid.UUID {
|
||||
if v, ok := ctx.Value(AgentIDKey).(uuid.UUID); ok {
|
||||
if v, ok := ctx.Value(AgentIDKey).(uuid.UUID); ok && v != uuid.Nil {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.AgentID
|
||||
}
|
||||
return uuid.Nil
|
||||
}
|
||||
|
||||
@@ -85,9 +96,12 @@ func WithAgentType(ctx context.Context, t string) context.Context {
|
||||
|
||||
// AgentTypeFromContext extracts the agent type from context. Returns "" if not set.
|
||||
func AgentTypeFromContext(ctx context.Context) string {
|
||||
if v, ok := ctx.Value(AgentTypeKey).(string); ok {
|
||||
if v, ok := ctx.Value(AgentTypeKey).(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.AgentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -98,9 +112,12 @@ func WithAgentKey(ctx context.Context, key string) context.Context {
|
||||
|
||||
// AgentKeyFromContext extracts the agent key from context. Returns "" if not set.
|
||||
func AgentKeyFromContext(ctx context.Context) string {
|
||||
if v, ok := ctx.Value(AgentKeyKey).(string); ok {
|
||||
if v, ok := ctx.Value(AgentKeyKey).(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.AgentKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -111,9 +128,12 @@ func WithSenderID(ctx context.Context, id string) context.Context {
|
||||
|
||||
// SenderIDFromContext extracts the sender ID from context. Returns "" if not set.
|
||||
func SenderIDFromContext(ctx context.Context) string {
|
||||
if v, ok := ctx.Value(SenderIDKey).(string); ok {
|
||||
if v, ok := ctx.Value(SenderIDKey).(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.SenderID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -124,8 +144,11 @@ func WithSelfEvolve(ctx context.Context, v bool) context.Context {
|
||||
|
||||
// SelfEvolveFromContext extracts the self-evolve flag from context. Returns false if not set.
|
||||
func SelfEvolveFromContext(ctx context.Context) bool {
|
||||
if v, ok := ctx.Value(SelfEvolveKey).(bool); ok {
|
||||
return v
|
||||
if v, ok := ctx.Value(SelfEvolveKey).(bool); ok && v {
|
||||
return true
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.SelfEvolve
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -137,8 +160,13 @@ func WithSharedMemory(ctx context.Context) context.Context {
|
||||
|
||||
// IsSharedMemory returns true if memory should be shared across users.
|
||||
func IsSharedMemory(ctx context.Context) bool {
|
||||
v, _ := ctx.Value(SharedMemoryKey).(bool)
|
||||
return v
|
||||
if v, _ := ctx.Value(SharedMemoryKey).(bool); v {
|
||||
return true
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.SharedMemory
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MemoryUserID returns the userID to use for memory operations.
|
||||
@@ -166,8 +194,13 @@ func WithSharedKG(ctx context.Context) context.Context {
|
||||
|
||||
// IsSharedKG returns true if the knowledge graph should be shared across users.
|
||||
func IsSharedKG(ctx context.Context) bool {
|
||||
v, _ := ctx.Value(SharedKGKey).(bool)
|
||||
return v
|
||||
if v, _ := ctx.Value(SharedKGKey).(bool); v {
|
||||
return true
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.SharedKG
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WithLocale returns a new context with the given locale.
|
||||
@@ -191,9 +224,12 @@ func WithTenantID(ctx context.Context, id uuid.UUID) context.Context {
|
||||
// TenantIDFromContext extracts the tenant UUID from context.
|
||||
// Returns uuid.Nil if not set (fail-closed — callers must check).
|
||||
func TenantIDFromContext(ctx context.Context) uuid.UUID {
|
||||
if v, ok := ctx.Value(TenantIDKey).(uuid.UUID); ok {
|
||||
if v, ok := ctx.Value(TenantIDKey).(uuid.UUID); ok && v != uuid.Nil {
|
||||
return v
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.TenantID
|
||||
}
|
||||
return uuid.Nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/config"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/sandbox"
|
||||
)
|
||||
|
||||
// runContextKey is the context key for RunContext.
|
||||
type runContextKey struct{}
|
||||
|
||||
// RunContext consolidates all agent-loop-injected context values into a single
|
||||
// typed struct. This replaces 27 individual context.WithValue calls with one
|
||||
// WithRunContext call, improving readability and making it trivial to add new
|
||||
// scope fields (e.g. ProjectID).
|
||||
//
|
||||
// Consumers can read via RunContextFromCtx() or continue using existing
|
||||
// accessor functions (which fall back to individual keys when RunContext is absent).
|
||||
type RunContext struct {
|
||||
// Identity
|
||||
AgentID uuid.UUID
|
||||
AgentKey string
|
||||
TenantID uuid.UUID
|
||||
UserID string
|
||||
AgentType string
|
||||
SenderID string
|
||||
|
||||
// Flags
|
||||
SelfEvolve bool
|
||||
SharedMemory bool
|
||||
SharedKG bool
|
||||
RestrictToWorkspace bool
|
||||
|
||||
// Tool configuration
|
||||
BuiltinToolSettings map[string][]byte
|
||||
ChannelType string
|
||||
SubagentsCfg *config.SubagentsConfig
|
||||
ParentModel string
|
||||
ParentProvider string
|
||||
MemoryCfg *config.MemoryConfig
|
||||
SandboxCfg *sandbox.Config
|
||||
ShellDenyGroups map[string]bool
|
||||
|
||||
// Workspace
|
||||
Workspace string
|
||||
TeamWorkspace string
|
||||
TeamID string
|
||||
WorkspaceChannel string
|
||||
WorkspaceChatID string
|
||||
TeamTaskID string
|
||||
AgentToolKey string // tool-level agent key for registry routing
|
||||
}
|
||||
|
||||
// WithRunContext stores a RunContext on the context.
|
||||
func WithRunContext(ctx context.Context, rc *RunContext) context.Context {
|
||||
return context.WithValue(ctx, runContextKey{}, rc)
|
||||
}
|
||||
|
||||
// RunContextFromCtx extracts RunContext from context. Returns nil if not set.
|
||||
func RunContextFromCtx(ctx context.Context) *RunContext {
|
||||
rc, _ := ctx.Value(runContextKey{}).(*RunContext)
|
||||
return rc
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/config"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/sandbox"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
)
|
||||
|
||||
// Tool execution context keys.
|
||||
@@ -59,8 +60,13 @@ func WithToolChannelType(ctx context.Context, channelType string) context.Contex
|
||||
}
|
||||
|
||||
func ToolChannelTypeFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxChannelType).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxChannelType).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.ChannelType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func WithToolChatID(ctx context.Context, chatID string) context.Context {
|
||||
@@ -115,8 +121,13 @@ func WithToolWorkspace(ctx context.Context, ws string) context.Context {
|
||||
}
|
||||
|
||||
func ToolWorkspaceFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxWorkspace).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxWorkspace).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.Workspace
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WithToolAgentKey injects the calling agent's key into context.
|
||||
@@ -127,8 +138,13 @@ func WithToolAgentKey(ctx context.Context, key string) context.Context {
|
||||
}
|
||||
|
||||
func ToolAgentKeyFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxAgentKey).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxAgentKey).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.AgentToolKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WithToolSessionKey injects the parent's session key so subagent announce
|
||||
@@ -170,8 +186,13 @@ func WithBuiltinToolSettings(ctx context.Context, settings BuiltinToolSettings)
|
||||
}
|
||||
|
||||
func BuiltinToolSettingsFromCtx(ctx context.Context) BuiltinToolSettings {
|
||||
v, _ := ctx.Value(ctxBuiltinToolSettings).(BuiltinToolSettings)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxBuiltinToolSettings).(BuiltinToolSettings); v != nil {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return BuiltinToolSettings(rc.BuiltinToolSettings)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Per-agent restrict_to_workspace override ---
|
||||
@@ -206,8 +227,13 @@ func WithParentModel(ctx context.Context, model string) context.Context {
|
||||
|
||||
// ParentModelFromCtx returns the parent agent's model from context.
|
||||
func ParentModelFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxParentModel).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxParentModel).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.ParentModel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Parent agent provider (for subagent inheritance) ---
|
||||
@@ -221,8 +247,13 @@ func WithParentProvider(ctx context.Context, providerName string) context.Contex
|
||||
|
||||
// ParentProviderFromCtx returns the parent agent's provider name from context.
|
||||
func ParentProviderFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxParentProvider).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxParentProvider).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.ParentProvider
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Per-agent subagent config override ---
|
||||
@@ -234,8 +265,13 @@ func WithSubagentConfig(ctx context.Context, cfg *config.SubagentsConfig) contex
|
||||
}
|
||||
|
||||
func SubagentConfigFromCtx(ctx context.Context) *config.SubagentsConfig {
|
||||
v, _ := ctx.Value(ctxSubagentCfg).(*config.SubagentsConfig)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxSubagentCfg).(*config.SubagentsConfig); v != nil {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.SubagentsCfg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Per-agent memory config override ---
|
||||
@@ -247,8 +283,13 @@ func WithMemoryConfig(ctx context.Context, cfg *config.MemoryConfig) context.Con
|
||||
}
|
||||
|
||||
func MemoryConfigFromCtx(ctx context.Context) *config.MemoryConfig {
|
||||
v, _ := ctx.Value(ctxMemoryCfg).(*config.MemoryConfig)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxMemoryCfg).(*config.MemoryConfig); v != nil {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.MemoryCfg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Team ID propagation (task dispatch → workspace tools) ---
|
||||
@@ -264,8 +305,13 @@ func WithToolTeamID(ctx context.Context, teamID string) context.Context {
|
||||
|
||||
// ToolTeamIDFromCtx returns the dispatching team's ID from context.
|
||||
func ToolTeamIDFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxTeamID).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxTeamID).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.TeamID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Team workspace path (accessible but not default) ---
|
||||
@@ -280,8 +326,13 @@ func WithToolTeamWorkspace(ctx context.Context, dir string) context.Context {
|
||||
|
||||
// ToolTeamWorkspaceFromCtx returns the team shared workspace directory path.
|
||||
func ToolTeamWorkspaceFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxTeamWorkspace).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxTeamWorkspace).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.TeamWorkspace
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Team task ID propagation (delegation origin → workspace tools) ---
|
||||
@@ -296,8 +347,13 @@ func WithTeamTaskID(ctx context.Context, taskID string) context.Context {
|
||||
|
||||
// TeamTaskIDFromCtx returns the delegation's team task ID from context.
|
||||
func TeamTaskIDFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxTeamTaskID).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxTeamTaskID).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.TeamTaskID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Workspace scope propagation (delegation origin) ---
|
||||
@@ -312,8 +368,13 @@ func WithWorkspaceChannel(ctx context.Context, channel string) context.Context {
|
||||
}
|
||||
|
||||
func WorkspaceChannelFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxWsChannel).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxWsChannel).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.WorkspaceChannel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func WithWorkspaceChatID(ctx context.Context, chatID string) context.Context {
|
||||
@@ -321,8 +382,13 @@ func WithWorkspaceChatID(ctx context.Context, chatID string) context.Context {
|
||||
}
|
||||
|
||||
func WorkspaceChatIDFromCtx(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxWsChatID).(string)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxWsChatID).(string); v != "" {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.WorkspaceChatID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Pending team task dispatch (post-turn processing) ---
|
||||
@@ -481,6 +547,11 @@ func WithSandboxConfig(ctx context.Context, cfg *sandbox.Config) context.Context
|
||||
}
|
||||
|
||||
func SandboxConfigFromCtx(ctx context.Context) *sandbox.Config {
|
||||
v, _ := ctx.Value(ctxSandboxCfg).(*sandbox.Config)
|
||||
return v
|
||||
if v, _ := ctx.Value(ctxSandboxCfg).(*sandbox.Config); v != nil {
|
||||
return v
|
||||
}
|
||||
if rc := store.RunContextFromCtx(ctx); rc != nil {
|
||||
return rc.SandboxCfg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user