mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-17 06:17:30 +00:00
* fix(telegram): propagate local_key for forum topic routing in team notifications
Team task status messages (dispatched, completed, progress) were always
delivered to the General topic in Telegram forum groups because the
notification pipeline had no access to the originating topic's local_key.
Root cause: wireTeamProgressNotifySubscriber in gateway_events.go published
OutboundMessage with no Metadata, so the Telegram adapter had no
message_thread_id to route to the correct forum topic.
Fix has two parts:
1. Team notify path (root cause):
- Add LocalKey field to TeamTaskEventPayload (protocol)
- Extract local_key from tool context in WithContextInfo()
- Add LocalKey to NotifyRoutingMeta
- Pass LocalKey through to OutboundMessage Metadata in both
leader mode (InboundMessage) and direct mode (OutboundMessage)
2. MCP bridge context (supporting):
- Propagate local_key and session_key through bridge HTTP headers
- Add X-Local-Key and X-Session-Key to BridgeContext
- Extract and inject into tool context in gateway middleware
- Include in HMAC signature for integrity
Closes #798
* fix(telegram): pass LocalKey from task metadata in all dispatch/fail broadcast sites
The initial fix added LocalKey to the event payload and WithContextInfo(),
but 4 broadcast call sites use individual With* options instead of
WithContextInfo — so LocalKey was never populated for:
- fallback_dispatch (team_tasks_create.go)
- dispatch_unblocked (team_tool_dispatch.go)
- post_turn dispatch (team_tool_validation.go)
- blocker/fail (team_tasks_blocker.go)
Add WithLocalKey() option function and extract TaskMetaLocalKey from task
metadata at each site, matching the existing TaskMetaPeerKind pattern.
* test(mcp): add HMAC verification tests for extra params (localKey, sessionKey)
- Add tests for SignBridgeContext/VerifyBridgeContext with extra params
- Test backward compat fallback for pre-localKey sessions
- Test that param order matters in signature
- Add clarifying comment for routing context injection security model
---------
Co-authored-by: Jens Henke <jens@henke.dk>
Co-authored-by: viettranx <viettranx@gmail.com>
207 lines
8.9 KiB
Go
207 lines
8.9 KiB
Go
package protocol
|
|
|
|
// DelegationEventPayload is the typed payload for delegation lifecycle events
|
|
// (started, completed, failed, cancelled).
|
|
type DelegationEventPayload struct {
|
|
DelegationID string `json:"delegation_id"`
|
|
SourceAgentID string `json:"source_agent_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
SourceDisplayName string `json:"source_display_name,omitempty"`
|
|
TargetAgentID string `json:"target_agent_id"`
|
|
TargetAgentKey string `json:"target_agent_key"`
|
|
TargetDisplayName string `json:"target_display_name,omitempty"`
|
|
UserID string `json:"user_id"`
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
Mode string `json:"mode"`
|
|
Task string `json:"task,omitempty"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
TeamTaskID string `json:"team_task_id,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
ElapsedMS int `json:"elapsed_ms,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// DelegationProgressItem describes one active delegation in a progress report.
|
|
type DelegationProgressItem struct {
|
|
DelegationID string `json:"delegation_id"`
|
|
TargetAgentKey string `json:"target_agent_key"`
|
|
TargetDisplayName string `json:"target_display_name,omitempty"`
|
|
ElapsedMS int `json:"elapsed_ms"`
|
|
TeamTaskID string `json:"team_task_id,omitempty"`
|
|
Activity string `json:"activity,omitempty"` // "thinking", "tool_exec", "compacting"
|
|
Tool string `json:"tool,omitempty"` // current tool name (when Activity == "tool_exec")
|
|
}
|
|
|
|
// DelegationProgressPayload is emitted periodically for async delegations.
|
|
type DelegationProgressPayload struct {
|
|
SourceAgentID string `json:"source_agent_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
UserID string `json:"user_id"`
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
Active []DelegationProgressItem `json:"active_delegations"`
|
|
}
|
|
|
|
// DelegationAccumulatedPayload is emitted when an async delegation completes
|
|
// but siblings are still running (result accumulated, not yet announced).
|
|
type DelegationAccumulatedPayload struct {
|
|
DelegationID string `json:"delegation_id"`
|
|
SourceAgentID string `json:"source_agent_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
TargetAgentKey string `json:"target_agent_key"`
|
|
TargetDisplayName string `json:"target_display_name,omitempty"`
|
|
UserID string `json:"user_id"`
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
TeamTaskID string `json:"team_task_id,omitempty"`
|
|
SiblingsRemaining int `json:"siblings_remaining"`
|
|
ElapsedMS int `json:"elapsed_ms,omitempty"`
|
|
}
|
|
|
|
// DelegationAnnounceResultSummary describes one delegation result in the announce payload.
|
|
type DelegationAnnounceResultSummary struct {
|
|
AgentKey string `json:"agent_key"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
HasMedia bool `json:"has_media"`
|
|
ContentPreview string `json:"content_preview,omitempty"`
|
|
}
|
|
|
|
// DelegationAnnouncePayload is emitted when the last sibling delegation completes
|
|
// and all accumulated results are announced back to the lead agent.
|
|
type DelegationAnnouncePayload struct {
|
|
SourceAgentID string `json:"source_agent_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
SourceDisplayName string `json:"source_display_name,omitempty"`
|
|
UserID string `json:"user_id"`
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
Results []DelegationAnnounceResultSummary `json:"results"`
|
|
CompletedTaskIDs []string `json:"completed_task_ids,omitempty"`
|
|
TotalElapsedMS int `json:"total_elapsed_ms"`
|
|
HasMedia bool `json:"has_media"`
|
|
}
|
|
|
|
// TeamTaskEventPayload is the typed payload for team task lifecycle events
|
|
// (created, claimed, completed, cancelled, approved, rejected).
|
|
type TeamTaskEventPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
TaskID string `json:"task_id"`
|
|
TaskNumber int `json:"task_number,omitempty"`
|
|
Subject string `json:"subject,omitempty"`
|
|
Status string `json:"status"`
|
|
OwnerAgentKey string `json:"owner_agent_key,omitempty"`
|
|
OwnerDisplayName string `json:"owner_display_name,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
UserID string `json:"user_id"`
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
PeerKind string `json:"peer_kind,omitempty"` // "group" or "direct" — for correct session routing (#266)
|
|
LocalKey string `json:"local_key,omitempty"`
|
|
Timestamp string `json:"timestamp"`
|
|
|
|
// Comment text preview (for team.task.commented events, truncated).
|
|
CommentText string `json:"comment_text,omitempty"`
|
|
|
|
// Progress (for team.task.progress events).
|
|
ProgressPercent int `json:"progress_percent,omitempty"`
|
|
ProgressStep string `json:"progress_step,omitempty"`
|
|
|
|
// Actor info for audit trail (recorded to team_task_events by subscriber).
|
|
ActorType string `json:"actor_type,omitempty"` // "agent", "human", "system"
|
|
ActorID string `json:"actor_id,omitempty"` // agent key, user ID, or system identifier
|
|
}
|
|
|
|
// TeamMessageEventPayload is the typed payload for team.message.sent events.
|
|
type TeamMessageEventPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
FromAgentKey string `json:"from_agent_key"`
|
|
FromDisplayName string `json:"from_display_name,omitempty"`
|
|
ToAgentKey string `json:"to_agent_key"`
|
|
ToDisplayName string `json:"to_display_name,omitempty"`
|
|
MessageType string `json:"message_type"`
|
|
Preview string `json:"preview"`
|
|
TaskID string `json:"task_id,omitempty"`
|
|
UserID string `json:"user_id"`
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
}
|
|
|
|
// --- Team CRUD event payloads ---
|
|
|
|
// TeamCreatedPayload is emitted when a new team is created via RPC.
|
|
type TeamCreatedPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
TeamName string `json:"team_name"`
|
|
LeadAgentKey string `json:"lead_agent_key"`
|
|
LeadDisplayName string `json:"lead_display_name,omitempty"`
|
|
MemberCount int `json:"member_count"`
|
|
}
|
|
|
|
// TeamUpdatedPayload is emitted when team settings are updated via RPC.
|
|
type TeamUpdatedPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
TeamName string `json:"team_name"`
|
|
Changes []string `json:"changes"`
|
|
}
|
|
|
|
// TeamDeletedPayload is emitted when a team is deleted via RPC.
|
|
type TeamDeletedPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
TeamName string `json:"team_name"`
|
|
}
|
|
|
|
// TeamMemberAddedPayload is emitted when a member is added to a team.
|
|
type TeamMemberAddedPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
TeamName string `json:"team_name"`
|
|
AgentID string `json:"agent_id"`
|
|
AgentKey string `json:"agent_key"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
Role string `json:"role"`
|
|
}
|
|
|
|
// TeamMemberRemovedPayload is emitted when a member is removed from a team.
|
|
type TeamMemberRemovedPayload struct {
|
|
TeamID string `json:"team_id"`
|
|
TeamName string `json:"team_name"`
|
|
AgentID string `json:"agent_id"`
|
|
AgentKey string `json:"agent_key"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
}
|
|
|
|
// --- Agent link event payloads ---
|
|
|
|
// AgentLinkCreatedPayload is emitted when a new agent link is created.
|
|
type AgentLinkCreatedPayload struct {
|
|
LinkID string `json:"link_id"`
|
|
SourceAgentID string `json:"source_agent_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
TargetAgentID string `json:"target_agent_id"`
|
|
TargetAgentKey string `json:"target_agent_key"`
|
|
Direction string `json:"direction"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
// AgentLinkUpdatedPayload is emitted when an agent link is updated.
|
|
type AgentLinkUpdatedPayload struct {
|
|
LinkID string `json:"link_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
TargetAgentKey string `json:"target_agent_key"`
|
|
Direction string `json:"direction,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
Changes []string `json:"changes"`
|
|
}
|
|
|
|
// AgentLinkDeletedPayload is emitted when an agent link is deleted.
|
|
type AgentLinkDeletedPayload struct {
|
|
LinkID string `json:"link_id"`
|
|
SourceAgentKey string `json:"source_agent_key"`
|
|
TargetAgentKey string `json:"target_agent_key"`
|
|
}
|