mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-11 03:13:24 +00:00
* feat(collaboration): isolate delegated artifacts and child runs Isolate delegated inputs and outputs behind secure artifact exchange lifecycles. Scope Agent Link tasks by tenant and root agent, and enforce delegation spawn-tree boundaries. Add process-wide child-run admission and preserve logical media paths across native, MCP, and sandbox execution. * fix(collaboration): harden delegated task isolation Enforce tenant and root-agent task scope across migrations and stores. Add exactly-once async completion delivery, delegated sandbox boundaries, and confined artifact and media recovery across runtime surfaces. * fix(collaboration): recover interrupted async tasks * fix(collaboration): normalize persisted child-run status --------- Co-authored-by: ntduc <ntduc@cpp.ai.vn>
66 lines
1.8 KiB
Go
66 lines
1.8 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
type scriptedSubagentTaskRecovery struct {
|
|
mu sync.Mutex
|
|
failures int
|
|
recovered int64
|
|
calls int
|
|
}
|
|
|
|
func (s *scriptedSubagentTaskRecovery) RecoverInterrupted(ctx context.Context) (int64, error) {
|
|
if !store.IsMasterScope(ctx) || store.TenantIDFromContext(ctx) != store.MasterTenantID {
|
|
return 0, errors.New("recovery did not receive explicit master scope")
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.calls++
|
|
if s.calls <= s.failures {
|
|
return 0, errors.New("database temporarily unavailable")
|
|
}
|
|
return s.recovered, nil
|
|
}
|
|
|
|
func (s *scriptedSubagentTaskRecovery) callCount() int {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.calls
|
|
}
|
|
|
|
func TestRecoverInterruptedSubagentTasksRetriesBeforeTraffic(t *testing.T) {
|
|
recovery := &scriptedSubagentTaskRecovery{failures: 2, recovered: 3}
|
|
stores := &store.Stores{SubagentTaskRecovery: recovery}
|
|
recovered, err := recoverInterruptedSubagentTasks(
|
|
context.Background(), stores, time.Millisecond,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("recoverInterruptedSubagentTasks: %v", err)
|
|
}
|
|
if recovered != 3 || recovery.callCount() != 3 {
|
|
t.Fatalf("recovery = (%d rows, %d calls), want (3, 3)", recovered, recovery.callCount())
|
|
}
|
|
}
|
|
|
|
func TestRecoverInterruptedSubagentTasksStopsOnShutdown(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
recovery := &scriptedSubagentTaskRecovery{failures: 1}
|
|
stores := &store.Stores{SubagentTaskRecovery: recovery}
|
|
_, err := recoverInterruptedSubagentTasks(ctx, stores, time.Hour)
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("recovery error = %v, want context.Canceled", err)
|
|
}
|
|
if recovery.callCount() != 1 {
|
|
t.Fatalf("recovery calls = %d, want 1", recovery.callCount())
|
|
}
|
|
}
|