mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-18 12:14:24 +00:00
feat: Add tool allow list configuration and enforcement for Telegram channels, allowing per-group/topic tool restrictions.
This commit is contained in:
1 parent
ae89239dd5
commit
278a98ba74
7 files changed
+48
-3
No files matched your search
@@ -199,6 +199,7 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
RunID: runID,
|
||||
Stream: enableStream,
|
||||
HistoryLimit: msg.HistoryLimit,
|
||||
ToolAllow: msg.ToolAllow,
|
||||
ExtraSystemPrompt: extraPrompt,
|
||||
SkillFilter: skillFilter,
|
||||
}, scheduler.ScheduleOpts{
|
||||
|
||||
+21
-3
@@ -254,6 +254,7 @@ type RunRequest struct {
|
||||
ExtraSystemPrompt string // optional: injected into system prompt (skills, subagent context, etc.)
|
||||
SkillFilter []string // per-request skill override: nil=use agent default, []=no skills, ["x","y"]=whitelist
|
||||
HistoryLimit int // max user turns to keep in context (0=unlimited, from channel config)
|
||||
ToolAllow []string // per-group tool allow list (nil = no restriction, supports "group:xxx")
|
||||
LocalKey string // composite key with topic/thread suffix for routing (e.g. "-100123:topic:42")
|
||||
ParentTraceID uuid.UUID // if set, reuse parent trace instead of creating new (announce runs)
|
||||
ParentRootSpanID uuid.UUID // if set, nest announce agent span under this parent span
|
||||
@@ -573,8 +574,13 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
|
||||
// Build provider request with policy-filtered tools
|
||||
var toolDefs []providers.ToolDefinition
|
||||
var allowedTools map[string]bool
|
||||
if l.toolPolicy != nil {
|
||||
toolDefs = l.toolPolicy.FilterTools(l.tools, l.id, l.provider.Name(), l.agentToolPolicy, nil, false, false)
|
||||
toolDefs = l.toolPolicy.FilterTools(l.tools, l.id, l.provider.Name(), l.agentToolPolicy, req.ToolAllow, false, false)
|
||||
allowedTools = make(map[string]bool, len(toolDefs))
|
||||
for _, td := range toolDefs {
|
||||
allowedTools[td.Function.Name] = true
|
||||
}
|
||||
} else {
|
||||
toolDefs = l.tools.ProviderDefs()
|
||||
}
|
||||
@@ -703,7 +709,13 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
argsHash := loopDetector.record(tc.Name, tc.Arguments)
|
||||
|
||||
toolSpanStart := time.Now().UTC()
|
||||
result := l.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, req.Channel, req.ChatID, req.PeerKind, req.SessionKey, nil)
|
||||
var result *tools.Result
|
||||
if allowedTools != nil && !allowedTools[tc.Name] {
|
||||
slog.Warn("security.tool_policy_blocked", "agent", l.id, "tool", tc.Name)
|
||||
result = tools.ErrorResult("tool not allowed by policy: " + tc.Name)
|
||||
} else {
|
||||
result = l.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, req.Channel, req.ChatID, req.PeerKind, req.SessionKey, nil)
|
||||
}
|
||||
|
||||
l.emitToolSpan(ctx, toolSpanStart, tc.Name, tc.ID, string(argsJSON), result)
|
||||
|
||||
@@ -796,7 +808,13 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
argsJSON, _ := json.Marshal(tc.Arguments)
|
||||
slog.Info("tool call", "agent", l.id, "tool", tc.Name, "args_len", len(argsJSON), "parallel", true)
|
||||
spanStart := time.Now().UTC()
|
||||
result := l.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, req.Channel, req.ChatID, req.PeerKind, req.SessionKey, nil)
|
||||
var result *tools.Result
|
||||
if allowedTools != nil && !allowedTools[tc.Name] {
|
||||
slog.Warn("security.tool_policy_blocked", "agent", l.id, "tool", tc.Name)
|
||||
result = tools.ErrorResult("tool not allowed by policy: " + tc.Name)
|
||||
} else {
|
||||
result = l.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, req.Channel, req.ChatID, req.PeerKind, req.SessionKey, nil)
|
||||
}
|
||||
resultCh <- indexedResult{idx: idx, tc: tc, result: result, argsJSON: string(argsJSON), spanStart: spanStart}
|
||||
}(i, tc)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type InboundMessage struct {
|
||||
AgentID string `json:"agent_id,omitempty"` // target agent (for multi-agent routing)
|
||||
UserID string `json:"user_id,omitempty"` // external user ID for per-user scoping (memory, bootstrap)
|
||||
HistoryLimit int `json:"history_limit,omitempty"` // max turns to keep in context (0=unlimited, from channel config)
|
||||
ToolAllow []string `json:"tool_allow,omitempty"` // per-group tool allow list (nil = no restriction)
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -441,6 +441,7 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) {
|
||||
UserID: userID,
|
||||
AgentID: targetAgentID,
|
||||
HistoryLimit: c.historyLimit,
|
||||
ToolAllow: topicCfg.tools,
|
||||
Metadata: metadata,
|
||||
})
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ type resolvedTopicConfig struct {
|
||||
allowFrom []string
|
||||
enabled *bool
|
||||
skills []string // nil = inherit, non-nil = override (empty = no skills)
|
||||
tools []string // nil = inherit (all tools), non-nil = override (supports "group:xxx")
|
||||
systemPrompt string // concatenated group + topic prompts
|
||||
}
|
||||
|
||||
@@ -73,6 +74,9 @@ func mergeGroupInto(dst *resolvedTopicConfig, src *config.TelegramGroupConfig) {
|
||||
if src.Skills != nil {
|
||||
dst.skills = src.Skills
|
||||
}
|
||||
if src.Tools != nil {
|
||||
dst.tools = src.Tools
|
||||
}
|
||||
if src.SystemPrompt != "" {
|
||||
dst.systemPrompt = src.SystemPrompt
|
||||
}
|
||||
@@ -97,6 +101,10 @@ func mergeTopicInto(dst *resolvedTopicConfig, src *config.TelegramTopicConfig, g
|
||||
if src.Skills != nil {
|
||||
dst.skills = src.Skills
|
||||
}
|
||||
// Tools: topic overrides group (same firstDefined pattern as skills).
|
||||
if src.Tools != nil {
|
||||
dst.tools = src.Tools
|
||||
}
|
||||
// SystemPrompt: concatenate group + topic (both may exist, matching TS).
|
||||
if src.SystemPrompt != "" {
|
||||
parts := []string{}
|
||||
|
||||
@@ -48,6 +48,7 @@ type TelegramGroupConfig struct {
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from,omitempty"` // override allow_from for this group
|
||||
Enabled *bool `json:"enabled,omitempty"` // disable bot for this group (default: true)
|
||||
Skills []string `json:"skills,omitempty"` // skill whitelist (nil = all, [] = none)
|
||||
Tools []string `json:"tools,omitempty"` // tool allow list (nil = all, supports "group:xxx")
|
||||
SystemPrompt string `json:"system_prompt,omitempty"` // extra system prompt for this group
|
||||
Topics map[string]*TelegramTopicConfig `json:"topics,omitempty"` // per-topic overrides (key: thread ID string)
|
||||
}
|
||||
@@ -58,6 +59,7 @@ type TelegramTopicConfig struct {
|
||||
RequireMention *bool `json:"require_mention,omitempty"`
|
||||
GroupPolicy string `json:"group_policy,omitempty"`
|
||||
Skills []string `json:"skills,omitempty"`
|
||||
Tools []string `json:"tools,omitempty"` // tool allow list (nil = inherit, supports "group:xxx")
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from,omitempty"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { groupPolicyOptions } from "./channel-schemas";
|
||||
import { ToolNameSelect } from "@/components/shared/tool-name-select";
|
||||
|
||||
export interface TelegramGroupConfigValues {
|
||||
group_policy?: string;
|
||||
@@ -15,6 +16,7 @@ export interface TelegramGroupConfigValues {
|
||||
enabled?: boolean;
|
||||
allow_from?: string[];
|
||||
skills?: string[];
|
||||
tools?: string[];
|
||||
system_prompt?: string;
|
||||
}
|
||||
|
||||
@@ -141,6 +143,18 @@ export function TelegramGroupFields({ config, onChange, idPrefix }: Props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-1.5">
|
||||
<Label>Tool Allow</Label>
|
||||
<ToolNameSelect
|
||||
value={config.tools ?? []}
|
||||
onChange={(v) => update({ tools: v.length > 0 ? v : undefined })}
|
||||
placeholder="Select allowed tools (empty = inherit all)..."
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Restrict which tools are available in this group. Supports group names like "group:web", "group:fs". Empty = inherit all tools.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-1.5">
|
||||
<Label htmlFor={`${idPrefix}-prompt`}>System Prompt</Label>
|
||||
<Textarea
|
||||
|
||||
Reference in new issue
Block a user