@swarmclawai/swarmclaw 1.4.0 → 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 +13 -71
- package/next.config.ts +9 -4
- package/package.json +10 -8
- package/scripts/build-bootstrap-env.mjs +24 -0
- package/scripts/run-next-build.mjs +120 -0
- package/scripts/run-next-typegen.mjs +61 -0
- package/src/app/api/approvals/route.test.ts +29 -3
- package/src/app/api/approvals/route.ts +13 -7
- package/src/app/api/chats/[id]/chat/route.test.ts +64 -0
- package/src/app/api/chats/[id]/chat/route.ts +24 -8
- package/src/app/api/chats/chat-route.test.ts +68 -0
- package/src/app/api/connectors/[id]/doctor/route.test.ts +97 -0
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -1
- package/src/app/api/connectors/connector-doctor-route.test.ts +1 -0
- package/src/app/api/logs/route.test.ts +61 -0
- package/src/app/api/logs/route.ts +35 -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/api/tts/route.test.ts +82 -0
- package/src/app/api/tts/route.ts +13 -6
- package/src/app/api/tts/stream/route.ts +12 -5
- package/src/app/error.tsx +32 -0
- package/src/app/global-error.tsx +33 -0
- package/src/app/marketplace/page.tsx +7 -0
- package/src/cli/index.js +10 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +10 -0
- package/src/components/layout/error-boundary.tsx +12 -30
- package/src/components/layout/error-fallback.tsx +61 -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/features/swarmfeed/queries.ts +3 -3
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/report-client-error.ts +52 -0
- package/src/lib/app/view-constants.ts +9 -1
- package/src/lib/providers/anthropic.ts +9 -1
- package/src/lib/providers/ollama.ts +34 -14
- package/src/lib/providers/openai.ts +9 -1
- package/src/lib/providers/openclaw.ts +3 -3
- 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/chat-execution/chat-turn-preparation.ts +19 -12
- package/src/lib/server/connectors/swarmdock.ts +29 -7
- package/src/lib/server/messages/message-repository.ts +31 -0
- package/src/lib/server/provider-health.ts +19 -3
- package/src/lib/server/safe-parse-body.test.ts +32 -0
- package/src/lib/server/safe-parse-body.ts +20 -3
- 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/server/storage.ts +13 -4
- package/src/lib/swarmfeed-client.ts +1 -1
- package/src/lib/tool-definitions.ts +2 -0
- package/src/types/agent.ts +23 -0
- package/src/types/session.ts +1 -1
- package/tsconfig.json +1 -2
- package/src/.env.local +0 -4
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { formatZodError } from '@/lib/validation/schemas'
|
|
2
5
|
|
|
3
6
|
type SafeResult<T> = { data: T; error?: never } | { data?: never; error: NextResponse }
|
|
4
7
|
|
|
@@ -6,11 +9,25 @@ type SafeResult<T> = { data: T; error?: never } | { data?: never; error: NextRes
|
|
|
6
9
|
* Wraps `req.json()` so malformed/empty bodies return a 400
|
|
7
10
|
* instead of throwing an unhandled error (500).
|
|
8
11
|
*/
|
|
9
|
-
export async function safeParseBody<T = Record<string, unknown>>(
|
|
12
|
+
export async function safeParseBody<T = Record<string, unknown>>(
|
|
13
|
+
req: Request,
|
|
14
|
+
schema?: z.ZodType<T>,
|
|
15
|
+
): Promise<SafeResult<T>> {
|
|
16
|
+
let raw: unknown
|
|
10
17
|
try {
|
|
11
|
-
|
|
12
|
-
return { data }
|
|
18
|
+
raw = await req.json()
|
|
13
19
|
} catch {
|
|
14
20
|
return { error: NextResponse.json({ error: 'Invalid or missing request body' }, { status: 400 }) }
|
|
15
21
|
}
|
|
22
|
+
|
|
23
|
+
if (!schema) {
|
|
24
|
+
return { data: raw as T }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const parsed = schema.safeParse(raw)
|
|
28
|
+
if (!parsed.success) {
|
|
29
|
+
return { error: NextResponse.json(formatZodError(parsed.error), { status: 400 }) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { data: parsed.data }
|
|
16
33
|
}
|
|
@@ -44,6 +44,8 @@ import { buildSkillsTools } from './skills-tool'
|
|
|
44
44
|
import { buildFilesTools } from './files-tool'
|
|
45
45
|
import { buildMemoryTool } from './memory-tool'
|
|
46
46
|
import { buildPlatformV2Tools } from './platform-tool'
|
|
47
|
+
import { buildSwarmFeedTools } from './swarmfeed'
|
|
48
|
+
import { buildSwarmDockTools } from './swarmdock'
|
|
47
49
|
import './connector'
|
|
48
50
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
49
51
|
import { enforceFileAccessPolicy } from './file-access-policy'
|
|
@@ -208,6 +210,8 @@ export async function buildSessionTools(cwd: string, enabledExtensions: string[]
|
|
|
208
210
|
['files_v2', buildFilesTools],
|
|
209
211
|
['memory_v2', buildMemoryTool],
|
|
210
212
|
['platform_v2', buildPlatformV2Tools],
|
|
213
|
+
['swarmfeed', buildSwarmFeedTools],
|
|
214
|
+
['swarmdock', buildSwarmDockTools],
|
|
211
215
|
]
|
|
212
216
|
|
|
213
217
|
for (const [extensionId, builder] of nativeBuilders) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import { getAgent } from '@/lib/server/agents/agent-repository'
|
|
4
|
+
import { log } from '@/lib/server/logger'
|
|
5
|
+
import type { ToolBuildContext } from './context'
|
|
6
|
+
import type { Agent } from '@/types'
|
|
7
|
+
|
|
8
|
+
const TAG = 'swarmdock-tool'
|
|
9
|
+
|
|
10
|
+
const SWARMDOCK_SCHEMA = z.object({
|
|
11
|
+
action: z.enum(['browse_tasks', 'check_status', 'list_skills', 'get_agent_profile']).describe(
|
|
12
|
+
'The SwarmDock marketplace action to perform',
|
|
13
|
+
),
|
|
14
|
+
taskId: z.string().optional().describe('Task ID for task-specific actions'),
|
|
15
|
+
skillFilter: z.string().optional().describe('Filter tasks by skill (e.g. "data-analysis")'),
|
|
16
|
+
limit: z.number().optional().describe('Number of results to return (default: 10)'),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
type SwarmDockInput = z.infer<typeof SWARMDOCK_SCHEMA>
|
|
20
|
+
|
|
21
|
+
async function executeSwarmDock(input: SwarmDockInput, bctx: ToolBuildContext): Promise<string> {
|
|
22
|
+
const agentId = bctx.ctx?.agentId
|
|
23
|
+
if (!agentId) return JSON.stringify({ error: 'No agent context' })
|
|
24
|
+
|
|
25
|
+
const agent = getAgent(agentId) as Agent | undefined
|
|
26
|
+
if (!agent) return JSON.stringify({ error: 'Agent not found' })
|
|
27
|
+
if (!agent.swarmdockEnabled) return JSON.stringify({ error: 'SwarmDock is not enabled for this agent' })
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
switch (input.action) {
|
|
31
|
+
case 'browse_tasks': {
|
|
32
|
+
const apiUrl = process.env.SWARMDOCK_API_URL || 'https://swarmdock-api.onrender.com'
|
|
33
|
+
const res = await fetch(`${apiUrl}/api/v1/tasks?limit=${input.limit || 10}${input.skillFilter ? `&skill=${input.skillFilter}` : ''}`)
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const text = await res.text().catch(() => 'Unknown error')
|
|
36
|
+
return JSON.stringify({ error: `SwarmDock API error ${res.status}: ${text}` })
|
|
37
|
+
}
|
|
38
|
+
const data = await res.json()
|
|
39
|
+
return JSON.stringify({ tasks: data.tasks || data })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case 'check_status': {
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
agent: agent.name,
|
|
45
|
+
swarmdockEnabled: agent.swarmdockEnabled,
|
|
46
|
+
swarmdockAgentId: agent.swarmdockAgentId || null,
|
|
47
|
+
swarmdockDid: agent.swarmdockDid || null,
|
|
48
|
+
listedAt: agent.swarmdockListedAt || null,
|
|
49
|
+
skills: agent.swarmdockSkills || [],
|
|
50
|
+
description: agent.swarmdockDescription || null,
|
|
51
|
+
marketplace: agent.swarmdockMarketplace || null,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case 'list_skills': {
|
|
56
|
+
return JSON.stringify({
|
|
57
|
+
agentSkills: agent.swarmdockSkills || [],
|
|
58
|
+
description: agent.swarmdockDescription || null,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case 'get_agent_profile': {
|
|
63
|
+
return JSON.stringify({
|
|
64
|
+
name: agent.name,
|
|
65
|
+
description: agent.swarmdockDescription || agent.description || null,
|
|
66
|
+
skills: agent.swarmdockSkills || [],
|
|
67
|
+
swarmdockAgentId: agent.swarmdockAgentId || null,
|
|
68
|
+
swarmdockDid: agent.swarmdockDid || null,
|
|
69
|
+
walletId: agent.swarmdockWalletId || null,
|
|
70
|
+
marketplace: agent.swarmdockMarketplace || null,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
return JSON.stringify({ error: `Unknown action: ${input.action}` })
|
|
76
|
+
}
|
|
77
|
+
} catch (err: unknown) {
|
|
78
|
+
const message = err instanceof Error ? err.message : 'Unknown error'
|
|
79
|
+
log.error(TAG, `Action "${input.action}" failed for agent "${agent.name}": ${message}`)
|
|
80
|
+
return JSON.stringify({ error: message })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function buildSwarmDockTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
85
|
+
const agentId = bctx.ctx?.agentId
|
|
86
|
+
if (!agentId) return []
|
|
87
|
+
|
|
88
|
+
const agent = getAgent(agentId) as Agent | undefined
|
|
89
|
+
if (!agent?.swarmdockEnabled) return []
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
tool(
|
|
93
|
+
async (args) => executeSwarmDock(args as SwarmDockInput, bctx),
|
|
94
|
+
{
|
|
95
|
+
name: 'swarmdock',
|
|
96
|
+
description:
|
|
97
|
+
'Interact with SwarmDock, the AI agent marketplace. ' +
|
|
98
|
+
'Actions: browse_tasks (find available tasks), check_status (check marketplace registration status), ' +
|
|
99
|
+
'list_skills (view configured skills), get_agent_profile (view marketplace profile).',
|
|
100
|
+
schema: SWARMDOCK_SCHEMA,
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
]
|
|
104
|
+
}
|
|
@@ -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>
|
|
@@ -6,12 +6,13 @@ import Database from 'better-sqlite3'
|
|
|
6
6
|
import { perf } from '@/lib/server/runtime/perf'
|
|
7
7
|
import { log } from '@/lib/server/logger'
|
|
8
8
|
import { notify } from '@/lib/server/ws-hub'
|
|
9
|
-
|
|
10
|
-
const TAG = 'storage'
|
|
11
9
|
import { DATA_DIR, IS_BUILD_BOOTSTRAP, WORKSPACE_DIR } from './data-dir'
|
|
12
10
|
import { normalizeHeartbeatSettingFields } from '@/lib/runtime/heartbeat-defaults'
|
|
13
11
|
import { normalizeRuntimeSettingFields } from '@/lib/runtime/runtime-loop'
|
|
14
12
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
13
|
+
|
|
14
|
+
const TAG = 'storage'
|
|
15
|
+
const malformedRecordWarnings = new Set<string>()
|
|
15
16
|
import type {
|
|
16
17
|
Agent,
|
|
17
18
|
AppNotification,
|
|
@@ -236,8 +237,16 @@ function loadCollectionWithNormalizationState(table: string): {
|
|
|
236
237
|
if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) continue
|
|
237
238
|
result[id] = normalized as StoredObject
|
|
238
239
|
if (changed) normalizedCount += 1
|
|
239
|
-
} catch {
|
|
240
|
-
|
|
240
|
+
} catch (err) {
|
|
241
|
+
const fingerprint = `${table}:${id}`
|
|
242
|
+
if (!malformedRecordWarnings.has(fingerprint)) {
|
|
243
|
+
malformedRecordWarnings.add(fingerprint)
|
|
244
|
+
log.warn(TAG, 'Ignoring malformed stored record during collection load', {
|
|
245
|
+
table,
|
|
246
|
+
id,
|
|
247
|
+
error: err instanceof Error ? err.message : String(err),
|
|
248
|
+
})
|
|
249
|
+
}
|
|
241
250
|
}
|
|
242
251
|
}
|
|
243
252
|
endPerf({ count: raw.size, normalizedCount })
|
|
@@ -6,7 +6,7 @@ interface SwarmFeedConfig {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const config = hmrSingleton<SwarmFeedConfig>('swarmfeed_config', () => ({
|
|
9
|
-
apiUrl: process.env.SWARMFEED_API_URL || '
|
|
9
|
+
apiUrl: process.env.SWARMFEED_API_URL || 'https://swarmfeed-api.onrender.com',
|
|
10
10
|
}))
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -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'
|
package/tsconfig.json
CHANGED
package/src/.env.local
DELETED