@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.
Files changed (34) hide show
  1. package/README.md +17 -2
  2. package/package.json +1 -1
  3. package/scripts/run-next-build.mjs +44 -0
  4. package/src/app/api/swarmfeed/actions/route.ts +101 -0
  5. package/src/app/api/swarmfeed/bookmarks/route.ts +30 -0
  6. package/src/app/api/swarmfeed/notifications/route.ts +30 -0
  7. package/src/app/api/swarmfeed/posts/[postId]/replies/route.ts +23 -0
  8. package/src/app/api/swarmfeed/posts/[postId]/route.ts +20 -0
  9. package/src/app/api/swarmfeed/posts/route.ts +12 -52
  10. package/src/app/api/swarmfeed/profiles/[agentId]/posts/route.ts +25 -0
  11. package/src/app/api/swarmfeed/profiles/[agentId]/route.ts +32 -0
  12. package/src/app/api/swarmfeed/route.ts +15 -13
  13. package/src/app/api/swarmfeed/search/route.ts +30 -0
  14. package/src/app/api/swarmfeed/suggested/route.ts +25 -0
  15. package/src/cli/index.js +11 -0
  16. package/src/features/swarmfeed/agent-social-settings.tsx +7 -1
  17. package/src/features/swarmfeed/compose-post.tsx +72 -87
  18. package/src/features/swarmfeed/feed-page.tsx +600 -76
  19. package/src/features/swarmfeed/post-card.tsx +205 -73
  20. package/src/features/swarmfeed/post-thread-sheet.tsx +170 -0
  21. package/src/features/swarmfeed/profile-sheet.tsx +179 -0
  22. package/src/features/swarmfeed/queries.ts +191 -8
  23. package/src/lib/server/agents/agent-swarm-registration.ts +31 -8
  24. package/src/lib/server/runtime/heartbeat-service.ts +8 -1
  25. package/src/lib/server/runtime/queue/core.ts +2 -0
  26. package/src/lib/server/session-tools/swarmfeed.ts +226 -63
  27. package/src/lib/server/storage-normalization.ts +1 -0
  28. package/src/lib/server/swarmfeed-runtime.test.ts +188 -0
  29. package/src/lib/server/swarmfeed-runtime.ts +131 -0
  30. package/src/lib/server/tasks/task-route-service.ts +2 -0
  31. package/src/lib/swarmfeed-client.ts +130 -28
  32. package/src/lib/tool-definitions.ts +1 -1
  33. package/src/types/agent.ts +1 -0
  34. package/src/types/swarmfeed.ts +105 -5
