mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 14:15:00 +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>
73 lines
2.0 KiB
Go
73 lines
2.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"cmp"
|
|
"slices"
|
|
)
|
|
|
|
// SubagentRosterEntry summarizes one subagent task for the roster.
|
|
type SubagentRosterEntry struct {
|
|
Label string
|
|
Status string // "running", "completed", "failed", "cancelled"
|
|
}
|
|
|
|
// SubagentRoster holds the full roster of subagent tasks for a parent,
|
|
// including per-agent config limits for deterministic LLM context.
|
|
type SubagentRoster struct {
|
|
Entries []SubagentRosterEntry
|
|
Active int // currently running tasks for this parent
|
|
Total int // retained tasks for this parent
|
|
MaxPerAgent int // from spawnConfig.MaxChildrenPerAgent
|
|
}
|
|
|
|
// RosterForParent returns the full roster for one tenant/root-agent tree.
|
|
// Sorted: completed/failed/cancelled first, then running (deterministic output).
|
|
func (sm *SubagentManager) RosterForParent(scope TaskScope) SubagentRoster {
|
|
sm.mu.RLock()
|
|
defer sm.mu.RUnlock()
|
|
|
|
var entries []SubagentRosterEntry
|
|
active := 0
|
|
maxPerAgent := sm.config.MaxChildrenPerAgent
|
|
var latestCreatedAt int64
|
|
var latestID string
|
|
for _, t := range sm.tasks {
|
|
if !taskMatchesScope(t, scope) {
|
|
continue
|
|
}
|
|
entries = append(entries, SubagentRosterEntry{
|
|
Label: t.Label,
|
|
Status: t.Status,
|
|
})
|
|
if isActiveTaskStatus(t.Status) {
|
|
active++
|
|
}
|
|
if t.CreatedAt > latestCreatedAt || (t.CreatedAt == latestCreatedAt && t.ID > latestID) {
|
|
latestCreatedAt = t.CreatedAt
|
|
latestID = t.ID
|
|
maxPerAgent = t.spawnConfig.MaxChildrenPerAgent
|
|
}
|
|
}
|
|
|
|
// Sort: non-running first (completed/failed/cancelled), then running.
|
|
// Within same group, sort alphabetically by label for determinism.
|
|
slices.SortFunc(entries, func(a, b SubagentRosterEntry) int {
|
|
aRunning := a.Status == TaskStatusRunning
|
|
bRunning := b.Status == TaskStatusRunning
|
|
if aRunning != bRunning {
|
|
if aRunning {
|
|
return 1 // running goes last
|
|
}
|
|
return -1
|
|
}
|
|
return cmp.Compare(a.Label, b.Label)
|
|
})
|
|
|
|
return SubagentRoster{
|
|
Entries: entries,
|
|
Active: active,
|
|
Total: len(entries),
|
|
MaxPerAgent: maxPerAgent,
|
|
}
|
|
}
|