mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 20:15:42 +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>
94 lines
2.0 KiB
Go
94 lines
2.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
|
)
|
|
|
|
const asyncCompletionAttempts = 3
|
|
|
|
const (
|
|
asyncTerminalPersistenceAttempts = 12
|
|
asyncPersistenceAttemptTimeout = 2 * time.Second
|
|
asyncPersistenceMaxBackoff = time.Second
|
|
)
|
|
|
|
// PublishAsyncCompletion retries only after child execution has released its
|
|
// admission lease. The durable task row remains the fallback if every attempt
|
|
// finds the inbound queue saturated.
|
|
func PublishAsyncCompletion(
|
|
ctx context.Context,
|
|
msgBus *bus.MessageBus,
|
|
message bus.InboundMessage,
|
|
) bool {
|
|
if msgBus == nil {
|
|
return true
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
for attempt := range asyncCompletionAttempts {
|
|
if msgBus.TryPublishInbound(message) {
|
|
return true
|
|
}
|
|
if attempt == asyncCompletionAttempts-1 {
|
|
break
|
|
}
|
|
timer := time.NewTimer(time.Duration(attempt+1) * 10 * time.Millisecond)
|
|
select {
|
|
case <-ctx.Done():
|
|
timer.Stop()
|
|
return false
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func retryAsyncPersistence(
|
|
ctx context.Context,
|
|
operation func(context.Context) error,
|
|
) error {
|
|
return retryPersistence(ctx, asyncCompletionAttempts, operation)
|
|
}
|
|
|
|
func retryTerminalPersistence(
|
|
ctx context.Context,
|
|
operation func(context.Context) error,
|
|
) error {
|
|
return retryPersistence(ctx, asyncTerminalPersistenceAttempts, operation)
|
|
}
|
|
|
|
func retryPersistence(
|
|
ctx context.Context,
|
|
attempts int,
|
|
operation func(context.Context) error,
|
|
) error {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
var err error
|
|
for attempt := range attempts {
|
|
attemptCtx, cancel := context.WithTimeout(ctx, asyncPersistenceAttemptTimeout)
|
|
err = operation(attemptCtx)
|
|
cancel()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if attempt == attempts-1 {
|
|
break
|
|
}
|
|
backoff := min(time.Duration(1<<attempt)*25*time.Millisecond, asyncPersistenceMaxBackoff)
|
|
timer := time.NewTimer(backoff)
|
|
select {
|
|
case <-ctx.Done():
|
|
timer.Stop()
|
|
return err
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
return err
|
|
}
|