picoclaw/pkg/bus/types.go
admin-mf 93fb7b8794 Merge upstream/main into sync/upstream-2026-05-07
Sync with sipeed/picoclaw upstream/main (~1095 commits since last merge),
including major restructures:

- Launcher migrated to web/backend (#1275): cmd/picoclaw-launcher/* removed.
  Our launcher is dropped (MagicForm doesn't use it; web/backend is the
  upstream replacement when needed).
- Agent loop split (#2585, 12d5421c): pkg/agent/loop.go deleted, replaced by
  pkg/agent/agent_*.go files. Multi-tenancy customizations to be forward-ported
  in follow-up commits on this branch.
- Tool packages reorganized (4c133dc2): pkg/tools/{filesystem,web}.go moved
  to pkg/tools/{fs,integration}/. Customizations to be forward-ported.
- Channels switched to map+Settings model: MagicFormConfig → MagicFormSettings
  registered in config_channel.go alongside other channels; channel constructor
  updated to (channelName, channelType, *Config, *MessageBus) factory pattern.
- Config schema updates: removed deprecated AgentDefaults.Model field;
  removed Config.Bindings (legacy migration in upstream); SessionConfig now
  uses Dimensions instead of DMScope; ExecConfig adds AllowRemote.

Conflicts resolved:
- pkg/agent/loop.go + loop_test.go: accepted upstream deletion (split)
- pkg/tools/{filesystem,web}.go: accepted upstream deletion (moved)
- cmd/picoclaw-launcher/*: dropped (deps deleted upstream, unused by MagicForm)
- cmd/picoclaw/internal/gateway/helpers.go: accepted upstream deletion
- pkg/channels/manager.go: switched to upstream's map-driven channel init
- pkg/config/config.go: merged AgentDefaults fields, kept WorkspaceRoot,
  removed deprecated Model
- pkg/bus/types.go: merged OutboundMessage with upstream's new fields
  (Context, AgentID, SessionKey, Scope, ContextUsage) plus our magicform
  callback fields (Type, Metrics, Progress, Escalation)
- pkg/tools/{cron,shell}.go: combined our customizations with upstream changes
- pkg/skills/installer.go: dropped our LimitReader path (upstream now uses
  chunked DownloadToFile which is size-bounded)

The magicform channel was rebuilt to match upstream's new API:
- *config.MagicFormSettings (SecureString token) instead of MagicFormConfig
- Send returns ([]string, error) per upstream Channel interface
- InboundMessage built with Context (with Raw map for tenancy hints) instead
  of bus.Peer + Metadata field which were removed.

Multi-tenancy hints (workspace_override, config_dir, allowed_tools,
allowed_skills) are still flowing via Context.Raw — the agent loop side of
the integration is forward-ported in a follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:12:44 -05:00

181 lines
7.5 KiB
Go

package bus
// SenderInfo provides structured sender identity information.
type SenderInfo struct {
Platform string `json:"platform,omitempty"` // "telegram", "discord", "slack", ...
PlatformID string `json:"platform_id,omitempty"` // raw platform ID, e.g. "123456"
CanonicalID string `json:"canonical_id,omitempty"` // "platform:id" format
Username string `json:"username,omitempty"` // username (e.g. @alice)
DisplayName string `json:"display_name,omitempty"` // display name
}
// InboundContext captures the normalized, platform-agnostic facts about an
// inbound message. This is the source of truth for routing and session
// allocation.
type InboundContext struct {
Channel string `json:"channel"`
Account string `json:"account,omitempty"`
ChatID string `json:"chat_id"`
ChatType string `json:"chat_type,omitempty"` // direct / group / channel
TopicID string `json:"topic_id,omitempty"`
SpaceID string `json:"space_id,omitempty"`
SpaceType string `json:"space_type,omitempty"` // guild / team / workspace / tenant
SenderID string `json:"sender_id"`
MessageID string `json:"message_id,omitempty"`
Mentioned bool `json:"mentioned,omitempty"`
ReplyToMessageID string `json:"reply_to_message_id,omitempty"`
ReplyToSenderID string `json:"reply_to_sender_id,omitempty"`
ReplyHandles map[string]string `json:"reply_handles,omitempty"`
Raw map[string]string `json:"raw,omitempty"`
}
type InboundMessage struct {
Context InboundContext `json:"context"`
Sender SenderInfo `json:"sender"`
Content string `json:"content"`
Media []string `json:"media,omitempty"`
MediaScope string `json:"media_scope,omitempty"` // media lifecycle scope
SessionKey string `json:"session_key"`
// Convenience mirrors derived from Context for runtime consumers.
Channel string `json:"channel"`
SenderID string `json:"sender_id"`
ChatID string `json:"chat_id"`
MessageID string `json:"message_id,omitempty"` // platform message ID
}
// OutboundScope captures the structured session scope associated with an
// outbound turn result without depending on the session package.
type OutboundScope struct {
Version int `json:"version,omitempty"`
AgentID string `json:"agent_id,omitempty"`
Channel string `json:"channel,omitempty"`
Account string `json:"account,omitempty"`
Dimensions []string `json:"dimensions,omitempty"`
Values map[string]string `json:"values,omitempty"`
}
// ContextUsage describes how much of the model's context window the current
// session consumes, and how far it is from triggering compression.
type ContextUsage struct {
UsedTokens int `json:"used_tokens"`
TotalTokens int `json:"total_tokens"` // model context window
CompressAtTokens int `json:"compress_at_tokens"` // threshold that triggers compression
UsedPercent int `json:"used_percent"` // 0-100
}
// Outbound message type constants identify the kind of outbound message.
// Channels that don't support progress/escalation treat all messages as content.
const (
MessageTypeFinal = "" // default: final response
MessageTypeProgress = "progress" // intermediate progress update
MessageTypeEscalation = "escalation" // needs human intervention
)
type OutboundMessage struct {
Channel string `json:"channel"`
ChatID string `json:"chat_id"`
Context InboundContext `json:"context"`
AgentID string `json:"agent_id,omitempty"`
SessionKey string `json:"session_key,omitempty"`
Scope *OutboundScope `json:"scope,omitempty"`
Content string `json:"content"`
ReplyToMessageID string `json:"reply_to_message_id,omitempty"`
ContextUsage *ContextUsage `json:"context_usage,omitempty"`
// Type distinguishes final responses from progress/escalation updates.
// Empty string (default) means final response. Channels that don't support
// progress callbacks ignore this field and treat all messages as content.
Type string `json:"type,omitempty"`
// Metrics is populated by the agent loop for the final response message.
// Channels that support rich callbacks (e.g. MagicForm) use this to include
// execution metrics in their response payloads. Nil for intermediate messages.
Metrics *ResponseMetrics `json:"metrics,omitempty"`
// Progress is populated for Type="progress" messages.
Progress *OutboundProgress `json:"progress,omitempty"`
// Escalation is populated for Type="escalation" messages.
Escalation *OutboundEscalation `json:"escalation,omitempty"`
}
// OutboundProgress carries progress update details.
type OutboundProgress struct {
Status string `json:"status"`
ToolName string `json:"toolName,omitempty"`
StepNumber int `json:"stepNumber,omitempty"`
Message string `json:"message,omitempty"`
}
// OutboundEscalation carries escalation details.
type OutboundEscalation struct {
Reason string `json:"reason"`
Notes string `json:"notes,omitempty"`
}
// TokenUsage tracks LLM token consumption across all iterations in a turn.
type TokenUsage struct {
PromptTokens int `json:"promptTokens"`
CompletionTokens int `json:"completionTokens"`
TotalTokens int `json:"totalTokens"`
Model string `json:"model"`
Provider string `json:"provider,omitempty"`
}
// ResponseMetrics captures execution metrics for a single agent processing turn.
type ResponseMetrics struct {
DurationMs int64 `json:"durationMs"`
TokenUsage *TokenUsage `json:"tokenUsage,omitempty"`
ToolCalls int `json:"toolCalls"`
Iterations int `json:"iterations"`
Model string `json:"model,omitempty"`
}
// MediaPart describes a single media attachment to send.
type MediaPart struct {
Type string `json:"type"` // "image" | "audio" | "video" | "file"
Ref string `json:"ref"` // media store ref, e.g. "media://abc123"
Caption string `json:"caption,omitempty"` // optional caption text
Filename string `json:"filename,omitempty"` // original filename hint
ContentType string `json:"content_type,omitempty"` // MIME type hint
}
// OutboundMediaMessage carries media attachments from Agent to channels via the bus.
type OutboundMediaMessage struct {
Channel string `json:"channel"`
ChatID string `json:"chat_id"`
Context InboundContext `json:"context"`
AgentID string `json:"agent_id,omitempty"`
SessionKey string `json:"session_key,omitempty"`
Scope *OutboundScope `json:"scope,omitempty"`
Parts []MediaPart `json:"parts"`
}
// AudioChunk represents a chunk of streaming voice data.
type AudioChunk struct {
SessionID string `json:"session_id"`
SpeakerID string `json:"speaker_id"` // User ID or SSRC
ChatID string `json:"chat_id"` // Where to respond
Channel string `json:"channel"` // Source channel type (e.g. "discord")
Sequence uint64 `json:"sequence"`
Timestamp uint32 `json:"timestamp"`
SampleRate int `json:"sample_rate"`
Channels int `json:"channels"`
Format string `json:"format"` // "opus", "pcm", etc
Data []byte `json:"data"`
}
// VoiceControl represents state or commands for voice sessions.
type VoiceControl struct {
SessionID string `json:"session_id"`
ChatID string `json:"chat_id"`
Type string `json:"type"` // "state", "command"
Action string `json:"action"` // "idle", "listening", "start", "stop", "leave"
}