feat(tools): enhance SpawnStatusTool to restrict task visibility by conversation context
This commit is contained in:
parent
e60b456f18
commit
2ce7aebec1
1 changed files with 56 additions and 4 deletions
|
|
@ -50,6 +50,12 @@ func (t *SpawnStatusTool) Execute(ctx context.Context, args map[string]any) *Too
|
||||||
return ErrorResult("Subagent manager not configured")
|
return ErrorResult("Subagent manager not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive the calling conversation's identity so we can scope results to the
|
||||||
|
// current chat only — preventing cross-conversation task leakage in
|
||||||
|
// multi-user deployments.
|
||||||
|
callerChannel := ToolChannel(ctx)
|
||||||
|
callerChatID := ToolChatID(ctx)
|
||||||
|
|
||||||
taskID, _ := args["task_id"].(string)
|
taskID, _ := args["task_id"].(string)
|
||||||
taskID = strings.TrimSpace(taskID)
|
taskID = strings.TrimSpace(taskID)
|
||||||
|
|
||||||
|
|
@ -58,21 +64,63 @@ func (t *SpawnStatusTool) Execute(ctx context.Context, args map[string]any) *Too
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrorResult(fmt.Sprintf("No subagent found with task ID: %s", taskID))
|
return ErrorResult(fmt.Sprintf("No subagent found with task ID: %s", taskID))
|
||||||
}
|
}
|
||||||
return NewToolResult(spawnStatusFormatTask(task))
|
|
||||||
|
// Snapshot before formatting to avoid racing with the subagent goroutine.
|
||||||
|
taskCopy := *task
|
||||||
|
|
||||||
|
// Restrict lookup to tasks that belong to this conversation.
|
||||||
|
if callerChannel != "" && taskCopy.OriginChannel != "" && taskCopy.OriginChannel != callerChannel {
|
||||||
|
return ErrorResult(fmt.Sprintf("No subagent found with task ID: %s", taskID))
|
||||||
|
}
|
||||||
|
if callerChatID != "" && taskCopy.OriginChatID != "" && taskCopy.OriginChatID != callerChatID {
|
||||||
|
return ErrorResult(fmt.Sprintf("No subagent found with task ID: %s", taskID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewToolResult(spawnStatusFormatTask(&taskCopy))
|
||||||
|
}
|
||||||
|
|
||||||
|
origTasks := t.manager.ListTasks()
|
||||||
|
if len(origTasks) == 0 {
|
||||||
|
return NewToolResult("No subagents have been spawned yet.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot each task to avoid reading concurrently mutated state via shared
|
||||||
|
// pointers once the manager lock is released.
|
||||||
|
tasks := make([]*SubagentTask, 0, len(origTasks))
|
||||||
|
for _, task := range origTasks {
|
||||||
|
if task == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cpy := *task
|
||||||
|
|
||||||
|
// Filter to tasks that originate from the current conversation only.
|
||||||
|
if callerChannel != "" && cpy.OriginChannel != "" && cpy.OriginChannel != callerChannel {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if callerChatID != "" && cpy.OriginChatID != "" && cpy.OriginChatID != callerChatID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks = append(tasks, &cpy)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks := t.manager.ListTasks()
|
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
return NewToolResult("No subagents have been spawned yet.")
|
return NewToolResult("No subagents have been spawned yet.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deterministic ordering: sort by ID string (e.g. "subagent-1" < "subagent-2").
|
// Deterministic ordering: sort by ID string (e.g. "subagent-1" < "subagent-2").
|
||||||
sort.Slice(tasks, func(i, j int) bool {
|
sort.Slice(tasks, func(i, j int) bool {
|
||||||
|
if tasks[i] == nil || tasks[j] == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return tasks[i].ID < tasks[j].ID
|
return tasks[i].ID < tasks[j].ID
|
||||||
})
|
})
|
||||||
|
|
||||||
counts := map[string]int{}
|
counts := map[string]int{}
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
|
if task == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
counts[task.Status]++
|
counts[task.Status]++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +135,9 @@ func (t *SpawnStatusTool) Execute(ctx context.Context, args map[string]any) *Too
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
|
if task == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
sb.WriteString(spawnStatusFormatTask(task))
|
sb.WriteString(spawnStatusFormatTask(task))
|
||||||
sb.WriteString("\n\n")
|
sb.WriteString("\n\n")
|
||||||
}
|
}
|
||||||
|
|
@ -117,8 +168,9 @@ func spawnStatusFormatTask(task *SubagentTask) string {
|
||||||
if task.Result != "" {
|
if task.Result != "" {
|
||||||
result := task.Result
|
result := task.Result
|
||||||
const maxResultLen = 300
|
const maxResultLen = 300
|
||||||
if len(result) > maxResultLen {
|
runes := []rune(result)
|
||||||
result = result[:maxResultLen] + "…"
|
if len(runes) > maxResultLen {
|
||||||
|
result = string(runes[:maxResultLen]) + "…"
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf("\n result: %s", result))
|
sb.WriteString(fmt.Sprintf("\n result: %s", result))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue