feat: enable dynamic swarm event relaying to original channels (Telegram/Discord)
This commit is contained in:
parent
e4700cd201
commit
acac61ac2f
5 changed files with 55 additions and 22 deletions
|
|
@ -84,9 +84,9 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range al.swarm.Outbound {
|
for msg := range al.swarm.Outbound {
|
||||||
al.bus.PublishOutbound(bus.OutboundMessage{
|
al.bus.PublishOutbound(bus.OutboundMessage{
|
||||||
Channel: "cli", // Default to CLI for now, ideally dynamic
|
Channel: msg.Channel,
|
||||||
ChatID: "direct",
|
ChatID: msg.ChatID,
|
||||||
Content: msg,
|
Content: msg.Content,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -104,7 +104,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
|
|
||||||
// Intercept /swarm commands
|
// Intercept /swarm commands
|
||||||
if al.swarm != nil && len(msg.Content) > 6 && msg.Content[:7] == "/swarm " {
|
if al.swarm != nil && len(msg.Content) > 6 && msg.Content[:7] == "/swarm " {
|
||||||
response := al.swarm.HandleCommand(ctx, msg.Content)
|
response := al.swarm.HandleCommand(ctx, msg.Channel, msg.ChatID, msg.Content)
|
||||||
al.bus.PublishOutbound(bus.OutboundMessage{
|
al.bus.PublishOutbound(bus.OutboundMessage{
|
||||||
Channel: msg.Channel,
|
Channel: msg.Channel,
|
||||||
ChatID: msg.ChatID,
|
ChatID: msg.ChatID,
|
||||||
|
|
@ -141,7 +141,7 @@ func (al *AgentLoop) Stop() {
|
||||||
func (al *AgentLoop) ProcessDirect(ctx context.Context, content, sessionKey string) (string, error) {
|
func (al *AgentLoop) ProcessDirect(ctx context.Context, content, sessionKey string) (string, error) {
|
||||||
// Intercept /swarm commands
|
// Intercept /swarm commands
|
||||||
if al.swarm != nil && len(content) > 6 && content[:7] == "/swarm " {
|
if al.swarm != nil && len(content) > 6 && content[:7] == "/swarm " {
|
||||||
return al.swarm.HandleCommand(ctx, content), nil
|
return al.swarm.HandleCommand(ctx, "cli", "direct", content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,12 @@ const (
|
||||||
|
|
||||||
// --- Core Structs ---
|
// --- Core Structs ---
|
||||||
type Swarm struct {
|
type Swarm struct {
|
||||||
ID SwarmID `json:"id"`
|
ID SwarmID `json:"id"`
|
||||||
Goal string `json:"goal"`
|
Goal string `json:"goal"`
|
||||||
Status SwarmStatus `json:"status"`
|
Status SwarmStatus `json:"status"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
OriginChannel string `json:"origin_channel"`
|
||||||
|
OriginChatID string `json:"origin_chat_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
|
|
@ -126,3 +128,10 @@ type FactResult struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Score float32 `json:"score"`
|
Score float32 `json:"score"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Relay Types ---
|
||||||
|
type RelayMessage struct {
|
||||||
|
Channel string
|
||||||
|
ChatID string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLiteStore) init() error {
|
func (s *SQLiteStore) init() error {
|
||||||
if _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS swarms (id TEXT PRIMARY KEY, goal TEXT, status TEXT, created_at DATETIME);`); err != nil {
|
if _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS swarms (id TEXT PRIMARY KEY, goal TEXT, status TEXT, created_at DATETIME, origin_channel TEXT, origin_chat_id TEXT);`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, swarm_id TEXT, parent_id TEXT, role JSON, task TEXT, status TEXT, output TEXT, stats JSON);`); err != nil {
|
if _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, swarm_id TEXT, parent_id TEXT, role JSON, task TEXT, status TEXT, output TEXT, stats JSON);`); err != nil {
|
||||||
|
|
@ -37,14 +37,14 @@ func (s *SQLiteStore) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLiteStore) CreateSwarm(ctx context.Context, sw *core.Swarm) error {
|
func (s *SQLiteStore) CreateSwarm(ctx context.Context, sw *core.Swarm) error {
|
||||||
_, err := s.db.ExecContext(ctx, "INSERT INTO swarms (id, goal, status, created_at) VALUES (?, ?, ?, ?)", sw.ID, sw.Goal, sw.Status, sw.CreatedAt)
|
_, err := s.db.ExecContext(ctx, "INSERT INTO swarms (id, goal, status, created_at, origin_channel, origin_chat_id) VALUES (?, ?, ?, ?, ?, ?)", sw.ID, sw.Goal, sw.Status, sw.CreatedAt, sw.OriginChannel, sw.OriginChatID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLiteStore) GetSwarm(ctx context.Context, id core.SwarmID) (*core.Swarm, error) {
|
func (s *SQLiteStore) GetSwarm(ctx context.Context, id core.SwarmID) (*core.Swarm, error) {
|
||||||
row := s.db.QueryRowContext(ctx, "SELECT id, goal, status, created_at FROM swarms WHERE id=?", id)
|
row := s.db.QueryRowContext(ctx, "SELECT id, goal, status, created_at, origin_channel, origin_chat_id FROM swarms WHERE id=?", id)
|
||||||
var sw core.Swarm
|
var sw core.Swarm
|
||||||
if err := row.Scan(&sw.ID, &sw.Goal, &sw.Status, &sw.CreatedAt); err != nil {
|
if err := row.Scan(&sw.ID, &sw.Goal, &sw.Status, &sw.CreatedAt, &sw.OriginChannel, &sw.OriginChatID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &sw, nil
|
return &sw, nil
|
||||||
|
|
@ -56,7 +56,7 @@ func (s *SQLiteStore) UpdateSwarm(ctx context.Context, sw *core.Swarm) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLiteStore) ListSwarms(ctx context.Context, status core.SwarmStatus) ([]*core.Swarm, error) {
|
func (s *SQLiteStore) ListSwarms(ctx context.Context, status core.SwarmStatus) ([]*core.Swarm, error) {
|
||||||
rows, err := s.db.QueryContext(ctx, "SELECT id, goal, status, created_at FROM swarms WHERE status=?", status)
|
rows, err := s.db.QueryContext(ctx, "SELECT id, goal, status, created_at, origin_channel, origin_chat_id FROM swarms WHERE status=?", status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ func (s *SQLiteStore) ListSwarms(ctx context.Context, status core.SwarmStatus) (
|
||||||
var list []*core.Swarm
|
var list []*core.Swarm
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var sw core.Swarm
|
var sw core.Swarm
|
||||||
if err := rows.Scan(&sw.ID, &sw.Goal, &sw.Status, &sw.CreatedAt); err != nil {
|
if err := rows.Scan(&sw.ID, &sw.Goal, &sw.Status, &sw.CreatedAt, &sw.OriginChannel, &sw.OriginChatID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
list = append(list, &sw)
|
list = append(list, &sw)
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,12 @@ func NewOrchestrator(store core.SwarmStore, bus core.EventBus, llm core.LLMClien
|
||||||
|
|
||||||
func (o *Orchestrator) SetSharedMemory(m core.SharedMemory) { o.memory = m }
|
func (o *Orchestrator) SetSharedMemory(m core.SharedMemory) { o.memory = m }
|
||||||
|
|
||||||
func (o *Orchestrator) SpawnSwarm(ctx context.Context, goal string) (core.SwarmID, error) {
|
func (o *Orchestrator) SpawnSwarm(ctx context.Context, goal, channel, chatID string) (core.SwarmID, error) {
|
||||||
id := core.SwarmID(uuid.New().String())
|
id := core.SwarmID(uuid.New().String())
|
||||||
if err := o.store.CreateSwarm(ctx, &core.Swarm{ID: id, Goal: goal, Status: core.SwarmStatusActive, CreatedAt: time.Now()}); err != nil {
|
if err := o.store.CreateSwarm(ctx, &core.Swarm{
|
||||||
|
ID: id, Goal: goal, Status: core.SwarmStatusActive, CreatedAt: time.Now(),
|
||||||
|
OriginChannel: channel, OriginChatID: chatID,
|
||||||
|
}); err != nil {
|
||||||
return "", fmt.Errorf("failed to create swarm in store: %w", err)
|
return "", fmt.Errorf("failed to create swarm in store: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type Service struct {
|
||||||
Orchestrator *runtime.Orchestrator
|
Orchestrator *runtime.Orchestrator
|
||||||
Store core.SwarmStore
|
Store core.SwarmStore
|
||||||
Bus core.EventBus
|
Bus core.EventBus
|
||||||
Outbound chan string
|
Outbound chan core.RelayMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(dbPath string, provider providers.LLMProvider, registry *tools.ToolRegistry, cfg config.SwarmConfig, model string) (*Service, error) {
|
func NewService(dbPath string, provider providers.LLMProvider, registry *tools.ToolRegistry, cfg config.SwarmConfig, model string) (*Service, error) {
|
||||||
|
|
@ -35,7 +35,7 @@ func NewService(dbPath string, provider providers.LLMProvider, registry *tools.T
|
||||||
orch := runtime.NewOrchestrator(store, eventBus, adapter, registry, cfg, model)
|
orch := runtime.NewOrchestrator(store, eventBus, adapter, registry, cfg, model)
|
||||||
orch.SetSharedMemory(sharedMem)
|
orch.SetSharedMemory(sharedMem)
|
||||||
|
|
||||||
s := &Service{Orchestrator: orch, Store: store, Bus: eventBus, Outbound: make(chan string, 100)}
|
s := &Service{Orchestrator: orch, Store: store, Bus: eventBus, Outbound: make(chan core.RelayMessage, 100)}
|
||||||
s.listen()
|
s.listen()
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
@ -49,21 +49,42 @@ func (s *Service) listen() {
|
||||||
case core.EventNodeFailed: msg = fmt.Sprintf("❌ [%s] Failed: %v", e.NodeID[:4], e.Payload["error"])
|
case core.EventNodeFailed: msg = fmt.Sprintf("❌ [%s] Failed: %v", e.NodeID[:4], e.Payload["error"])
|
||||||
}
|
}
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
select { case s.Outbound <- msg: default: }
|
// Get swarm origin
|
||||||
|
sw, err := s.Store.GetSwarm(context.Background(), e.SwarmID)
|
||||||
|
if err == nil && sw.OriginChannel != "" {
|
||||||
|
select {
|
||||||
|
case s.Outbound <- core.RelayMessage{
|
||||||
|
Channel: sw.OriginChannel,
|
||||||
|
ChatID: sw.OriginChatID,
|
||||||
|
Content: msg,
|
||||||
|
}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to CLI
|
||||||
|
select {
|
||||||
|
case s.Outbound <- core.RelayMessage{
|
||||||
|
Channel: "cli",
|
||||||
|
ChatID: "direct",
|
||||||
|
Content: msg,
|
||||||
|
}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fmt.Printf("Warning: Swarm event subscription failed: %v\n", err)
|
fmt.Printf("Warning: Swarm event subscription failed: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HandleCommand(ctx context.Context, input string) string {
|
func (s *Service) HandleCommand(ctx context.Context, channel, chatID, input string) string {
|
||||||
args := strings.Fields(input)
|
args := strings.Fields(input)
|
||||||
if len(args) < 2 { return "Usage: /swarm <spawn|list|stop|status|viz> [goal/id]" }
|
if len(args) < 2 { return "Usage: /swarm <spawn|list|stop|status|viz> [goal/id]" }
|
||||||
|
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "spawn":
|
case "spawn":
|
||||||
goal := strings.Join(args[2:], " ")
|
goal := strings.Join(args[2:], " ")
|
||||||
id, err := s.Orchestrator.SpawnSwarm(ctx, goal)
|
id, err := s.Orchestrator.SpawnSwarm(ctx, goal, channel, chatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("❌ Failed to spawn swarm: %v", err)
|
return fmt.Sprintf("❌ Failed to spawn swarm: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue