177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/config"
|
|
"github.com/sipeed/picoclaw/pkg/evolution"
|
|
"github.com/sipeed/picoclaw/pkg/logger"
|
|
"github.com/sipeed/picoclaw/pkg/providers"
|
|
)
|
|
|
|
const evolutionObserverHookName = "evolution-observer"
|
|
|
|
type evolutionBridge struct {
|
|
cfg config.EvolutionConfig
|
|
registry *AgentRegistry
|
|
runtime *evolution.Runtime
|
|
coldPathRunner *evolution.ColdPathRunner
|
|
bgCtx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func newEvolutionBridge(registry *AgentRegistry, cfg *config.Config, provider providers.LLMProvider) (*evolutionBridge, error) {
|
|
if cfg == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
modelID := ""
|
|
if provider != nil {
|
|
modelID = provider.GetDefaultModel()
|
|
}
|
|
runtime, err := evolution.NewRuntime(evolution.RuntimeOptions{
|
|
Config: cfg.Evolution,
|
|
GeneratorFactory: func(workspace string) evolution.DraftGenerator {
|
|
return evolution.NewDraftGeneratorForWorkspace(workspace, provider, modelID)
|
|
},
|
|
SuccessJudgeFactory: func(workspace string) evolution.SuccessJudge {
|
|
return evolution.NewLLMTaskSuccessJudge(provider, modelID, &evolution.HeuristicSuccessJudge{})
|
|
},
|
|
ApplierFactory: func(workspace string) *evolution.Applier {
|
|
return evolution.NewApplier(evolution.NewPaths(workspace, cfg.Evolution.StateDir), nil)
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bgCtx, cancel := context.WithCancel(context.Background())
|
|
|
|
bridge := &evolutionBridge{
|
|
cfg: cfg.Evolution,
|
|
registry: registry,
|
|
runtime: runtime,
|
|
bgCtx: bgCtx,
|
|
cancel: cancel,
|
|
}
|
|
if cfg.Evolution.RunsColdPathAutomatically() {
|
|
bridge.coldPathRunner = evolution.NewColdPathRunnerWithErrorHandler(runtime, func(err error) {
|
|
logger.WarnCF("agent", "Cold path run failed", map[string]any{
|
|
"error": err.Error(),
|
|
})
|
|
})
|
|
}
|
|
|
|
return bridge, nil
|
|
}
|
|
|
|
func (b *evolutionBridge) Close() error {
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
if b.cancel != nil {
|
|
b.cancel()
|
|
}
|
|
var closeErr error
|
|
if b.coldPathRunner != nil {
|
|
closeErr = b.coldPathRunner.Close()
|
|
}
|
|
b.wg.Wait()
|
|
return closeErr
|
|
}
|
|
|
|
func (b *evolutionBridge) OnEvent(_ context.Context, evt Event) error {
|
|
if b == nil || !b.cfg.Enabled || b.runtime == nil {
|
|
return nil
|
|
}
|
|
|
|
switch evt.Kind {
|
|
case EventKindTurnEnd:
|
|
payload, ok := evt.Payload.(TurnEndPayload)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
b.handleTurnEndAsync(evt.Meta, payload)
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *evolutionBridge) handleTurnEndAsync(meta EventMeta, payload TurnEndPayload) {
|
|
if b == nil || b.runtime == nil {
|
|
return
|
|
}
|
|
|
|
input := evolution.TurnCaseInput{
|
|
Workspace: payload.Workspace,
|
|
WorkspaceID: payload.Workspace,
|
|
TurnID: meta.TurnID,
|
|
SessionKey: meta.SessionKey,
|
|
AgentID: meta.AgentID,
|
|
Status: string(payload.Status),
|
|
UserMessage: payload.UserMessage,
|
|
FinalContent: payload.FinalContent,
|
|
ToolKinds: append([]string(nil), payload.ToolKinds...),
|
|
ToolExecutions: toEvolutionToolExecutions(payload.ToolExecutions),
|
|
ActiveSkillNames: append([]string(nil), payload.ActiveSkills...),
|
|
AttemptedSkillNames: append([]string(nil), payload.AttemptedSkills...),
|
|
FinalSuccessfulPath: append([]string(nil), payload.FinalSuccessfulPath...),
|
|
SkillContextSnapshots: toEvolutionSkillContextSnapshots(payload.SkillContextSnapshots),
|
|
}
|
|
|
|
ctx := b.bgCtx
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
b.wg.Add(1)
|
|
go func() {
|
|
defer b.wg.Done()
|
|
if err := b.runtime.FinalizeTurn(ctx, input); err != nil {
|
|
logger.WarnCF("agent", "Evolution finalize turn failed", map[string]any{
|
|
"error": err.Error(),
|
|
"turn_id": input.TurnID,
|
|
"workspace": input.Workspace,
|
|
})
|
|
return
|
|
}
|
|
if b.coldPathRunner != nil {
|
|
b.coldPathRunner.Trigger(input.Workspace)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func toEvolutionSkillContextSnapshots(input []SkillContextSnapshot) []evolution.SkillContextSnapshot {
|
|
if len(input) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]evolution.SkillContextSnapshot, 0, len(input))
|
|
for _, snapshot := range input {
|
|
out = append(out, evolution.SkillContextSnapshot{
|
|
Sequence: snapshot.Sequence,
|
|
Trigger: snapshot.Trigger,
|
|
SkillNames: append([]string(nil), snapshot.SkillNames...),
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func toEvolutionToolExecutions(input []ToolExecutionRecord) []evolution.ToolExecutionRecord {
|
|
if len(input) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]evolution.ToolExecutionRecord, 0, len(input))
|
|
for _, record := range input {
|
|
out = append(out, evolution.ToolExecutionRecord{
|
|
Name: record.Name,
|
|
Success: record.Success,
|
|
ErrorSummary: record.ErrorSummary,
|
|
SkillNames: append([]string(nil), record.SkillNames...),
|
|
})
|
|
}
|
|
return out
|
|
}
|