@swarmclawai/swarmclaw 1.4.7 → 1.4.9
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 +17 -2
- package/package.json +1 -1
- package/scripts/run-next-build.mjs +44 -0
- package/src/app/api/swarmfeed/actions/route.ts +101 -0
- package/src/app/api/swarmfeed/bookmarks/route.ts +30 -0
- package/src/app/api/swarmfeed/notifications/route.ts +30 -0
- package/src/app/api/swarmfeed/posts/[postId]/replies/route.ts +23 -0
- package/src/app/api/swarmfeed/posts/[postId]/route.ts +20 -0
- package/src/app/api/swarmfeed/posts/route.ts +12 -52
- package/src/app/api/swarmfeed/profiles/[agentId]/posts/route.ts +25 -0
- package/src/app/api/swarmfeed/profiles/[agentId]/route.ts +32 -0
- package/src/app/api/swarmfeed/route.ts +15 -13
- package/src/app/api/swarmfeed/search/route.ts +30 -0
- package/src/app/api/swarmfeed/suggested/route.ts +25 -0
- package/src/cli/index.js +11 -0
- package/src/features/swarmfeed/agent-social-settings.tsx +7 -1
- package/src/features/swarmfeed/compose-post.tsx +72 -87
- package/src/features/swarmfeed/feed-page.tsx +600 -76
- package/src/features/swarmfeed/post-card.tsx +205 -73
- package/src/features/swarmfeed/post-thread-sheet.tsx +170 -0
- package/src/features/swarmfeed/profile-sheet.tsx +179 -0
- package/src/features/swarmfeed/queries.ts +191 -8
- package/src/lib/server/agents/agent-swarm-registration.ts +31 -8
- package/src/lib/server/runtime/heartbeat-service.ts +8 -1
- package/src/lib/server/runtime/queue/core.ts +2 -0
- package/src/lib/server/session-tools/swarmfeed.ts +226 -63
- package/src/lib/server/storage-normalization.ts +1 -0
- package/src/lib/server/swarmfeed-runtime.test.ts +188 -0
- package/src/lib/server/swarmfeed-runtime.ts +131 -0
- package/src/lib/server/tasks/task-route-service.ts +2 -0
- package/src/lib/swarmfeed-client.ts +130 -28
- package/src/lib/tool-definitions.ts +1 -1
- package/src/types/agent.ts +1 -0
- package/src/types/swarmfeed.ts +105 -5
|
@@ -1,137 +1,122 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
-
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
6
|
-
import { submitPost, fetchChannels } from './queries'
|
|
3
|
+
import { useMemo, useState } from 'react'
|
|
7
4
|
import { toast } from 'sonner'
|
|
5
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
6
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
7
|
+
import { useSwarmFeedChannelsQuery, useSwarmFeedPostMutation } from './queries'
|
|
8
8
|
import type { Agent } from '@/types'
|
|
9
|
-
import type { SwarmFeedChannel, SwarmFeedPost } from '@/types/swarmfeed'
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
10
|
+
type Props = {
|
|
11
|
+
selectedAgentId?: string
|
|
12
|
+
onSelectAgent?: (agentId: string) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ComposePost({ selectedAgentId, onSelectAgent }: Props) {
|
|
15
16
|
const agents = useAppStore((s) => s.agents)
|
|
16
|
-
const feedAgents =
|
|
17
|
-
(
|
|
17
|
+
const feedAgents = useMemo(
|
|
18
|
+
() => Object.values(agents).filter((agent: Agent) => agent.swarmfeedEnabled && !agent.disabled && !agent.trashedAt),
|
|
19
|
+
[agents],
|
|
18
20
|
)
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
+
const channelsQuery = useSwarmFeedChannelsQuery()
|
|
22
|
+
const postMutation = useSwarmFeedPostMutation()
|
|
21
23
|
const [content, setContent] = useState('')
|
|
22
24
|
const [channelId, setChannelId] = useState('')
|
|
23
|
-
const [channels, setChannels] = useState<SwarmFeedChannel[]>([])
|
|
24
|
-
const [posting, setPosting] = useState(false)
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}, [])
|
|
26
|
+
const activeAgentId = selectedAgentId || feedAgents[0]?.id || ''
|
|
27
|
+
const activeAgent = activeAgentId ? agents[activeAgentId] : null
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
if (!
|
|
32
|
-
setPosting(true)
|
|
29
|
+
async function handleSubmit() {
|
|
30
|
+
if (!activeAgentId || !content.trim()) return
|
|
33
31
|
try {
|
|
34
|
-
|
|
32
|
+
await postMutation.mutateAsync({
|
|
33
|
+
agentId: activeAgentId,
|
|
34
|
+
input: { content: content.trim(), channelId: channelId || undefined },
|
|
35
|
+
})
|
|
35
36
|
toast.success('Post published')
|
|
36
37
|
setContent('')
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
toast.error('Failed to publish post')
|
|
41
|
-
} finally {
|
|
42
|
-
setPosting(false)
|
|
38
|
+
setChannelId('')
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
toast.error(err instanceof Error ? err.message : 'Failed to publish post')
|
|
43
41
|
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const selectedAgent = selectedAgentId ? agents[selectedAgentId] : null
|
|
42
|
+
}
|
|
47
43
|
|
|
48
44
|
return (
|
|
49
|
-
<div className="rounded-[20px] border border-white/[0.08] bg-surface p-5
|
|
50
|
-
<div className="flex items-center justify-between
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
>
|
|
57
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
58
|
-
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
59
|
-
</svg>
|
|
60
|
-
</button>
|
|
61
|
-
)}
|
|
45
|
+
<div className="rounded-[20px] border border-white/[0.08] bg-surface/80 p-5">
|
|
46
|
+
<div className="mb-4 flex items-center justify-between">
|
|
47
|
+
<div>
|
|
48
|
+
<h3 className="font-display text-[17px] font-700 tracking-[-0.02em] text-text">Compose</h3>
|
|
49
|
+
<p className="mt-1 text-[12px] text-text-3/70">Publish from any SwarmFeed-enabled agent.</p>
|
|
50
|
+
</div>
|
|
51
|
+
{postMutation.isPending && <span className="text-[11px] text-accent-bright">Publishing…</span>}
|
|
62
52
|
</div>
|
|
63
53
|
|
|
64
|
-
{/* Agent picker */}
|
|
65
54
|
<div className="mb-4">
|
|
66
|
-
<label className="block text-[
|
|
67
|
-
|
|
55
|
+
<label className="mb-2 block text-[11px] font-700 uppercase tracking-[0.12em] text-text-3/70">
|
|
56
|
+
Acting As
|
|
68
57
|
</label>
|
|
69
58
|
{feedAgents.length === 0 ? (
|
|
70
59
|
<p className="text-[13px] text-text-3/75">
|
|
71
|
-
No agents have SwarmFeed enabled.
|
|
60
|
+
No agents have SwarmFeed enabled yet. Turn it on in an agent's social settings first.
|
|
72
61
|
</p>
|
|
73
62
|
) : (
|
|
74
63
|
<div className="flex flex-wrap gap-2">
|
|
75
64
|
{feedAgents.map((agent) => (
|
|
76
65
|
<button
|
|
77
66
|
key={agent.id}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
type="button"
|
|
68
|
+
onClick={() => onSelectAgent?.(agent.id)}
|
|
69
|
+
className={`flex cursor-pointer items-center gap-2 rounded-[12px] border px-3 py-2 text-[13px] font-600 transition-all ${
|
|
70
|
+
activeAgentId === agent.id
|
|
71
|
+
? 'border-accent-bright/50 bg-accent-bright/10 text-accent-bright'
|
|
72
|
+
: 'border-white/[0.08] bg-transparent text-text-3 hover:bg-white/[0.04] hover:text-text'
|
|
73
|
+
}`}
|
|
84
74
|
>
|
|
85
|
-
<AgentAvatar
|
|
86
|
-
|
|
75
|
+
<AgentAvatar
|
|
76
|
+
seed={agent.avatarSeed || agent.id}
|
|
77
|
+
avatarUrl={agent.avatarUrl}
|
|
78
|
+
name={agent.name}
|
|
79
|
+
size={22}
|
|
80
|
+
/>
|
|
81
|
+
<span className="max-w-[140px] truncate">{agent.name}</span>
|
|
87
82
|
</button>
|
|
88
83
|
))}
|
|
89
84
|
</div>
|
|
90
85
|
)}
|
|
91
86
|
</div>
|
|
92
87
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
style={{ fontFamily: 'inherit' }}
|
|
101
|
-
maxLength={2000}
|
|
102
|
-
/>
|
|
103
|
-
<div className="mt-1 text-right text-[11px] text-text-3/40">{content.length}/2000</div>
|
|
104
|
-
</div>
|
|
88
|
+
<textarea
|
|
89
|
+
value={content}
|
|
90
|
+
onChange={(event) => setContent(event.target.value)}
|
|
91
|
+
placeholder={activeAgent ? `What is ${activeAgent.name} shipping, learning, or noticing?` : 'Write an update…'}
|
|
92
|
+
className="min-h-[130px] w-full resize-y rounded-[16px] border border-white/[0.08] bg-bg/70 px-4 py-3.5 text-[14px] leading-[1.6] text-text outline-none transition-all placeholder:text-text-3/50 focus-glow"
|
|
93
|
+
maxLength={2000}
|
|
94
|
+
/>
|
|
105
95
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<div className="mb-4">
|
|
109
|
-
<label className="block text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
110
|
-
Channel (optional)
|
|
111
|
-
</label>
|
|
96
|
+
<div className="mt-3 flex items-center justify-between gap-3">
|
|
97
|
+
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
112
98
|
<select
|
|
113
99
|
value={channelId}
|
|
114
|
-
onChange={(
|
|
115
|
-
className="w-
|
|
100
|
+
onChange={(event) => setChannelId(event.target.value)}
|
|
101
|
+
className="min-w-0 rounded-[12px] border border-white/[0.08] bg-bg/70 px-3 py-2 text-[12px] text-text outline-none"
|
|
116
102
|
style={{ fontFamily: 'inherit' }}
|
|
117
103
|
>
|
|
118
104
|
<option value="">No channel</option>
|
|
119
|
-
{
|
|
120
|
-
<option key={
|
|
105
|
+
{(channelsQuery.data || []).map((channel) => (
|
|
106
|
+
<option key={channel.id} value={channel.id}>
|
|
107
|
+
#{channel.handle} · {channel.displayName}
|
|
108
|
+
</option>
|
|
121
109
|
))}
|
|
122
110
|
</select>
|
|
111
|
+
<span className="text-[11px] text-text-3/50">{content.length}/2000</span>
|
|
123
112
|
</div>
|
|
124
|
-
)}
|
|
125
|
-
|
|
126
|
-
{/* Submit */}
|
|
127
|
-
<div className="flex justify-end">
|
|
128
113
|
<button
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={() => { void handleSubmit() }}
|
|
116
|
+
disabled={postMutation.isPending || !activeAgentId || !content.trim()}
|
|
117
|
+
className="cursor-pointer rounded-[12px] bg-accent-bright px-5 py-2.5 text-[13px] font-700 text-white transition-all hover:bg-accent-bright/90 disabled:cursor-not-allowed disabled:opacity-40"
|
|
133
118
|
>
|
|
134
|
-
|
|
119
|
+
Publish
|
|
135
120
|
</button>
|
|
136
121
|
</div>
|
|
137
122
|
</div>
|