@swarmclawai/swarmclaw 0.6.2 → 0.6.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 +45 -44
- package/package.json +1 -1
- package/src/app/api/tts/route.ts +16 -36
- package/src/app/api/tts/stream/route.ts +14 -43
- package/src/components/chat/chat-area.tsx +30 -2
- package/src/components/chat/chat-header.tsx +70 -3
- package/src/components/chat/message-list.tsx +3 -71
- package/src/components/connectors/connector-sheet.tsx +16 -1
- package/src/lib/server/chat-execution.ts +74 -3
- package/src/lib/server/connectors/connector-routing.test.ts +118 -1
- package/src/lib/server/connectors/discord.ts +31 -8
- package/src/lib/server/connectors/manager.ts +398 -31
- package/src/lib/server/connectors/media.ts +5 -0
- package/src/lib/server/connectors/telegram.ts +12 -2
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.ts +28 -2
- package/src/lib/server/elevenlabs.test.ts +60 -0
- package/src/lib/server/elevenlabs.ts +103 -0
- package/src/lib/server/queue.ts +130 -1
- package/src/lib/server/session-tools/connector.ts +540 -94
- package/src/lib/server/session-tools/file.ts +26 -7
- package/src/lib/server/session-tools/web.ts +3 -4
- package/src/lib/server/stream-agent-chat.ts +7 -0
|
@@ -6,6 +6,17 @@ import { UPLOAD_DIR } from '../storage'
|
|
|
6
6
|
import type { ToolBuildContext } from './context'
|
|
7
7
|
import { safePath, truncate, listDirRecursive, MAX_OUTPUT, MAX_FILE } from './context'
|
|
8
8
|
|
|
9
|
+
const SEND_FILE_DEDUPE_TTL_MS = 30_000
|
|
10
|
+
const recentSendFileResults = new Map<string, { at: number; output: string; uploadPath: string }>()
|
|
11
|
+
|
|
12
|
+
function pruneRecentSendFileCache(now: number): void {
|
|
13
|
+
for (const [key, entry] of recentSendFileResults.entries()) {
|
|
14
|
+
if (now - entry.at > SEND_FILE_DEDUPE_TTL_MS || !fs.existsSync(entry.uploadPath)) {
|
|
15
|
+
recentSendFileResults.delete(key)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
export function buildFileTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
10
21
|
const tools: StructuredToolInterface[] = []
|
|
11
22
|
|
|
@@ -197,6 +208,8 @@ export function buildFileTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
197
208
|
tool(
|
|
198
209
|
async ({ filePath: rawPath }) => {
|
|
199
210
|
try {
|
|
211
|
+
const now = Date.now()
|
|
212
|
+
pruneRecentSendFileCache(now)
|
|
200
213
|
// Resolve relative to cwd, but also allow absolute paths
|
|
201
214
|
const resolved = path.isAbsolute(rawPath) ? rawPath : path.resolve(bctx.cwd, rawPath)
|
|
202
215
|
if (!fs.existsSync(resolved)) return `Error: file not found: ${rawPath}`
|
|
@@ -204,6 +217,13 @@ export function buildFileTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
204
217
|
if (stat.isDirectory()) return `Error: cannot send a directory. Send individual files instead.`
|
|
205
218
|
if (stat.size > 100 * 1024 * 1024) return `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 100MB.`
|
|
206
219
|
|
|
220
|
+
const sessionId = bctx.ctx?.sessionId || 'no-session'
|
|
221
|
+
const dedupeKey = `${sessionId}|${resolved}`
|
|
222
|
+
const cached = recentSendFileResults.get(dedupeKey)
|
|
223
|
+
if (cached && now - cached.at <= SEND_FILE_DEDUPE_TTL_MS && fs.existsSync(cached.uploadPath)) {
|
|
224
|
+
return cached.output
|
|
225
|
+
}
|
|
226
|
+
|
|
207
227
|
const ext = path.extname(resolved).slice(1).toLowerCase()
|
|
208
228
|
const basename = path.basename(resolved)
|
|
209
229
|
const filename = `${Date.now()}-${basename}`
|
|
@@ -212,14 +232,13 @@ export function buildFileTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
212
232
|
|
|
213
233
|
const IMAGE_EXTS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']
|
|
214
234
|
const VIDEO_EXTS = ['mp4', 'webm', 'mov', 'avi', 'mkv']
|
|
235
|
+
const AUDIO_EXTS = ['mp3', 'ogg', 'wav', 'aac', 'm4a', 'opus']
|
|
215
236
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return `[Download ${basename}](/api/uploads/${filename})`
|
|
222
|
-
}
|
|
237
|
+
const output = (IMAGE_EXTS.includes(ext) || VIDEO_EXTS.includes(ext) || AUDIO_EXTS.includes(ext))
|
|
238
|
+
? ``
|
|
239
|
+
: `[Download ${basename}](/api/uploads/${filename})`
|
|
240
|
+
recentSendFileResults.set(dedupeKey, { at: now, output, uploadPath: dest })
|
|
241
|
+
return output
|
|
223
242
|
} catch (err: unknown) {
|
|
224
243
|
return `Error sending file: ${err instanceof Error ? err.message : String(err)}`
|
|
225
244
|
}
|
|
@@ -278,10 +278,9 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
278
278
|
|
|
279
279
|
if (Array.isArray(content)) {
|
|
280
280
|
const parts: string[] = []
|
|
281
|
-
|
|
281
|
+
const contentHasBinaryImage = content.some((c) => c.type === 'image' && !!c.data)
|
|
282
282
|
for (const c of content) {
|
|
283
283
|
if (c.type === 'image' && c.data) {
|
|
284
|
-
hasBinaryImage = true
|
|
285
284
|
const imageBuffer = Buffer.from(c.data, 'base64')
|
|
286
285
|
const filename = `screenshot-${Date.now()}.png`
|
|
287
286
|
const filepath = path.join(UPLOAD_DIR, filename)
|
|
@@ -306,8 +305,8 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
306
305
|
if (fs.existsSync(srcPath)) {
|
|
307
306
|
const ext = path.extname(srcPath).slice(1).toLowerCase()
|
|
308
307
|
const IMAGE_EXTS = ['png', 'jpg', 'jpeg', 'gif', 'webp']
|
|
309
|
-
// Skip file-path images
|
|
310
|
-
if (IMAGE_EXTS.includes(ext) &&
|
|
308
|
+
// Skip file-path images whenever MCP already returned image binary payloads.
|
|
309
|
+
if (IMAGE_EXTS.includes(ext) && contentHasBinaryImage) {
|
|
311
310
|
parts.push(isError ? text : cleanPlaywrightOutput(text))
|
|
312
311
|
} else {
|
|
313
312
|
const filename = `browser-${Date.now()}.${ext}`
|
|
@@ -127,6 +127,12 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
127
127
|
opts.enabledTools.includes('manage_connectors')
|
|
128
128
|
? 'If the user wants proactive outreach (e.g., WhatsApp updates), configure connectors and pair with schedules/tasks to deliver status updates.'
|
|
129
129
|
: '',
|
|
130
|
+
opts.enabledTools.includes('manage_connectors')
|
|
131
|
+
? 'Autonomous outreach is allowed for significant events (completed/failed tasks, blockers, deadlines, meaningful reminders from memory). Avoid casual or repetitive check-ins.'
|
|
132
|
+
: '',
|
|
133
|
+
opts.enabledTools.includes('manage_connectors')
|
|
134
|
+
? 'When you proactively message through connectors, keep it concise and purposeful, and avoid sending duplicate updates about the same event.'
|
|
135
|
+
: '',
|
|
130
136
|
opts.enabledTools.includes('manage_sessions')
|
|
131
137
|
? 'When coordinating platform work, inspect existing sessions and avoid duplicating active efforts.'
|
|
132
138
|
: '',
|
|
@@ -164,6 +170,7 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
164
170
|
'The test: if you saw this message from a friend, would you feel compelled to type something back? If not, NO_MESSAGE.',
|
|
165
171
|
'Ask for confirmation only for high-risk or irreversible actions. For normal low-risk research/build steps, proceed autonomously.',
|
|
166
172
|
'Default behavior is execution, not interrogation: do not ask exploratory clarification questions when a safe next action exists.',
|
|
173
|
+
'Do not end every response with a question. Use declarative completion statements by default, and only ask a question when a concrete missing detail blocks the next action.',
|
|
167
174
|
'Do not pause for a "continue" confirmation after the user has already asked you to execute a goal. Keep moving until blocked by permissions, missing credentials, or hard tool failures.',
|
|
168
175
|
'Never repeat one-time side effects that are already complete (for example creating the same schedule/task again). Verify state first, then either continue execution or reply HEARTBEAT_OK.',
|
|
169
176
|
'For main-loop tick messages that begin with "SWARM_MAIN_MISSION_TICK" or "SWARM_MAIN_AUTO_FOLLOWUP", follow that response contract exactly and include one valid [MAIN_LOOP_META] JSON line when you are not returning HEARTBEAT_OK.',
|