@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.
- package/README.md +9 -0
- package/package.json +1 -1
- package/scripts/run-next-build.mjs +46 -0
- package/src/app/api/swarmdock/route.ts +25 -0
- package/src/app/api/swarmfeed/posts/route.ts +44 -6
- package/src/app/api/swarmfeed/route.ts +4 -0
- package/src/app/marketplace/page.tsx +7 -0
- package/src/cli/index.js +7 -0
- package/src/components/agents/agent-sheet.tsx +10 -0
- package/src/components/layout/sidebar-rail.tsx +5 -0
- package/src/features/swarmdock/agent-marketplace-settings.tsx +303 -0
- package/src/features/swarmdock/marketplace-page.tsx +189 -0
- package/src/features/swarmfeed/feed-page.tsx +3 -33
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/view-constants.ts +9 -1
- package/src/lib/server/agents/agent-service.ts +18 -0
- package/src/lib/server/agents/agent-swarm-registration.ts +35 -0
- package/src/lib/server/connectors/swarmdock.ts +28 -6
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/swarmdock.ts +104 -0
- package/src/lib/server/session-tools/swarmfeed.ts +150 -0
- package/src/lib/server/storage-normalization.ts +10 -0
- package/src/lib/tool-definitions.ts +2 -0
- package/src/types/agent.ts +23 -0
- package/src/types/session.ts +1 -1
|
@@ -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
|
/**
|
package/src/types/agent.ts
CHANGED
|
@@ -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
|
}
|
package/src/types/session.ts
CHANGED
|
@@ -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'
|