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/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"github.com/sipeed/picoclaw/pkg/providers"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func agentCmd() {
|
func agentCmd() {
|
||||||
|
|
@ -72,6 +73,15 @@ func agentCmd() {
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
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)
|
// Print agent startup info (only for interactive mode)
|
||||||
startupInfo := agentLoop.GetStartupInfo()
|
startupInfo := agentLoop.GetStartupInfo()
|
||||||
logger.InfoCF("agent", "Agent initialized",
|
logger.InfoCF("agent", "Agent initialized",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ type AgentInstance struct {
|
||||||
Sessions *session.SessionManager
|
Sessions *session.SessionManager
|
||||||
ContextBuilder *ContextBuilder
|
ContextBuilder *ContextBuilder
|
||||||
Tools *tools.ToolRegistry
|
Tools *tools.ToolRegistry
|
||||||
|
PermStore *tools.PermissionStore
|
||||||
Subagents *config.SubagentsConfig
|
Subagents *config.SubagentsConfig
|
||||||
SkillsFilter []string
|
SkillsFilter []string
|
||||||
Candidates []providers.FallbackCandidate
|
Candidates []providers.FallbackCandidate
|
||||||
|
|
@ -88,6 +89,16 @@ func NewAgentInstance(
|
||||||
temperature = *defaults.Temperature
|
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
|
// Resolve fallback candidates
|
||||||
modelCfg := providers.ModelConfig{
|
modelCfg := providers.ModelConfig{
|
||||||
Primary: model,
|
Primary: model,
|
||||||
|
|
@ -109,6 +120,7 @@ func NewAgentInstance(
|
||||||
Sessions: sessionsManager,
|
Sessions: sessionsManager,
|
||||||
ContextBuilder: contextBuilder,
|
ContextBuilder: contextBuilder,
|
||||||
Tools: toolsRegistry,
|
Tools: toolsRegistry,
|
||||||
|
PermStore: permStore,
|
||||||
Subagents: subagents,
|
Subagents: subagents,
|
||||||
SkillsFilter: skillsFilter,
|
SkillsFilter: skillsFilter,
|
||||||
Candidates: candidates,
|
Candidates: candidates,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewAgentInstance_UsesDefaultsTemperatureAndMaxTokens(t *testing.T) {
|
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)
|
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
|
summarizing sync.Map
|
||||||
fallback *providers.FallbackChain
|
fallback *providers.FallbackChain
|
||||||
channelManager *channels.Manager
|
channelManager *channels.Manager
|
||||||
|
permFuncFactory tools.PermissionFuncFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// processOptions configures how a message is processed
|
// processOptions configures how a message is processed
|
||||||
|
|
@ -212,6 +213,12 @@ func (al *AgentLoop) SetChannelManager(cm *channels.Manager) {
|
||||||
al.channelManager = cm
|
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.
|
// RecordLastChannel records the last active channel for this workspace.
|
||||||
// This uses the atomic state save mechanism to prevent data loss on crash.
|
// This uses the atomic state save mechanism to prevent data loss on crash.
|
||||||
func (al *AgentLoop) RecordLastChannel(channel string) error {
|
func (al *AgentLoop) RecordLastChannel(channel string) error {
|
||||||
|
|
@ -742,6 +749,18 @@ func (al *AgentLoop) updateToolContexts(agent *AgentInstance, channel, chatID st
|
||||||
st.SetContext(channel, chatID)
|
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.
|
// maybeSummarize triggers summarization if the session history exceeds thresholds.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue