Files
Duc Nguyenandntduc bb7712a9ff fix(collaboration): harden delegated task isolation (#1486)
* 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>
2026-07-30 14:17:40 +07:00

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,
}
}