@swarmclawai/swarmclaw 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import { getAgent, patchAgent } from '@/lib/server/agents/agent-repository'
4
+ import { createPost, getFeed, likePost, repostPost, getChannels, registerAgent } from '@/lib/swarmfeed-client'
5
+ import { log } from '@/lib/server/logger'
6
+ import type { ToolBuildContext } from './context'
7
+ import type { Agent } from '@/types'
8
+
9
+ const TAG = 'swarmfeed-tool'
10
+
11
+ const SWARMFEED_SCHEMA = z.object({
12
+ action: z.enum(['post', 'reply', 'like', 'repost', 'browse_feed', 'get_channels']).describe(
13
+ 'The SwarmFeed action to perform',
14
+ ),
15
+ content: z.string().optional().describe('Post content (required for post/reply)'),
16
+ postId: z.string().optional().describe('Post ID (required for reply/like/repost)'),
17
+ channelId: z.string().optional().describe('Channel ID for posting to a channel or browsing a channel feed'),
18
+ feedType: z.enum(['for_you', 'following', 'trending', 'channel']).optional().describe('Feed type for browse_feed (default: for_you)'),
19
+ limit: z.number().optional().describe('Number of posts to fetch for browse_feed (default: 10)'),
20
+ })
21
+
22
+ type SwarmFeedInput = z.infer<typeof SWARMFEED_SCHEMA>
23
+
24
+ async function ensureApiKey(agent: Agent): Promise<string> {
25
+ if (agent.swarmfeedApiKey) return agent.swarmfeedApiKey
26
+
27
+ log.info(TAG, `Auto-registering agent "${agent.name}" on SwarmFeed`)
28
+ const reg = await registerAgent({
29
+ name: agent.name,
30
+ description: agent.description || agent.swarmfeedBio || `${agent.name} agent on SwarmClaw`,
31
+ framework: 'swarmclaw',
32
+ model: agent.model,
33
+ avatar: agent.avatarUrl || undefined,
34
+ bio: agent.swarmfeedBio || undefined,
35
+ })
36
+
37
+ patchAgent(agent.id, (current) => {
38
+ if (!current) return null
39
+ return {
40
+ ...current,
41
+ swarmfeedApiKey: reg.apiKey,
42
+ swarmfeedAgentId: reg.agentId,
43
+ swarmfeedJoinedAt: current.swarmfeedJoinedAt ?? Date.now(),
44
+ updatedAt: Date.now(),
45
+ }
46
+ })
47
+
48
+ return reg.apiKey
49
+ }
50
+
51
+ async function executeSwarmFeed(input: SwarmFeedInput, bctx: ToolBuildContext): Promise<string> {
52
+ const agentId = bctx.ctx?.agentId
53
+ if (!agentId) return JSON.stringify({ error: 'No agent context' })
54
+
55
+ const agent = getAgent(agentId) as Agent | undefined
56
+ if (!agent) return JSON.stringify({ error: 'Agent not found' })
57
+ if (!agent.swarmfeedEnabled) return JSON.stringify({ error: 'SwarmFeed is not enabled for this agent' })
58
+
59
+ try {
60
+ switch (input.action) {
61
+ case 'post': {
62
+ if (!input.content?.trim()) return JSON.stringify({ error: 'content is required for post action' })
63
+ const apiKey = await ensureApiKey(agent)
64
+ const post = await createPost(apiKey, {
65
+ content: input.content.trim(),
66
+ channelId: input.channelId,
67
+ })
68
+ return JSON.stringify({ success: true, post: { id: post.id, content: post.content, createdAt: post.createdAt } })
69
+ }
70
+
71
+ case 'reply': {
72
+ if (!input.content?.trim()) return JSON.stringify({ error: 'content is required for reply action' })
73
+ if (!input.postId) return JSON.stringify({ error: 'postId is required for reply action' })
74
+ const apiKey = await ensureApiKey(agent)
75
+ const reply = await createPost(apiKey, {
76
+ content: input.content.trim(),
77
+ parentId: input.postId,
78
+ channelId: input.channelId,
79
+ })
80
+ return JSON.stringify({ success: true, post: { id: reply.id, content: reply.content, parentId: input.postId, createdAt: reply.createdAt } })
81
+ }
82
+
83
+ case 'like': {
84
+ if (!input.postId) return JSON.stringify({ error: 'postId is required for like action' })
85
+ const apiKey = await ensureApiKey(agent)
86
+ await likePost(apiKey, input.postId)
87
+ return JSON.stringify({ success: true, action: 'liked', postId: input.postId })
88
+ }
89
+
90
+ case 'repost': {
91
+ if (!input.postId) return JSON.stringify({ error: 'postId is required for repost action' })
92
+ const apiKey = await ensureApiKey(agent)
93
+ await repostPost(apiKey, input.postId)
94
+ return JSON.stringify({ success: true, action: 'reposted', postId: input.postId })
95
+ }
96
+
97
+ case 'browse_feed': {
98
+ const feedType = input.feedType || 'for_you'
99
+ const limit = input.limit || 10
100
+ const apiKey = agent.swarmfeedApiKey || undefined
101
+ const result = await getFeed(feedType, { channelId: input.channelId, limit }, apiKey)
102
+ const posts = result.posts.map((p) => ({
103
+ id: p.id,
104
+ agent: p.agentId,
105
+ content: p.content.slice(0, 500),
106
+ likes: p.likeCount,
107
+ replies: p.replyCount,
108
+ reposts: p.repostCount,
109
+ createdAt: p.createdAt,
110
+ }))
111
+ return JSON.stringify({ posts, nextCursor: result.nextCursor })
112
+ }
113
+
114
+ case 'get_channels': {
115
+ const channels = await getChannels()
116
+ return JSON.stringify({ channels: channels.map((c) => ({ id: c.id, name: c.displayName, handle: c.handle })) })
117
+ }
118
+
119
+ default:
120
+ return JSON.stringify({ error: `Unknown action: ${input.action}` })
121
+ }
122
+ } catch (err: unknown) {
123
+ const message = err instanceof Error ? err.message : 'Unknown error'
124
+ log.error(TAG, `Action "${input.action}" failed for agent "${agent.name}": ${message}`)
125
+ return JSON.stringify({ error: message })
126
+ }
127
+ }
128
+
129
+ export function buildSwarmFeedTools(bctx: ToolBuildContext): StructuredToolInterface[] {
130
+ // Only provide tool if the agent has SwarmFeed enabled
131
+ const agentId = bctx.ctx?.agentId
132
+ if (!agentId) return []
133
+
134
+ const agent = getAgent(agentId) as Agent | undefined
135
+ if (!agent?.swarmfeedEnabled) return []
136
+
137
+ return [
138
+ tool(
139
+ async (args) => executeSwarmFeed(args as SwarmFeedInput, bctx),
140
+ {
141
+ name: 'swarmfeed',
142
+ description:
143
+ 'Interact with SwarmFeed, the social network for AI agents. ' +
144
+ 'Actions: post (publish a post), reply (reply to a post), like, repost, ' +
145
+ 'browse_feed (read the feed), get_channels (list available channels).',
146
+ schema: SWARMFEED_SCHEMA,
147
+ },
148
+ ),
149
+ ]
150
+ }
@@ -528,6 +528,16 @@ function normalizeStoredRecordInner(
528
528
  if (typeof agent.swarmfeedAgentId !== 'string' && agent.swarmfeedAgentId !== null) agent.swarmfeedAgentId = null
529
529
  if (!agent.origin) agent.origin = 'swarmclaw'
530
530
  if (agent.swarmfeedHeartbeat === undefined) agent.swarmfeedHeartbeat = null
531
+ // SwarmDock defaults
532
+ if (typeof agent.swarmdockEnabled !== 'boolean') agent.swarmdockEnabled = false
533
+ if (agent.swarmdockListedAt === undefined) agent.swarmdockListedAt = null
534
+ if (typeof agent.swarmdockDescription !== 'string' && agent.swarmdockDescription !== null) agent.swarmdockDescription = null
535
+ if (!Array.isArray(agent.swarmdockSkills)) agent.swarmdockSkills = []
536
+ if (typeof agent.swarmdockWalletId !== 'string' && agent.swarmdockWalletId !== null) agent.swarmdockWalletId = null
537
+ if (typeof agent.swarmdockAgentId !== 'string' && agent.swarmdockAgentId !== null) agent.swarmdockAgentId = null
538
+ if (typeof agent.swarmdockDid !== 'string' && agent.swarmdockDid !== null) agent.swarmdockDid = null
539
+ if (typeof agent.swarmdockApiKey !== 'string' && agent.swarmdockApiKey !== null) agent.swarmdockApiKey = null
540
+ if (agent.swarmdockMarketplace === undefined) agent.swarmdockMarketplace = null
531
541
  // Org chart normalization
532
542
  if (agent.orgChart && typeof agent.orgChart === 'object' && !Array.isArray(agent.orgChart)) {
533
543
  const oc = agent.orgChart as Record<string, unknown>
@@ -29,6 +29,8 @@ export const AVAILABLE_TOOLS: ToolDefinition[] = [
29
29
  { id: 'email', label: 'Email', description: 'Send emails via SMTP with plain text and HTML support', extensionId: 'email' },
30
30
  { id: 'replicate', label: 'Replicate', description: 'Run any AI model on Replicate — image generation, LLMs, audio, video, and more', extensionId: 'replicate' },
31
31
  { id: 'google_workspace', label: 'Google Workspace', description: 'Run Google Workspace CLI (`gws`) commands for Drive, Docs, Sheets, Gmail, Calendar, Chat, and more', extensionId: 'google_workspace' },
32
+ { id: 'swarmfeed', label: 'SwarmFeed', description: 'Post, reply, like, repost, and browse the SwarmFeed social network (auto-enabled when SwarmFeed is on)' },
33
+ { id: 'swarmdock', label: 'SwarmDock', description: 'Browse tasks, check status, and manage marketplace profile on SwarmDock (auto-enabled when SwarmDock is on)' },
32
34
  ]
33
35
 
34
36
  /**
@@ -14,6 +14,18 @@ export interface SwarmFeedHeartbeatConfig {
14
14
  channelsToMonitor: string[]
15
15
  }
16
16
 
17
+ // --- SwarmDock Marketplace ---
18
+
19
+ export interface SwarmDockMarketplaceConfig {
20
+ enabled: boolean
21
+ autoDiscover: boolean
22
+ maxBudgetUsdc: string
23
+ autoBid: boolean
24
+ autoBidMaxPrice: string
25
+ taskNotifications: boolean
26
+ preferredCategories: string[]
27
+ }
28
+
17
29
  // --- Agent / Delegation ---
18
30
 
19
31
  export type AgentRole = 'worker' | 'coordinator'
@@ -203,6 +215,17 @@ export interface Agent {
203
215
  origin?: 'swarmdock' | 'swarmfeed' | 'swarmclaw' | 'external'
204
216
  swarmfeedHeartbeat?: SwarmFeedHeartbeatConfig | null
205
217
 
218
+ // SwarmDock (marketplace integration)
219
+ swarmdockEnabled?: boolean
220
+ swarmdockListedAt?: number | null
221
+ swarmdockDescription?: string | null
222
+ swarmdockSkills?: string[]
223
+ swarmdockWalletId?: string | null
224
+ swarmdockAgentId?: string | null
225
+ swarmdockDid?: string | null
226
+ swarmdockApiKey?: string | null
227
+ swarmdockMarketplace?: SwarmDockMarketplaceConfig | null
228
+
206
229
  createdAt: number
207
230
  updatedAt: number
208
231
  }
@@ -214,4 +214,4 @@ export type SessionTool =
214
214
  | 'crawl'
215
215
 
216
216
  export type SessionType = 'human'
217
- export type AppView = 'home' | 'agents' | 'org_chart' | 'inbox' | 'chatrooms' | 'protocols' | 'schedules' | 'memory' | 'tasks' | 'secrets' | 'wallets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'extensions' | 'usage' | 'runs' | 'autonomy' | 'logs' | 'settings' | 'projects' | 'activity' | 'swarmfeed'
217
+ export type AppView = 'home' | 'agents' | 'org_chart' | 'inbox' | 'chatrooms' | 'protocols' | 'schedules' | 'memory' | 'tasks' | 'secrets' | 'wallets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'extensions' | 'usage' | 'runs' | 'autonomy' | 'logs' | 'settings' | 'projects' | 'activity' | 'swarmfeed' | 'marketplace'