refactor: extract instance.go fork fields into instanceExt embedded struct
Move fork-specific AgentInstance fields and worktree methods to instance_ext.go: - SubagentMgr, Subagents, SkillsFilter - Interview staleness tracking fields - Per-session worktree isolation (worktrees map, all worktree methods) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6fc07c4409
commit
e99ff8b96b
2 changed files with 128 additions and 105 deletions
|
|
@ -7,7 +7,6 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/git"
|
||||
|
|
@ -20,6 +19,8 @@ import (
|
|||
// AgentInstance represents a fully configured agent with its own workspace,
|
||||
// session manager, context builder, and tool registry.
|
||||
type AgentInstance struct {
|
||||
instanceExt // fork-specific fields (see instance_ext.go)
|
||||
|
||||
ID string
|
||||
Name string
|
||||
Model string
|
||||
|
|
@ -37,8 +38,6 @@ type AgentInstance struct {
|
|||
Sessions *session.LegacyAdapter
|
||||
ContextBuilder *ContextBuilder
|
||||
Tools *tools.ToolRegistry
|
||||
Subagents *config.SubagentsConfig
|
||||
SkillsFilter []string
|
||||
Candidates []providers.FallbackCandidate
|
||||
PlanModel string
|
||||
PlanFallbacks []string
|
||||
|
|
@ -51,18 +50,6 @@ type AgentInstance struct {
|
|||
// LightCandidates holds the resolved provider candidates for the light model.
|
||||
// Pre-computed at agent creation to avoid repeated model_list lookups at runtime.
|
||||
LightCandidates []providers.FallbackCandidate
|
||||
|
||||
// SubagentMgr is set during registerSharedTools when orchestration is enabled.
|
||||
// Used by runAgentLoop to wait for spawned subagents before worktree cleanup.
|
||||
SubagentMgr *tools.SubagentManager
|
||||
|
||||
// Interview staleness tracking: consecutive turns where MEMORY.md was not updated.
|
||||
interviewStaleCount int
|
||||
interviewMemoryLen int
|
||||
|
||||
// Per-session worktree isolation
|
||||
worktrees map[string]*git.WorktreeInfo // sessionKey → worktree
|
||||
worktreeMu sync.RWMutex
|
||||
}
|
||||
|
||||
// Close releases resources held by the agent instance.
|
||||
|
|
@ -295,6 +282,10 @@ func NewAgentInstance(
|
|||
}
|
||||
|
||||
return &AgentInstance{
|
||||
instanceExt: instanceExt{
|
||||
Subagents: subagents,
|
||||
SkillsFilter: skillsFilter,
|
||||
},
|
||||
ID: agentID,
|
||||
Name: agentName,
|
||||
Model: model,
|
||||
|
|
@ -312,8 +303,6 @@ func NewAgentInstance(
|
|||
Sessions: sessionsManager,
|
||||
ContextBuilder: contextBuilder,
|
||||
Tools: toolsRegistry,
|
||||
Subagents: subagents,
|
||||
SkillsFilter: skillsFilter,
|
||||
Candidates: candidates,
|
||||
PlanModel: planModel,
|
||||
PlanFallbacks: planFallbacks,
|
||||
|
|
@ -370,94 +359,6 @@ func resolvePlanFallbacks(agentCfg *config.AgentConfig, defaults *config.AgentDe
|
|||
return defaults.PlanModelFallbacks
|
||||
}
|
||||
|
||||
// ActivateWorktree creates a worktree for a session.
|
||||
// projectDir is the git repository to create the worktree in.
|
||||
// If empty, falls back to ai.Workspace.
|
||||
// Worktree path: <workspace>/.worktrees/<branch-basename>/
|
||||
func (ai *AgentInstance) ActivateWorktree(sessionKey, taskName, projectDir string) (*git.WorktreeInfo, error) {
|
||||
if projectDir == "" {
|
||||
projectDir = ai.Workspace
|
||||
}
|
||||
|
||||
repoRoot := git.FindRepoRoot(projectDir)
|
||||
if repoRoot == "" {
|
||||
return nil, fmt.Errorf("directory is not a git repository: %s", projectDir)
|
||||
}
|
||||
|
||||
branchName := git.SanitizeBranchName(taskName)
|
||||
baseName := git.BranchBaseName(branchName)
|
||||
wtPath := filepath.Join(ai.Workspace, ".worktrees", baseName)
|
||||
|
||||
wt, err := git.CreateWorktree(repoRoot, wtPath, branchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ai.worktreeMu.Lock()
|
||||
if ai.worktrees == nil {
|
||||
ai.worktrees = make(map[string]*git.WorktreeInfo)
|
||||
}
|
||||
ai.worktrees[sessionKey] = wt
|
||||
ai.worktreeMu.Unlock()
|
||||
|
||||
return wt, nil
|
||||
}
|
||||
|
||||
// DeactivateWorktree safe-disposes the session's worktree.
|
||||
func (ai *AgentInstance) DeactivateWorktree(sessionKey, commitMsg string, discard bool) (*git.DisposeResult, error) {
|
||||
ai.worktreeMu.Lock()
|
||||
wt, ok := ai.worktrees[sessionKey]
|
||||
if ok {
|
||||
delete(ai.worktrees, sessionKey)
|
||||
}
|
||||
ai.worktreeMu.Unlock()
|
||||
|
||||
if !ok || wt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
repoRoot := git.FindRepoRoot(ai.Workspace)
|
||||
if repoRoot == "" {
|
||||
return nil, fmt.Errorf("workspace is not a git repository")
|
||||
}
|
||||
|
||||
// Even on discard, SafeDispose auto-commits first for safety
|
||||
if commitMsg != "" && git.HasUncommittedChanges(wt.Path) {
|
||||
_ = git.AutoCommit(wt.Path, commitMsg)
|
||||
}
|
||||
|
||||
result := git.SafeDispose(repoRoot, wt)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetWorktree returns the session's active worktree, or nil.
|
||||
func (ai *AgentInstance) GetWorktree(sessionKey string) *git.WorktreeInfo {
|
||||
ai.worktreeMu.RLock()
|
||||
defer ai.worktreeMu.RUnlock()
|
||||
return ai.worktrees[sessionKey]
|
||||
}
|
||||
|
||||
// IsInWorktree returns true if the session has an active worktree.
|
||||
func (ai *AgentInstance) IsInWorktree(sessionKey string) bool {
|
||||
return ai.GetWorktree(sessionKey) != nil
|
||||
}
|
||||
|
||||
// EffectiveWorkspace returns worktree path for session, or original Workspace.
|
||||
func (ai *AgentInstance) EffectiveWorkspace(sessionKey string) string {
|
||||
if wt := ai.GetWorktree(sessionKey); wt != nil {
|
||||
return wt.Path
|
||||
}
|
||||
return ai.Workspace
|
||||
}
|
||||
|
||||
// GetWorktreeBranch returns the branch name for the session's worktree, or "".
|
||||
func (ai *AgentInstance) GetWorktreeBranch(sessionKey string) string {
|
||||
if wt := ai.GetWorktree(sessionKey); wt != nil {
|
||||
return wt.Branch
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func compilePatterns(patterns []string) []*regexp.Regexp {
|
||||
compiled := make([]*regexp.Regexp, 0, len(patterns))
|
||||
for _, p := range patterns {
|
||||
|
|
|
|||
122
pkg/agent/instance_ext.go
Normal file
122
pkg/agent/instance_ext.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/git"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
)
|
||||
|
||||
// instanceExt holds fork-specific fields for AgentInstance.
|
||||
// Embedded in AgentInstance so existing field access continues to work.
|
||||
type instanceExt struct {
|
||||
// SubagentMgr is set during registerSharedTools when orchestration is enabled.
|
||||
// Used by runAgentLoop to wait for spawned subagents before worktree cleanup.
|
||||
SubagentMgr *tools.SubagentManager
|
||||
|
||||
Subagents *config.SubagentsConfig
|
||||
SkillsFilter []string
|
||||
|
||||
// Interview staleness tracking: consecutive turns where MEMORY.md was not updated.
|
||||
interviewStaleCount int
|
||||
interviewMemoryLen int
|
||||
|
||||
// Per-session worktree isolation
|
||||
worktrees map[string]*git.WorktreeInfo // sessionKey → worktree
|
||||
worktreeMu sync.RWMutex
|
||||
}
|
||||
|
||||
// ActivateWorktree creates a worktree for a session.
|
||||
// projectDir is the git repository to create the worktree in.
|
||||
// If empty, falls back to ai.Workspace.
|
||||
// Worktree path: <workspace>/.worktrees/<branch-basename>/
|
||||
func (ai *AgentInstance) ActivateWorktree(sessionKey, taskName, projectDir string) (*git.WorktreeInfo, error) {
|
||||
if projectDir == "" {
|
||||
projectDir = ai.Workspace
|
||||
}
|
||||
|
||||
repoRoot := git.FindRepoRoot(projectDir)
|
||||
if repoRoot == "" {
|
||||
return nil, fmt.Errorf("directory is not a git repository: %s", projectDir)
|
||||
}
|
||||
|
||||
branchName := git.SanitizeBranchName(taskName)
|
||||
baseName := git.BranchBaseName(branchName)
|
||||
wtPath := ai.worktreePath(baseName)
|
||||
|
||||
wt, err := git.CreateWorktree(repoRoot, wtPath, branchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ai.worktreeMu.Lock()
|
||||
if ai.worktrees == nil {
|
||||
ai.worktrees = make(map[string]*git.WorktreeInfo)
|
||||
}
|
||||
ai.worktrees[sessionKey] = wt
|
||||
ai.worktreeMu.Unlock()
|
||||
|
||||
return wt, nil
|
||||
}
|
||||
|
||||
// worktreePath returns the standard path for a worktree under the workspace.
|
||||
func (ai *AgentInstance) worktreePath(baseName string) string {
|
||||
return ai.Workspace + "/.worktrees/" + baseName
|
||||
}
|
||||
|
||||
// DeactivateWorktree safe-disposes the session's worktree.
|
||||
func (ai *AgentInstance) DeactivateWorktree(sessionKey, commitMsg string, discard bool) (*git.DisposeResult, error) {
|
||||
ai.worktreeMu.Lock()
|
||||
wt, ok := ai.worktrees[sessionKey]
|
||||
if ok {
|
||||
delete(ai.worktrees, sessionKey)
|
||||
}
|
||||
ai.worktreeMu.Unlock()
|
||||
|
||||
if !ok || wt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
repoRoot := git.FindRepoRoot(ai.Workspace)
|
||||
if repoRoot == "" {
|
||||
return nil, fmt.Errorf("workspace is not a git repository")
|
||||
}
|
||||
|
||||
// Even on discard, SafeDispose auto-commits first for safety
|
||||
if commitMsg != "" && git.HasUncommittedChanges(wt.Path) {
|
||||
_ = git.AutoCommit(wt.Path, commitMsg)
|
||||
}
|
||||
|
||||
result := git.SafeDispose(repoRoot, wt)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetWorktree returns the session's active worktree, or nil.
|
||||
func (ai *AgentInstance) GetWorktree(sessionKey string) *git.WorktreeInfo {
|
||||
ai.worktreeMu.RLock()
|
||||
defer ai.worktreeMu.RUnlock()
|
||||
return ai.worktrees[sessionKey]
|
||||
}
|
||||
|
||||
// IsInWorktree returns true if the session has an active worktree.
|
||||
func (ai *AgentInstance) IsInWorktree(sessionKey string) bool {
|
||||
return ai.GetWorktree(sessionKey) != nil
|
||||
}
|
||||
|
||||
// EffectiveWorkspace returns worktree path for session, or original Workspace.
|
||||
func (ai *AgentInstance) EffectiveWorkspace(sessionKey string) string {
|
||||
if wt := ai.GetWorktree(sessionKey); wt != nil {
|
||||
return wt.Path
|
||||
}
|
||||
return ai.Workspace
|
||||
}
|
||||
|
||||
// GetWorktreeBranch returns the branch name for the session's worktree, or "".
|
||||
func (ai *AgentInstance) GetWorktreeBranch(sessionKey string) string {
|
||||
if wt := ai.GetWorktree(sessionKey); wt != nil {
|
||||
return wt.Branch
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue