feat: wire permission system into agent loop and CLI
Add PermStore to AgentInstance, SetPermissionFuncFactory to AgentLoop, and update updateToolContexts to set per-request PermissionFunc on all PermissibleTools. CLI agent command creates a stdin-based permission prompt for the cli channel.
This commit is contained in:
parent
e3300b199d
commit
0dc21b649f
4 changed files with 76 additions and 8 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
)
|
||||
|
||||
func agentCmd() {
|
||||
|
|
@ -72,6 +73,15 @@ func agentCmd() {
|
|||
msgBus := bus.NewMessageBus()
|
||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
// Set up CLI permission prompt for workspace access
|
||||
cliPermFn := tools.NewCLIPermissionFunc(os.Stdin, os.Stdout)
|
||||
agentLoop.SetPermissionFuncFactory(func(channel, chatID string) tools.PermissionFunc {
|
||||
if channel == "cli" {
|
||||
return cliPermFn
|
||||
}
|
||||
return nil // Other channels fall back to LLM-driven flow
|
||||
})
|
||||
|
||||
// Print agent startup info (only for interactive mode)
|
||||
startupInfo := agentLoop.GetStartupInfo()
|
||||
logger.InfoCF("agent", "Agent initialized",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ type AgentInstance struct {
|
|||
Sessions *session.SessionManager
|
||||
ContextBuilder *ContextBuilder
|
||||
Tools *tools.ToolRegistry
|
||||
PermStore *tools.PermissionStore
|
||||
Subagents *config.SubagentsConfig
|
||||
SkillsFilter []string
|
||||
Candidates []providers.FallbackCandidate
|
||||
|
|
@ -88,6 +89,16 @@ func NewAgentInstance(
|
|||
temperature = *defaults.Temperature
|
||||
}
|
||||
|
||||
// Set up permission store and wire into permissible tools
|
||||
permStore := tools.NewPermissionStore()
|
||||
for _, name := range toolsRegistry.List() {
|
||||
if tool, ok := toolsRegistry.Get(name); ok {
|
||||
if pt, ok := tool.(tools.PermissibleTool); ok {
|
||||
pt.SetPermission(permStore, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve fallback candidates
|
||||
modelCfg := providers.ModelConfig{
|
||||
Primary: model,
|
||||
|
|
@ -109,6 +120,7 @@ func NewAgentInstance(
|
|||
Sessions: sessionsManager,
|
||||
ContextBuilder: contextBuilder,
|
||||
Tools: toolsRegistry,
|
||||
PermStore: permStore,
|
||||
Subagents: subagents,
|
||||
SkillsFilter: skillsFilter,
|
||||
Candidates: candidates,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
)
|
||||
|
||||
func TestNewAgentInstance_UsesDefaultsTemperatureAndMaxTokens(t *testing.T) {
|
||||
|
|
@ -93,3 +94,29 @@ func TestNewAgentInstance_DefaultsTemperatureWhenUnset(t *testing.T) {
|
|||
t.Fatalf("Temperature = %f, want %f", agent.Temperature, 0.7)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentInstance_ToolsImplementPermissibleTool(t *testing.T) {
|
||||
defaults := &config.AgentDefaults{
|
||||
Model: "test-model",
|
||||
Workspace: t.TempDir(),
|
||||
RestrictToWorkspace: true,
|
||||
}
|
||||
cfg := config.DefaultConfig()
|
||||
instance := NewAgentInstance(nil, defaults, cfg, nil)
|
||||
|
||||
permissibleTools := []string{"read_file", "write_file", "list_dir", "edit_file", "append_file", "exec"}
|
||||
for _, name := range permissibleTools {
|
||||
tool, ok := instance.Tools.Get(name)
|
||||
if !ok {
|
||||
t.Errorf("tool %q not registered", name)
|
||||
continue
|
||||
}
|
||||
if _, ok := tool.(tools.PermissibleTool); !ok {
|
||||
t.Errorf("tool %q does not implement PermissibleTool", name)
|
||||
}
|
||||
}
|
||||
|
||||
if instance.PermStore == nil {
|
||||
t.Error("expected PermStore to be non-nil")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type AgentLoop struct {
|
|||
summarizing sync.Map
|
||||
fallback *providers.FallbackChain
|
||||
channelManager *channels.Manager
|
||||
permFuncFactory tools.PermissionFuncFactory
|
||||
}
|
||||
|
||||
// processOptions configures how a message is processed
|
||||
|
|
@ -212,6 +213,12 @@ func (al *AgentLoop) SetChannelManager(cm *channels.Manager) {
|
|||
al.channelManager = cm
|
||||
}
|
||||
|
||||
// SetPermissionFuncFactory sets the factory that creates PermissionFunc instances
|
||||
// for each channel/chatID pair. This allows channel-specific permission prompts.
|
||||
func (al *AgentLoop) SetPermissionFuncFactory(factory tools.PermissionFuncFactory) {
|
||||
al.permFuncFactory = factory
|
||||
}
|
||||
|
||||
// RecordLastChannel records the last active channel for this workspace.
|
||||
// This uses the atomic state save mechanism to prevent data loss on crash.
|
||||
func (al *AgentLoop) RecordLastChannel(channel string) error {
|
||||
|
|
@ -742,6 +749,18 @@ func (al *AgentLoop) updateToolContexts(agent *AgentInstance, channel, chatID st
|
|||
st.SetContext(channel, chatID)
|
||||
}
|
||||
}
|
||||
|
||||
// Set permission function for this request's channel
|
||||
if al.permFuncFactory != nil {
|
||||
permFn := al.permFuncFactory(channel, chatID)
|
||||
for _, name := range agent.Tools.List() {
|
||||
if tool, ok := agent.Tools.Get(name); ok {
|
||||
if pt, ok := tool.(tools.PermissibleTool); ok {
|
||||
pt.SetPermission(agent.PermStore, permFn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// maybeSummarize triggers summarization if the session history exceeds thresholds.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue