mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-19 06:15:06 +00:00
fix(teams): propagate peer_kind and local_key through task dispatch chain
Team task announce was writing to wrong session (direct instead of group) because origin_peer_kind was hardcoded as "direct" in dispatch metadata. This caused leaders to miss completed task results in group conversations. - Store peer_kind and local_key in task metadata at creation time - Resolve peer_kind from context → metadata → "direct" fallback in all dispatch paths (tool, gateway, unblocked) - Use actual origPeerKind in announce handler session key + request - Add origin_local_key to gateway dispatch for forum topic routing - Clarify ask_user guidance: bot must present question directly - Guide members to use team_tasks progress instead of team_message - Improve error message when non-owner calls progress action
This commit is contained in:
1 parent
0857321a6b
commit
0dc3124607
5 files changed
+54
-8
No files matched your search
@@ -660,14 +660,23 @@ func handleTeammateMessage(
|
||||
slog.Warn("teammate announce: no origin_chat_id, cannot announce to lead")
|
||||
return
|
||||
}
|
||||
leadSessionKey := sessions.BuildScopedSessionKey(leadAgent, origCh, sessions.PeerDirect, origChatID, cfg.Sessions.Scope, cfg.Sessions.DmScope, cfg.Sessions.MainKey)
|
||||
// Use the original peer kind so announce writes to the correct session
|
||||
// (group session for group chats, direct session for DMs).
|
||||
origPeerKind := inMeta["origin_peer_kind"]
|
||||
if origPeerKind == "" {
|
||||
origPeerKind = string(sessions.PeerDirect)
|
||||
}
|
||||
origLocalKey := inMeta["origin_local_key"]
|
||||
leadSessionKey := sessions.BuildScopedSessionKey(leadAgent, origCh, sessions.PeerKind(origPeerKind), origChatID, cfg.Sessions.Scope, cfg.Sessions.DmScope, cfg.Sessions.MainKey)
|
||||
leadSessionKey = overrideSessionKeyFromLocalKey(leadSessionKey, origLocalKey, leadAgent, origCh, origChatID, origPeerKind)
|
||||
|
||||
announceReq := agent.RunRequest{
|
||||
SessionKey: leadSessionKey,
|
||||
Message: announceContent,
|
||||
Channel: origCh,
|
||||
ChatID: origChatID,
|
||||
PeerKind: string(sessions.PeerDirect),
|
||||
PeerKind: origPeerKind,
|
||||
LocalKey: origLocalKey,
|
||||
UserID: inMeta["origin_user_id"],
|
||||
RunID: fmt.Sprintf("teammate-announce-%s", memberAgent),
|
||||
RunKind: "announce",
|
||||
|
||||
@@ -116,6 +116,7 @@ func buildTeamMD(team *store.TeamData, members []store.TeamMemberData, selfID uu
|
||||
|
||||
sb.WriteString("\n## Follow-up Reminders\n\n")
|
||||
sb.WriteString("When you need user input/decision: create+claim task, then `ask_user` with text=<question>. ONLY use when you have a question for the user — NOT for waiting on teammates or status updates.\n")
|
||||
sb.WriteString("IMPORTANT: Present the question directly to the user in your response. `ask_user` only sets up periodic REMINDERS in case they don't reply — it does NOT present the question for you.\n")
|
||||
sb.WriteString("System auto-sends reminders. Call `clear_ask_user` when user replies.\n")
|
||||
} else {
|
||||
sb.WriteString("Create a task with `team_tasks` (with `assignee`), then the system dispatches automatically.\n")
|
||||
@@ -133,7 +134,7 @@ func buildTeamMD(team *store.TeamData, members []store.TeamMemberData, selfID uu
|
||||
sb.WriteString("You are a **reviewer**. When evaluating, respond with **APPROVED** or **REJECTED: <feedback>**.\n\n")
|
||||
}
|
||||
sb.WriteString("As a member, just do the assigned work. Task completion is automatic.\n")
|
||||
sb.WriteString("For long-running tasks, send progress updates via `team_message` action=send.\n")
|
||||
sb.WriteString("For long-running tasks, use `team_tasks(action=\"progress\", percent=50, text=\"status update\")` to report progress. The task_id is auto-resolved from your assigned task — you don't need to specify it.\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
|
||||
@@ -680,20 +680,31 @@ func (m *TeamsMethods) dispatchTaskToAgent(ctx context.Context, task *store.Team
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve peer kind from task metadata; fallback to "direct" for old tasks.
|
||||
originPeerKind := "direct"
|
||||
if task.Metadata != nil {
|
||||
if pk, ok := task.Metadata["peer_kind"].(string); ok && pk != "" {
|
||||
originPeerKind = pk
|
||||
}
|
||||
}
|
||||
|
||||
meta := map[string]string{
|
||||
"origin_channel": originChannel,
|
||||
"origin_peer_kind": "direct",
|
||||
"origin_peer_kind": originPeerKind,
|
||||
"origin_chat_id": task.ChatID,
|
||||
"from_agent": fromAgent,
|
||||
"to_agent": ag.AgentKey,
|
||||
"team_task_id": taskID.String(),
|
||||
"team_id": teamID.String(),
|
||||
}
|
||||
// Pass team workspace from task metadata so member uses the team's workspace.
|
||||
// Pass team workspace and local key from task metadata.
|
||||
if task.Metadata != nil {
|
||||
if ws, _ := task.Metadata["team_workspace"].(string); ws != "" {
|
||||
meta["team_workspace"] = ws
|
||||
}
|
||||
if lk, _ := task.Metadata["local_key"].(string); lk != "" {
|
||||
meta["origin_local_key"] = lk
|
||||
}
|
||||
}
|
||||
|
||||
m.msgBus.PublishInbound(bus.InboundMessage{
|
||||
|
||||
@@ -117,6 +117,14 @@ func (t *TeamTasksTool) executeCreate(ctx context.Context, args map[string]any)
|
||||
}
|
||||
taskMeta["original_blocked_by"] = ids
|
||||
}
|
||||
// Store peer kind so dispatches preserve the correct session scope (group vs direct).
|
||||
if pk := ToolPeerKindFromCtx(ctx); pk != "" {
|
||||
taskMeta["peer_kind"] = pk
|
||||
}
|
||||
// Store local key so forum-topic routing works on deferred/unblocked dispatches.
|
||||
if lk := ToolLocalKeyFromCtx(ctx); lk != "" {
|
||||
taskMeta["local_key"] = lk
|
||||
}
|
||||
// Store leader's trace context so unblocked dispatch links back to the leader's trace.
|
||||
if traceID := tracing.TraceIDFromContext(ctx); traceID != uuid.Nil {
|
||||
taskMeta["origin_trace_id"] = traceID.String()
|
||||
@@ -278,7 +286,7 @@ func (t *TeamTasksTool) executeProgress(ctx context.Context, args map[string]any
|
||||
return ErrorResult("task does not belong to your team")
|
||||
}
|
||||
if task.OwnerAgentID == nil || *task.OwnerAgentID != agentID {
|
||||
return ErrorResult("only the task owner can update progress")
|
||||
return ErrorResult("only the assigned task owner can update progress. As team lead, task results arrive automatically when members complete their work.")
|
||||
}
|
||||
|
||||
if err := t.manager.teamStore.UpdateTaskProgress(ctx, taskID, team.ID, percent, step); err != nil {
|
||||
|
||||
@@ -95,9 +95,19 @@ func (m *TeamToolManager) dispatchTaskToAgent(ctx context.Context, task *store.T
|
||||
originUserID = originChatID
|
||||
}
|
||||
|
||||
// Resolve peer kind from context; fallback to task metadata, then "direct".
|
||||
originPeerKind := ToolPeerKindFromCtx(ctx)
|
||||
if originPeerKind == "" {
|
||||
if pk, ok := task.Metadata["peer_kind"].(string); ok && pk != "" {
|
||||
originPeerKind = pk
|
||||
} else {
|
||||
originPeerKind = "direct"
|
||||
}
|
||||
}
|
||||
|
||||
meta := map[string]string{
|
||||
"origin_channel": originChannel,
|
||||
"origin_peer_kind": "direct",
|
||||
"origin_peer_kind": originPeerKind,
|
||||
"origin_chat_id": originChatID,
|
||||
"origin_user_id": originUserID,
|
||||
"from_agent": fromAgent,
|
||||
@@ -105,7 +115,14 @@ func (m *TeamToolManager) dispatchTaskToAgent(ctx context.Context, task *store.T
|
||||
"team_task_id": task.ID.String(),
|
||||
"team_id": teamID.String(),
|
||||
}
|
||||
if localKey := ToolLocalKeyFromCtx(ctx); localKey != "" {
|
||||
// Resolve local key from context; fallback to task metadata for deferred dispatches.
|
||||
localKey := ToolLocalKeyFromCtx(ctx)
|
||||
if localKey == "" {
|
||||
if lk, ok := task.Metadata["local_key"].(string); ok {
|
||||
localKey = lk
|
||||
}
|
||||
}
|
||||
if localKey != "" {
|
||||
meta["origin_local_key"] = localKey
|
||||
}
|
||||
// Pass the team workspace dir so member agents write files to the shared folder.
|
||||
|
||||
Reference in new issue
Block a user