@@ -0,0 +1,131 @@
1
+ import { dispatchWake } from '@/lib/server/runtime/wake-dispatcher'
2
+ import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
3
+ import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
4
+ import { getAgent, patchAgent } from '@/lib/server/agents/agent-repository'
5
+ import type { Agent, BoardTask } from '@/types'
6
+
7
+ const DAY_MS = 24 * 60 * 60 * 1000
8
+
9
+ function formatChannels(channelIds: string[] | undefined): string {
10
+ if (!Array.isArray(channelIds) || channelIds.length === 0) return 'any relevant channel'
11
+ return channelIds.map((id) => `#${id}`).join(', ')
12
+ }
13
+
14
+ export function buildSwarmFeedHeartbeatGuidance(agent: Agent | null | undefined): string {
15
+ if (!agent?.swarmfeedEnabled || !agent.swarmfeedHeartbeat?.enabled) return ''
16
+
17
+ const config = agent.swarmfeedHeartbeat
18
+ const lines = [
19
+ '### SwarmFeed Social Guidance',
20
+ 'SwarmFeed is enabled for this agent. Use the built-in `swarmfeed` tool only when the policy below allows it.',
21
+ ]
22
+
23
+ if (agent.heartbeatEnabled !== true) {
24
+ lines.push('SwarmFeed social automation is configured but currently inactive because the agent heartbeat is disabled.')
25
+ lines.push('Do not do autonomous SwarmFeed work until the general heartbeat is enabled again.')
26
+ return lines.join('\n')
27
+ }
28
+
29
+ if (config.browseFeed) {
30
+ lines.push(`Browse the feed when helpful, prioritizing ${formatChannels(config.channelsToMonitor)}.`)
31
+ } else {
32
+ lines.push('Do not browse SwarmFeed unless the recent event context or direct user/task context makes it necessary.')
33
+ }
34
+
35
+ if (config.autoReply) {
36
+ lines.push('Auto-reply is allowed, but only when there is a specific mention, thread, or high-signal post worth responding to.')
37
+ } else {
38
+ lines.push('Do not reply automatically unless the user explicitly asked for it.')
39
+ }
40
+
41
+ if (config.autoFollow) {
42
+ lines.push('Auto-follow is allowed only after you first browsed or searched supporting context during this tick.')
43
+ } else {
44
+ lines.push('Do not auto-follow agents during this tick.')
45
+ }
46
+
47
+ switch (config.postFrequency) {
48
+ case 'manual_only':
49
+ lines.push('Posting policy: manual only. Do not author new SwarmFeed posts or replies during autonomous heartbeat work.')
50
+ break
51
+ case 'daily':
52
+ if (typeof agent.swarmfeedLastAutoPostAt === 'number' && Date.now() - agent.swarmfeedLastAutoPostAt < DAY_MS) {
53
+ lines.push('Posting policy: daily. A daily auto-post already happened in the last 24 hours, so do not author another new SwarmFeed post this tick.')
54
+ } else {
55
+ lines.push(`Posting policy: daily. At most one authored SwarmFeed post this tick, ideally in ${formatChannels(agent.swarmfeedAutoPostChannels)}.`)
56
+ }
57
+ break
58
+ case 'on_task_completion':
59
+ lines.push('Posting policy: on task completion. Only author a new SwarmFeed post if this tick was triggered by a newly completed task or the recent event context explicitly references a completed task.')
60
+ break
61
+ case 'every_cycle':
62
+ lines.push(`Posting policy: every cycle. You may author at most one SwarmFeed post this tick if there is a worthwhile update for ${formatChannels(agent.swarmfeedAutoPostChannels)}.`)
63
+ break
64
+ }
65
+
66
+ lines.push('Hard limits: max one authored SwarmFeed post this tick, no recursive reply chains, and skip SwarmFeed entirely when there is nothing socially useful to add.')
67
+ return lines.join('\n')
68
+ }
69
+
70
+ export function canAutoPostToSwarmFeed(agent: Agent | null | undefined): { allowed: boolean; reason?: string } {
71
+ if (!agent?.swarmfeedEnabled || !agent.swarmfeedHeartbeat?.enabled || agent.heartbeatEnabled !== true) {
72
+ return { allowed: true }
73
+ }
74
+ switch (agent.swarmfeedHeartbeat.postFrequency) {
75
+ case 'manual_only':
76
+ return { allowed: false, reason: 'SwarmFeed heartbeat is set to manual_only for this agent.' }
77
+ case 'daily':
78
+ if (typeof agent.swarmfeedLastAutoPostAt === 'number' && Date.now() - agent.swarmfeedLastAutoPostAt < DAY_MS) {
79
+ return { allowed: false, reason: 'This agent already made its daily autonomous SwarmFeed post in the last 24 hours.' }
80
+ }
81
+ return { allowed: true }
82
+ default:
83
+ return { allowed: true }
84
+ }
85
+ }
86
+
87
+ export function markSwarmFeedAutoPost(agentId: string): void {
88
+ patchAgent(agentId, (agent) => {
89
+ if (!agent) return null
90
+ return {
91
+ ...agent,
92
+ swarmfeedLastAutoPostAt: Date.now(),
93
+ updatedAt: Date.now(),
94
+ }
95
+ })
96
+ }
97
+
98
+ function summarizeTask(task: BoardTask): string {
99
+ const title = task.title.trim() || task.id
100
+ const result = typeof task.result === 'string' ? task.result.trim() : ''
101
+ if (!result) return `Completed task: ${title}.`
102
+ return `Completed task: ${title}. Result summary: ${result.slice(0, 300)}`
103
+ }
104
+
105
+ export function queueSwarmFeedTaskCompletionWake(task: BoardTask): void {
106
+ const agent = task.agentId ? (getAgent(task.agentId) as Agent | undefined) : undefined
107
+ if (!agent?.swarmfeedEnabled || !agent.swarmfeedHeartbeat?.enabled) return
108
+ if (agent.heartbeatEnabled !== true) return
109
+ if (agent.swarmfeedHeartbeat.postFrequency !== 'on_task_completion') return
110
+
111
+ const session = ensureAgentThreadSession(agent.id, 'default', agent)
112
+ if (!session) return
113
+
114
+ const summary = summarizeTask(task)
115
+ enqueueSystemEvent(
116
+ session.id,
117
+ `${summary} Consider whether it merits one concise SwarmFeed update in ${formatChannels(agent.swarmfeedAutoPostChannels)}.`,
118
+ `swarmfeed-task:${task.id}`,
119
+ )
120
+
121
+ dispatchWake({
122
+ mode: 'immediate',
123
+ agentId: agent.id,
124
+ sessionId: session.id,
125
+ eventId: `swarmfeed-task:${task.id}`,
126
+ reason: 'task-completed-social',
127
+ source: `swarmfeed:${task.id}`,
128
+ resumeMessage: `A completed task may merit one SwarmFeed update: ${task.title}`,
129
+ detail: summary,
130
+ })
131
+ }
@@ -25,6 +25,7 @@ import {
25
25
  import { resolveTaskAgentFromDescription } from '@/lib/server/tasks/task-mention'
26
26
  import { applyTaskPatch, prepareTaskCreation } from '@/lib/server/tasks/task-service'
27
27
  import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
28
+ import { queueSwarmFeedTaskCompletionWake } from '@/lib/server/swarmfeed-runtime'
28
29
  import { notify } from '@/lib/server/ws-hub'
29
30
  import type { BoardTask, BoardTaskStatus, TaskComment } from '@/types'
30
31
  import type { ServiceResult } from '@/lib/server/service-result'
@@ -152,6 +153,7 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
152
153
  { taskId: id, result: tasks[id].result },
153
154
  { enabledIds: agentExtensions },
154
155
  )
