mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
b3a7b7ad64
* feat: add agent self-evolution * fix ci * delete unused doc * fix lint * fix evolution review issues
122 lines
2.2 KiB
Go
122 lines
2.2 KiB
Go
package evolution
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
)
|
|
|
|
type coldPathRuntime interface {
|
|
RunColdPathOnce(ctx context.Context, workspace string) error
|
|
}
|
|
|
|
type ColdPathRunner struct {
|
|
runtime coldPathRuntime
|
|
async func(func())
|
|
onError func(error)
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
mu sync.Mutex
|
|
wg sync.WaitGroup
|
|
closeOnce sync.Once
|
|
closed bool
|
|
running map[string]workspaceRunState
|
|
}
|
|
|
|
func NewColdPathRunner(runtime coldPathRuntime) *ColdPathRunner {
|
|
return NewColdPathRunnerWithErrorHandler(runtime, nil)
|
|
}
|
|
|
|
func NewColdPathRunnerWithErrorHandler(runtime coldPathRuntime, onError func(error)) *ColdPathRunner {
|
|
if onError == nil {
|
|
onError = func(error) {}
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &ColdPathRunner{
|
|
runtime: runtime,
|
|
async: func(run func()) {
|
|
go run()
|
|
},
|
|
onError: onError,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
running: make(map[string]workspaceRunState),
|
|
}
|
|
}
|
|
|
|
type workspaceRunState struct {
|
|
running bool
|
|
pending bool
|
|
}
|
|
|
|
func (r *ColdPathRunner) Trigger(workspace string) bool {
|
|
if r == nil || r.runtime == nil || workspace == "" {
|
|
return false
|
|
}
|
|
|
|
r.mu.Lock()
|
|
if r.closed {
|
|
r.mu.Unlock()
|
|
return false
|
|
}
|
|
state, exists := r.running[workspace]
|
|
if exists && state.running {
|
|
state.pending = true
|
|
r.running[workspace] = state
|
|
r.mu.Unlock()
|
|
return true
|
|
}
|
|
r.running[workspace] = workspaceRunState{running: true}
|
|
r.wg.Add(1)
|
|
r.mu.Unlock()
|
|
|
|
r.async(func() {
|
|
defer r.wg.Done()
|
|
r.runWorkspace(workspace)
|
|
})
|
|
|
|
return true
|
|
}
|
|
|
|
func (r *ColdPathRunner) runWorkspace(workspace string) {
|
|
for {
|
|
if err := r.runtime.RunColdPathOnce(r.ctx, workspace); err != nil && !errors.Is(err, context.Canceled) {
|
|
r.onError(err)
|
|
}
|
|
|
|
r.mu.Lock()
|
|
state, exists := r.running[workspace]
|
|
if !exists || r.closed {
|
|
delete(r.running, workspace)
|
|
r.mu.Unlock()
|
|
return
|
|
}
|
|
if state.pending {
|
|
state.pending = false
|
|
r.running[workspace] = state
|
|
r.mu.Unlock()
|
|
continue
|
|
}
|
|
delete(r.running, workspace)
|
|
r.mu.Unlock()
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *ColdPathRunner) Close() error {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
r.closeOnce.Do(func() {
|
|
r.mu.Lock()
|
|
r.closed = true
|
|
r.mu.Unlock()
|
|
r.cancel()
|
|
})
|
|
r.wg.Wait()
|
|
return nil
|
|
}
|