mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 14:17:40 +00:00
feat(channels): add channel context scope contracts
This commit is contained in:
1 parent
07bdec3c08
commit
d230228217
6 files changed
+155
-7
No files matched your search
@@ -0,0 +1,136 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
ChannelScopeTypeChannel = "channel"
|
||||
ChannelScopeTypeGroup = "group"
|
||||
ChannelScopeTypeUser = "user"
|
||||
ChannelScopeTypeRole = "role"
|
||||
)
|
||||
|
||||
// ChannelContextScope identifies the channel-level context used for scoped
|
||||
// grants and credentials. ChannelInstanceName lets runtime code resolve scope
|
||||
// without a channel_instances lookup at the call site.
|
||||
type ChannelContextScope struct {
|
||||
ChannelInstanceID uuid.UUID `json:"channel_instance_id,omitempty"`
|
||||
ChannelInstanceName string `json:"channel_instance_name,omitempty"`
|
||||
ScopeType string `json:"scope_type"`
|
||||
ScopeKey string `json:"scope_key"`
|
||||
}
|
||||
|
||||
func (s ChannelContextScope) Valid() bool {
|
||||
if s.ScopeType == "" {
|
||||
return false
|
||||
}
|
||||
if s.ChannelInstanceID == uuid.Nil && s.ChannelInstanceName == "" {
|
||||
return false
|
||||
}
|
||||
return s.ScopeType == ChannelScopeTypeChannel || s.ScopeKey != ""
|
||||
}
|
||||
|
||||
func WithChannelContextScope(ctx context.Context, scope ChannelContextScope) context.Context {
|
||||
return context.WithValue(ctx, ChannelContextScopeKey, scope)
|
||||
}
|
||||
|
||||
func ChannelContextScopeFromContext(ctx context.Context) (ChannelContextScope, bool) {
|
||||
if v, ok := ctx.Value(ChannelContextScopeKey).(ChannelContextScope); ok && v.Valid() {
|
||||
return v, true
|
||||
}
|
||||
if rc := RunContextFromCtx(ctx); rc != nil && rc.ChannelContextScope.Valid() {
|
||||
return rc.ChannelContextScope, true
|
||||
}
|
||||
return ChannelContextScope{}, false
|
||||
}
|
||||
|
||||
type MCPContextGrant struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
ChannelInstanceID uuid.UUID `json:"channel_instance_id" db:"channel_instance_id"`
|
||||
ScopeType string `json:"scope_type" db:"scope_type"`
|
||||
ScopeKey string `json:"scope_key" db:"scope_key"`
|
||||
ServerID uuid.UUID `json:"server_id" db:"server_id"`
|
||||
Enabled bool `json:"enabled" db:"enabled"`
|
||||
ToolAllow json.RawMessage `json:"tool_allow,omitempty" db:"tool_allow"`
|
||||
ToolDeny json.RawMessage `json:"tool_deny,omitempty" db:"tool_deny"`
|
||||
ConfigOverrides json.RawMessage `json:"config_overrides,omitempty" db:"config_overrides"`
|
||||
GrantedBy string `json:"granted_by" db:"granted_by"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
type MCPContextCredentials struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
ChannelInstanceID uuid.UUID `json:"channel_instance_id" db:"channel_instance_id"`
|
||||
ScopeType string `json:"scope_type" db:"scope_type"`
|
||||
ScopeKey string `json:"scope_key" db:"scope_key"`
|
||||
ServerID uuid.UUID `json:"server_id" db:"server_id"`
|
||||
APIKey string `json:"api_key,omitempty" db:"-"`
|
||||
Headers map[string]string `json:"headers,omitempty" db:"-"`
|
||||
Env map[string]string `json:"env,omitempty" db:"-"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
type SecureCLIContextGrant struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
ChannelInstanceID uuid.UUID `json:"channel_instance_id" db:"channel_instance_id"`
|
||||
ScopeType string `json:"scope_type" db:"scope_type"`
|
||||
ScopeKey string `json:"scope_key" db:"scope_key"`
|
||||
BinaryID uuid.UUID `json:"binary_id" db:"binary_id"`
|
||||
Enabled bool `json:"enabled" db:"enabled"`
|
||||
DenyArgs *json.RawMessage `json:"deny_args,omitempty" db:"deny_args"`
|
||||
DenyVerbose *json.RawMessage `json:"deny_verbose,omitempty" db:"deny_verbose"`
|
||||
TimeoutSeconds *int `json:"timeout_seconds,omitempty" db:"timeout_seconds"`
|
||||
Tips *string `json:"tips,omitempty" db:"tips"`
|
||||
EncryptedEnv []byte `json:"-" db:"encrypted_env"`
|
||||
EnvKeys []string `json:"env_keys,omitempty" db:"-"`
|
||||
Env map[string]SecureCLIEnvResponseEntry `json:"env,omitempty" db:"-"`
|
||||
EnvSet bool `json:"env_set" db:"-"`
|
||||
GrantedBy string `json:"granted_by" db:"granted_by"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
type SecureCLIContextCredentials struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
ChannelInstanceID uuid.UUID `json:"channel_instance_id" db:"channel_instance_id"`
|
||||
ScopeType string `json:"scope_type" db:"scope_type"`
|
||||
ScopeKey string `json:"scope_key" db:"scope_key"`
|
||||
BinaryID uuid.UUID `json:"binary_id" db:"binary_id"`
|
||||
EncryptedEnv []byte `json:"-" db:"encrypted_env"`
|
||||
Metadata json.RawMessage `json:"metadata,omitempty" db:"metadata"`
|
||||
CredentialType *string `json:"credential_type,omitempty" db:"credential_type"`
|
||||
HostScope *string `json:"host_scope,omitempty" db:"host_scope"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
type MCPContextAdminStore interface {
|
||||
UpsertContextGrant(ctx context.Context, grant *MCPContextGrant) error
|
||||
DeleteContextGrant(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, serverID uuid.UUID) error
|
||||
ListContextGrants(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]MCPContextGrant, error)
|
||||
|
||||
SetContextCredentials(ctx context.Context, creds *MCPContextCredentials) error
|
||||
GetContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, serverID uuid.UUID) (*MCPContextCredentials, error)
|
||||
DeleteContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, serverID uuid.UUID) error
|
||||
ListContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]MCPContextCredentials, error)
|
||||
}
|
||||
|
||||
type SecureCLIContextAdminStore interface {
|
||||
UpsertContextGrant(ctx context.Context, grant *SecureCLIContextGrant) error
|
||||
DeleteContextGrant(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, binaryID uuid.UUID) error
|
||||
ListContextGrants(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]SecureCLIContextGrant, error)
|
||||
|
||||
SetContextCredentials(ctx context.Context, creds *SecureCLIContextCredentials) error
|
||||
GetContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, binaryID uuid.UUID) (*SecureCLIContextCredentials, error)
|
||||
DeleteContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, binaryID uuid.UUID) error
|
||||
ListContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]SecureCLIContextCredentials, error)
|
||||
}
|
||||
@@ -29,12 +29,13 @@ type ChannelContact struct {
|
||||
|
||||
// ContactListOpts holds pagination and filter options for listing contacts.
|
||||
type ContactListOpts struct {
|
||||
Search string // ILIKE on display_name, username, sender_id
|
||||
ChannelType string // filter by platform (telegram, discord, etc.)
|
||||
PeerKind string // "direct" or "group"
|
||||
ContactType string // "user" or "group"
|
||||
Limit int
|
||||
Offset int
|
||||
Search string // ILIKE on display_name, username, sender_id
|
||||
ChannelType string // filter by platform (telegram, discord, etc.)
|
||||
ChannelInstance string // filter by channel instance name
|
||||
PeerKind string // "direct" or "group"
|
||||
ContactType string // "user" or "group"
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// ContactStore manages channel contacts (auto-collected user info).
|
||||
|
||||
@@ -50,6 +50,8 @@ const (
|
||||
CredentialUserIDKey contextKey = "goclaw_credential_user_id"
|
||||
// SenderNameKey is the display name from channel metadata (for bootstrap auto-contact).
|
||||
SenderNameKey contextKey = "goclaw_sender_name"
|
||||
// ChannelContextScopeKey carries the channel/group/user scope for runtime grants and credentials.
|
||||
ChannelContextScopeKey contextKey = "goclaw_channel_context_scope"
|
||||
// AgentAudioKey carries the immutable agent audio snapshot for TTS tool dispatch.
|
||||
AgentAudioKey contextKey = "goclaw_agent_audio"
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
)
|
||||
|
||||
|
||||
// PGContactStore implements store.ContactStore backed by Postgres.
|
||||
type PGContactStore struct {
|
||||
db *sql.DB
|
||||
@@ -64,6 +63,11 @@ func contactWhereClause(ctx context.Context, opts store.ContactListOpts) (string
|
||||
args = append(args, opts.ChannelType)
|
||||
argIdx++
|
||||
}
|
||||
if opts.ChannelInstance != "" {
|
||||
conditions = append(conditions, fmt.Sprintf("channel_instance = $%d", argIdx))
|
||||
args = append(args, opts.ChannelInstance)
|
||||
argIdx++
|
||||
}
|
||||
if opts.PeerKind != "" {
|
||||
conditions = append(conditions, fmt.Sprintf("peer_kind = $%d", argIdx))
|
||||
args = append(args, opts.PeerKind)
|
||||
|
||||
@@ -40,6 +40,7 @@ type RunContext struct {
|
||||
// Tool configuration
|
||||
BuiltinToolSettings map[string][]byte
|
||||
ChannelType string
|
||||
ChannelContextScope ChannelContextScope
|
||||
SubagentsCfg *config.SubagentsConfig
|
||||
ParentModel string
|
||||
ParentProvider string
|
||||
|
||||
@@ -71,6 +71,10 @@ func contactWhereSQLite(ctx context.Context, opts store.ContactListOpts) (string
|
||||
conditions = append(conditions, "channel_type = ?")
|
||||
args = append(args, opts.ChannelType)
|
||||
}
|
||||
if opts.ChannelInstance != "" {
|
||||
conditions = append(conditions, "channel_instance = ?")
|
||||
args = append(args, opts.ChannelInstance)
|
||||
}
|
||||
if opts.PeerKind != "" {
|
||||
conditions = append(conditions, "peer_kind = ?")
|
||||
args = append(args, opts.PeerKind)
|
||||
|
||||
Reference in new issue
Block a user