156
+ queueSwarmFeedTaskCompletionWake(tasks[id])
155
157
  }
156
158
 
157
159
  if (tasks[id].sessionId) {
@@ -1,5 +1,18 @@
1
1
  import { hmrSingleton } from '@/lib/shared-utils'
2
- import type { SwarmFeedPost, SwarmFeedChannel, CreatePostInput, FeedType } from '@/types/swarmfeed'
2
+ import type {
3
+ CreatePostInput,
4
+ FeedType,
5
+ SwarmFeedChannel,
6
+ SwarmFeedFeedResponse,
7
+ SwarmFeedFollowState,
8
+ SwarmFeedNotificationsResponse,
9
+ SwarmFeedPost,
10
+ SwarmFeedProfile,
11
+ SwarmFeedReactionType,
12
+ SwarmFeedSearchResponse,
13
+ SwarmFeedSearchType,
14
+ SwarmFeedSuggestedResponse,
15
+ } from '@/types/swarmfeed'
3
16
 
4
17
  interface SwarmFeedConfig {
5
18
  apiUrl: string
@@ -9,10 +22,6 @@ const config = hmrSingleton<SwarmFeedConfig>('swarmfeed_config', () => ({
9
22
  apiUrl: process.env.SWARMFEED_API_URL || 'https://swarmfeed-api.onrender.com',
10
23
  }))
11
24
 
12
- /**
13
- * Internal fetch helper for SwarmFeed API.
14
- * @param agentApiKey - Per-agent API key (sf_live_*) for authenticated requests. Omit for public endpoints.
15
- */
16
25
  async function sfFetch<T>(path: string, agentApiKey?: string, init?: RequestInit): Promise<T> {
17
26
  const url = `${config.apiUrl}${path}`
18
27
  const headers: Record<string, string> = {
@@ -27,11 +36,10 @@ async function sfFetch<T>(path: string, agentApiKey?: string, init?: RequestInit
27
36
  const text = await res.text().catch(() => 'Unknown error')
28
37
  throw new Error(`SwarmFeed API error ${res.status}: ${text}`)
29
38
  }
39
+ if (res.status === 204) return undefined as T
30
40
  return res.json() as Promise<T>
31
41
  }
32
42
 
33
- // --- Feed (public, no auth needed) ---
34
-
35
43
  const FEED_TYPE_URL: Record<FeedType, string> = {
36
44
  for_you: 'for-you',
37
45
  following: 'following',
@@ -43,10 +51,9 @@ export async function getFeed(
43
51
  type: FeedType,
44
52
  params?: { channelId?: string; cursor?: string; limit?: number },
45
53
  agentApiKey?: string,
46
- ): Promise<{ posts: SwarmFeedPost[]; nextCursor?: string }> {
54
+ ): Promise<SwarmFeedFeedResponse> {
47
55
  const urlSegment = FEED_TYPE_URL[type] ?? type
48
56
 
49
- // Channel feed uses path param: /feed/channel/:channelId
50
57
  if (type === 'channel' && params?.channelId) {
51
58
  const searchParams = new URLSearchParams()
52
59
  if (params.cursor) searchParams.set('cursor', params.cursor)
@@ -62,8 +69,6 @@ export async function getFeed(
62
69
  return sfFetch(`/api/v1/feed/${urlSegment}${qs ? `?${qs}` : ''}`, agentApiKey)
63
70
  }
64
71
 
65
- // --- Posts (auth required for writes) ---
66
-
67
72
  export async function createPost(agentApiKey: string, input: CreatePostInput): Promise<SwarmFeedPost> {
68
73
  return sfFetch('/api/v1/posts', agentApiKey, {
69
74
  method: 'POST',
@@ -71,34 +76,138 @@ export async function createPost(agentApiKey: string, input: CreatePostInput): P
71
76
  content: input.content,
72
77
  channelId: input.channelId,
73
78
  parentId: input.parentId,
79
+ quotedPostId: input.quotedPostId,
74
80
  }),
75
81
  })
76
82
  }
77
83
 
78
- // --- Reactions (auth required) ---
84
+ export async function getPost(postId: string): Promise<SwarmFeedPost> {
85
+ return sfFetch(`/api/v1/posts/${postId}`)
86
+ }
87
+
88
+ export async function getPostReplies(
89
+ postId: string,
90
+ params?: { cursor?: string; limit?: number },
91
+ ): Promise<SwarmFeedFeedResponse> {
92
+ const searchParams = new URLSearchParams()
93
+ if (params?.cursor) searchParams.set('cursor', params.cursor)
94
+ if (params?.limit) searchParams.set('limit', String(params.limit))
95
+ const qs = searchParams.toString()
96
+ return sfFetch(`/api/v1/posts/${postId}/replies${qs ? `?${qs}` : ''}`)
97
+ }
79
98
 
80
- export async function likePost(agentApiKey: string, postId: string): Promise<unknown> {
81
- return sfFetch(`/api/v1/posts/${postId}/like`, agentApiKey, {
99
+ async function addReaction(agentApiKey: string, postId: string, reactionType: SwarmFeedReactionType): Promise<void> {
100
+ await sfFetch(`/api/v1/posts/${postId}/like`, agentApiKey, {
82
101
  method: 'POST',
83
- body: JSON.stringify({ reactionType: 'like' }),
102
+ body: JSON.stringify({ reactionType }),
84
103
  })
85
104
  }
86
105
 
87
- export async function repostPost(agentApiKey: string, postId: string): Promise<unknown> {
88
- return sfFetch(`/api/v1/posts/${postId}/like`, agentApiKey, {
89
- method: 'POST',
90
- body: JSON.stringify({ reactionType: 'repost' }),
106
+ async function removeReaction(agentApiKey: string, postId: string, reactionType: SwarmFeedReactionType): Promise<void> {
107
+ await sfFetch(`/api/v1/posts/${postId}/like?reactionType=${encodeURIComponent(reactionType)}`, agentApiKey, {
108
+ method: 'DELETE',
91
109
  })
92
110
  }
93
111
 
94
- // --- Channels (public reads) ---
112
+ export async function likePost(agentApiKey: string, postId: string): Promise<void> {
113
+ await addReaction(agentApiKey, postId, 'like')
114
+ }
115
+
116
+ export async function unlikePost(agentApiKey: string, postId: string): Promise<void> {
117
+ await removeReaction(agentApiKey, postId, 'like')
118
+ }
119
+
120
+ export async function repostPost(agentApiKey: string, postId: string): Promise<void> {
121
+ await addReaction(agentApiKey, postId, 'repost')
122
+ }
123
+
124
+ export async function unrepostPost(agentApiKey: string, postId: string): Promise<void> {
125
+ await removeReaction(agentApiKey, postId, 'repost')
126
+ }
127
+
128
+ export async function bookmarkPost(agentApiKey: string, postId: string): Promise<void> {
129
+ await addReaction(agentApiKey, postId, 'bookmark')
130
+ }
131
+
132
+ export async function unbookmarkPost(agentApiKey: string, postId: string): Promise<void> {
133
+ await removeReaction(agentApiKey, postId, 'bookmark')
134
+ }
95
135
 
96
136
  export async function getChannels(): Promise<SwarmFeedChannel[]> {
97
137
  const result = await sfFetch<{ channels: SwarmFeedChannel[] }>('/api/v1/channels')
98
138
  return result.channels
99
139
  }
100
140
 
101
- // --- Agent Registration ---
141
+ export async function getProfile(agentId: string): Promise<SwarmFeedProfile> {
142
+ return sfFetch(`/api/v1/agents/${agentId}/profile`)
143
+ }
144
+
145
+ export async function getProfilePosts(
146
+ agentId: string,
147
+ params?: { cursor?: string; limit?: number; filter?: 'posts' | 'replies' },
148
+ ): Promise<SwarmFeedFeedResponse> {
149
+ const searchParams = new URLSearchParams()
150
+ if (params?.cursor) searchParams.set('cursor', params.cursor)
151
+ if (params?.limit) searchParams.set('limit', String(params.limit))
152
+ if (params?.filter) searchParams.set('filter', params.filter)
153
+ const qs = searchParams.toString()
154
+ return sfFetch(`/api/v1/agents/${agentId}/posts${qs ? `?${qs}` : ''}`)
155
+ }
156
+
157
+ export async function getBookmarks(
158
+ agentApiKey: string,
159
+ params?: { cursor?: string; limit?: number },
160
+ ): Promise<SwarmFeedFeedResponse> {
161
+ const searchParams = new URLSearchParams()
162
+ if (params?.cursor) searchParams.set('cursor', params.cursor)
163
+ if (params?.limit) searchParams.set('limit', String(params.limit))
164
+ const qs = searchParams.toString()
165
+ return sfFetch(`/api/v1/bookmarks${qs ? `?${qs}` : ''}`, agentApiKey)
166
+ }
167
+
168
+ export async function getNotifications(
169
+ agentApiKey: string,
170
+ params?: { cursor?: string; limit?: number },
171
+ ): Promise<SwarmFeedNotificationsResponse> {
172
+ const searchParams = new URLSearchParams()
173
+ if (params?.cursor) searchParams.set('cursor', params.cursor)
174
+ if (params?.limit) searchParams.set('limit', String(params.limit))
175
+ const qs = searchParams.toString()
176
+ return sfFetch(`/api/v1/notifications${qs ? `?${qs}` : ''}`, agentApiKey)
177
+ }
178
+
179
+ export async function getSuggestedFollows(agentApiKey?: string, limit?: number): Promise<SwarmFeedSuggestedResponse> {
180
+ const searchParams = new URLSearchParams()
181
+ if (limit) searchParams.set('limit', String(limit))
182
+ const qs = searchParams.toString()
183
+ return sfFetch(`/api/v1/agents/suggested${qs ? `?${qs}` : ''}`, agentApiKey)
184
+ }
185
+
186
+ export async function followAgent(agentApiKey: string, targetAgentId: string): Promise<void> {
187
+ await sfFetch(`/api/v1/agents/${targetAgentId}/follow`, agentApiKey, { method: 'POST' })
188
+ }
189
+
190
+ export async function unfollowAgent(agentApiKey: string, targetAgentId: string): Promise<void> {
191
+ await sfFetch(`/api/v1/agents/${targetAgentId}/follow`, agentApiKey, { method: 'DELETE' })
192
+ }
193
+
194
+ export async function getFollowState(agentId: string, targetAgentId: string): Promise<SwarmFeedFollowState> {
195
+ return sfFetch(`/api/v1/agents/${agentId}/is-following?targetId=${encodeURIComponent(targetAgentId)}`)
196
+ }
197
+
198
+ export async function searchSwarmFeed(params: {
199
+ query: string
200
+ type?: SwarmFeedSearchType
201
+ limit?: number
202
+ offset?: number
203
+ }): Promise<SwarmFeedSearchResponse> {
204
+ const searchParams = new URLSearchParams()
205
+ searchParams.set('q', params.query)
206
+ if (params.type) searchParams.set('type', params.type)
207
+ if (params.limit) searchParams.set('limit', String(params.limit))
208
+ if (params.offset) searchParams.set('offset', String(params.offset))
209
+ return sfFetch(`/api/v1/search?${searchParams.toString()}`)
210
+ }
102
211
 
103
212
  interface RegisterResult {
104
213
  agentId: string
@@ -107,10 +216,6 @@ interface RegisterResult {
107
216
  challengeExpiresAt: string
108
217
  }
109
218
 
110
- /**
111
- * Register a SwarmClaw agent on SwarmFeed.
112
- * Generates an Ed25519 keypair, registers, verifies, and returns the API key.
113
- */
114
219
  export async function registerAgent(agent: {
115
220
  name: string
116
221
  description?: string
@@ -119,13 +224,11 @@ export async function registerAgent(agent: {
119
224
  avatar?: string
120
225
  bio?: string
121
226
  }): Promise<{ agentId: string; apiKey: string }> {
122
- // Dynamic import tweetnacl (available in SwarmClaw's deps)
123
227
  const naclModule = await import('tweetnacl')
124
228
  const nacl = naclModule.default ?? naclModule
125
229
  const keypair = nacl.sign.keyPair()
126
230
  const publicKeyHex = Buffer.from(keypair.publicKey).toString('hex')
127
231
 
128
- // Step 1: Register
129
232
  const reg = await sfFetch<RegisterResult>('/api/v1/register', undefined, {
130
233
  method: 'POST',
131
234
  body: JSON.stringify({
@@ -139,7 +242,6 @@ export async function registerAgent(agent: {
139
242
  }),
140
243
  })
141
244
 
142
- // Step 2: Sign the challenge and verify
143
245
  const messageBytes = new TextEncoder().encode(reg.challenge)
144
246
  const signature = nacl.sign.detached(messageBytes, keypair.secretKey)
145
247
  const signatureHex = Buffer.from(signature).toString('hex')
@@ -29,7 +29,7 @@ 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)' },
32
+ { id: 'swarmfeed', label: 'SwarmFeed', description: 'Post, reply, quote repost, bookmark, follow, search, browse feeds, read threads, and check notifications on the SwarmFeed agent network (auto-enabled when SwarmFeed is on)' },
33
33
  { id: 'swarmdock', label: 'SwarmDock', description: 'Browse tasks and inspect marketplace status/profile on SwarmDock (auto-enabled when SwarmDock is on)' },
34
34
  ]
35
35
 
@@ -212,6 +212,7 @@ export interface Agent {
212
212
  swarmfeedAutoPostChannels?: string[]
213
213
  swarmfeedApiKey?: string | null
214
214
  swarmfeedAgentId?: string | null
215
+ swarmfeedLastAutoPostAt?: number | null
215
216
  origin?: 'swarmdock' | 'swarmfeed' | 'swarmclaw' | 'external'
216
217
  swarmfeedHeartbeat?: SwarmFeedHeartbeatConfig | null
217
218
 
@@ -1,15 +1,52 @@
1
+ export type FeedType = 'for_you' | 'following' | 'channel' | 'trending'
2
+ export type SwarmFeedSearchType = 'posts' | 'agents' | 'channels' | 'hashtags'
3
+ export type SwarmFeedNotificationType = 'mention' | 'reaction' | 'follow'
4
+ export type SwarmFeedReactionType = 'like' | 'repost' | 'bookmark'
5
+
6
+ export interface SwarmFeedBadge {
7
+ id: string
8
+ badgeType: string
9
+ displayName: string
10
+ emoji: string
11
+ color: string
12
+ isActive: boolean
13
+ }
14
+
15
+ export interface SwarmFeedAgentSummary {
16
+ id: string
17
+ name: string
18
+ avatar?: string | null
19
+ framework?: string | null
20
+ bio?: string | null
21
+ followerCount?: number
22
+ }
23
+
24
+ export interface SwarmFeedLinkPreview {
25
+ url: string
26
+ title?: string
27
+ description?: string
28
+ image?: string
29
+ siteName?: string
30
+ }
31
+
1
32
  export interface SwarmFeedPost {
2
33
  id: string
3
34
  agentId: string
4
35
  content: string
5
- channelId?: string
6
- parentId?: string
36
+ channelId?: string | null
37
+ parentId?: string | null
38
+ quotedPostId?: string | null
7
39
  likeCount: number
8
40
  replyCount: number
9
41
  repostCount: number
10
42
  bookmarkCount: number
43
+ contentQualityScore?: number
44
+ isFlagged?: boolean
11
45
  createdAt: string
12
- agent?: { id: string; name: string; avatar?: string }
46
+ updatedAt?: string
47
+ agent?: SwarmFeedAgentSummary
48
+ quotedPost?: SwarmFeedPost
49
+ linkPreview?: SwarmFeedLinkPreview
13
50
  }
14
51
 
15
52
  export interface SwarmFeedChannel {
@@ -17,14 +54,77 @@ export interface SwarmFeedChannel {
17
54
  handle: string
18
55
  displayName: string
19
56
  description?: string
57
+ avatar?: string
20
58
  memberCount: number
21
59
  postCount: number
60
+ rules?: string
61
+ isModerated?: boolean
62
+ creatorAgentId?: string
63
+ createdAt?: string
64
+ }
65
+
66
+ export interface SwarmFeedProfile {
67
+ id: string
68
+ name: string
69
+ description?: string
70
+ avatar?: string | null
71
+ bio?: string | null
72
+ model?: string
73
+ framework?: string
74
+ origin?: string
75
+ postCount: number
76
+ followerCount: number
77
+ followingCount: number
78
+ totalTipsReceived?: number
79
+ badges?: SwarmFeedBadge[]
80
+ channelMemberships?: string[]
81
+ isFollowing?: boolean
82
+ }
83
+
84
+ export interface SwarmFeedNotification {
85
+ id: string
86
+ type: SwarmFeedNotificationType
87
+ actorId: string
88
+ actorName: string | null
89
+ postId: string | null
90
+ content: string | null
91
+ createdAt: string
92
+ }
93
+
94
+ export interface SwarmFeedHashtag {
95
+ tag: string
96
+ postCount: number
97
+ }
98
+
99
+ export interface SwarmFeedSearchResponse {
100
+ posts?: SwarmFeedPost[]
101
+ agents?: SwarmFeedProfile[]
102
+ channels?: SwarmFeedChannel[]
103
+ hashtags?: SwarmFeedHashtag[]
104
+ total: number
105
+ }
106
+
107
+ export interface SwarmFeedFollowState {
108
+ isFollowing: boolean
109
+ }
110
+
111
+ export interface SwarmFeedFeedResponse {
112
+ posts: SwarmFeedPost[]
113
+ nextCursor?: string
114
+ }
115
+
116
+ export interface SwarmFeedNotificationsResponse {
117
+ notifications: SwarmFeedNotification[]
118
+ nextCursor?: string
119
+ }
120
+
121
+ export interface SwarmFeedSuggestedResponse {
122
+ agents: SwarmFeedAgentSummary[]
22
123
  }
23
124
 
24
125
  export interface CreatePostInput {
25
126
  content: string
26
127
  channelId?: string
27
128
  parentId?: string
129
+ quotedPostId?: string
28
130
  }
29
-
30
- export type FeedType = 'for_you' | 'following' | 'channel' | 'trending'