@swarmclawai/swarmclaw 0.6.7 → 0.7.0
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 +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -6,6 +6,9 @@ import { loadConnectors, loadSettings, UPLOAD_DIR } from '../storage'
|
|
|
6
6
|
import { genId } from '@/lib/id'
|
|
7
7
|
import { synthesizeElevenLabsMp3 } from '../elevenlabs'
|
|
8
8
|
import type { ToolBuildContext } from './context'
|
|
9
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
10
|
+
import { getPluginManager } from '../plugins'
|
|
11
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
9
12
|
|
|
10
13
|
const CONNECTOR_ACTION_DEDUPE_TTL_MS = 30_000
|
|
11
14
|
const CONNECTOR_TURN_SEND_TTL_MS = 180_000
|
|
@@ -62,7 +65,8 @@ function isAutonomousSystemTurn(userText: string): boolean {
|
|
|
62
65
|
|| text.includes('SWARM_HEARTBEAT_CHECK')
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
69
|
+
function _isSignificantOutreachText(raw: string): boolean {
|
|
66
70
|
const text = (raw || '').trim().toLowerCase()
|
|
67
71
|
if (text.length < 12) return false
|
|
68
72
|
if (/\b(just checking in|checking in|touching base|quick check-in|hope you'?re well|any updates\??)\b/.test(text)) {
|
|
@@ -71,7 +75,8 @@ function isSignificantOutreachText(raw: string): boolean {
|
|
|
71
75
|
return /\b(completed|complete|done|finished|failed|failure|error|blocked|urgent|important|deadline|overdue|incident|warning|reminder|birthday|anniversary|milestone|congrats|congratulations|celebrate|payment|invoice|appointment|meeting)\b/.test(text)
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
79
|
+
function _isUrgentOutreachText(raw: string): boolean {
|
|
75
80
|
const text = (raw || '').toLowerCase()
|
|
76
81
|
return /\b(urgent|immediately|asap|critical|incident|outage|failed|failure|blocked|overdue|deadline)\b/.test(text)
|
|
77
82
|
}
|
|
@@ -111,7 +116,6 @@ function resolveUploadUrl(url: string | undefined): { mediaPath: string; mimeTyp
|
|
|
111
116
|
if (!url) return null
|
|
112
117
|
const match = url.match(/^\/api\/uploads\/([^?#]+)/)
|
|
113
118
|
if (!match) return null
|
|
114
|
-
// Decode URL-encoded filenames (e.g. from encodeURIComponent) before sanitizing
|
|
115
119
|
let decoded: string
|
|
116
120
|
try { decoded = decodeURIComponent(match[1]) } catch { decoded = match[1] }
|
|
117
121
|
const safeName = decoded.replace(/[^a-zA-Z0-9._-]/g, '')
|
|
@@ -234,458 +238,233 @@ function resolveConnectorMediaInput(params: {
|
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const allConnectors = loadConnectors()
|
|
328
|
-
const configured = Object.values(allConnectors)
|
|
329
|
-
.filter((c) => !platform || c.platform === platform)
|
|
330
|
-
.map((c) => ({ id: c.id, name: c.name, platform: c.platform, agentId: c.agentId || null }))
|
|
331
|
-
if (configured.length) {
|
|
332
|
-
return {
|
|
333
|
-
error: `Error: no running connectors${platform ? ` for platform "${platform}"` : ''}, but ${configured.length} configured connector(s) found: ${JSON.stringify(configured)}. These connectors exist but are not currently started. Ask the user if they'd like you to start one (use action "start" with the connectorId), then retry the send.`,
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
error: `Error: no running connectors${platform ? ` for platform "${platform}"` : ''}. No connectors are configured for this platform either — the user needs to set one up in the Connectors panel first.`,
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
const selected = connectorId
|
|
341
|
-
? running.find((c) => c.id === connectorId)
|
|
342
|
-
: running[0]
|
|
343
|
-
if (!selected) return { error: `Error: running connector not found: ${connectorId}` }
|
|
344
|
-
const connectors = loadConnectors()
|
|
345
|
-
const connector = connectors[selected.id]
|
|
346
|
-
if (!connector) return { error: `Error: connector not found: ${selected.id}` }
|
|
347
|
-
return { selected, connector }
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (actionName === 'send' || actionName === 'send_voice_note' || actionName === 'schedule_followup') {
|
|
351
|
-
const settings = loadSettings()
|
|
352
|
-
if (settings.safetyRequireApprovalForOutbound === true && approved !== true) {
|
|
353
|
-
return 'Error: outbound connector sends require explicit approval. Re-run with approved=true after user confirmation.'
|
|
354
|
-
}
|
|
355
|
-
const now = Date.now()
|
|
356
|
-
pruneOldConnectorToolState(now)
|
|
357
|
-
const resolved = resolveSelectedConnector()
|
|
358
|
-
if ('error' in resolved) return resolved.error
|
|
359
|
-
const { selected, connector } = resolved
|
|
360
|
-
|
|
361
|
-
const target = pickChannelTarget({
|
|
362
|
-
connector,
|
|
363
|
-
to,
|
|
364
|
-
recentChannelId: getConnectorRecentChannelId(selected.id),
|
|
365
|
-
})
|
|
366
|
-
if (target.error) return target.error
|
|
367
|
-
|
|
368
|
-
let channelId = target.channelId
|
|
369
|
-
if (connector.platform === 'whatsapp') channelId = normalizeWhatsAppTarget(channelId)
|
|
370
|
-
|
|
371
|
-
const currentSession = bctx.resolveCurrentSession()
|
|
372
|
-
const latestUserTurn = parseLatestUserTurn(currentSession)
|
|
373
|
-
const sessionId = bctx.ctx?.sessionId || currentSession?.id || 'unknown-session'
|
|
374
|
-
const turnKey = buildConnectorActionKey([sessionId, latestUserTurn.time || 'no-user-turn'])
|
|
375
|
-
const multiOutboundAllowed = userExplicitlyWantsMultipleOutbound(latestUserTurn.text)
|
|
376
|
-
const followupExplicitlyRequested = userExplicitlyRequestedFollowup(latestUserTurn.text)
|
|
377
|
-
const autonomousTurn = isAutonomousSystemTurn(latestUserTurn.text)
|
|
378
|
-
const existingBudget = connectorTurnSendBudget.get(turnKey)
|
|
379
|
-
if (
|
|
380
|
-
!multiOutboundAllowed
|
|
381
|
-
&& existingBudget
|
|
382
|
-
&& now - existingBudget.at <= CONNECTOR_TURN_SEND_TTL_MS
|
|
383
|
-
&& existingBudget.count >= 1
|
|
384
|
-
) {
|
|
385
|
-
if (existingBudget.lastResult) {
|
|
386
|
-
return normalizeDedupedReplayResult(existingBudget.lastResult, {
|
|
387
|
-
connectorId: selected.id,
|
|
388
|
-
platform: selected.platform,
|
|
389
|
-
to: channelId,
|
|
390
|
-
})
|
|
391
|
-
}
|
|
392
|
-
return JSON.stringify({
|
|
393
|
-
status: 'sent',
|
|
394
|
-
connectorId: selected.id,
|
|
395
|
-
platform: selected.platform,
|
|
396
|
-
to: channelId,
|
|
397
|
-
deduped: true,
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (actionName === 'send_voice_note') {
|
|
402
|
-
if (!voiceNoteToolEnabled) {
|
|
403
|
-
return 'Error: send_voice_note is unavailable. Enable ElevenLabs in Settings > Voice and set a valid API key.'
|
|
404
|
-
}
|
|
405
|
-
const ttsText = (voiceText || message || '').trim()
|
|
406
|
-
if (!ttsText) return 'Error: voiceText or message is required for send_voice_note action.'
|
|
407
|
-
const voiceActionKey = buildConnectorActionKey([
|
|
408
|
-
sessionId,
|
|
409
|
-
actionName,
|
|
410
|
-
selected.id,
|
|
411
|
-
channelId,
|
|
412
|
-
ttsText,
|
|
413
|
-
voiceId?.trim() || '',
|
|
414
|
-
fileName?.trim() || '',
|
|
415
|
-
caption?.trim() || '',
|
|
416
|
-
ptt ?? true,
|
|
417
|
-
])
|
|
418
|
-
const cachedVoice = recentConnectorActionCache.get(voiceActionKey)
|
|
419
|
-
if (cachedVoice && now - cachedVoice.at <= CONNECTOR_ACTION_DEDUPE_TTL_MS) {
|
|
420
|
-
return cachedVoice.result
|
|
421
|
-
}
|
|
422
|
-
const audioBuffer = await synthesizeElevenLabsMp3({ text: ttsText, voiceId: voiceId?.trim() || undefined })
|
|
423
|
-
const voiceFileName = `${Date.now()}-${genId()}-voicenote.mp3`
|
|
424
|
-
const voicePath = path.join(UPLOAD_DIR, voiceFileName)
|
|
425
|
-
fs.writeFileSync(voicePath, audioBuffer)
|
|
426
|
-
|
|
427
|
-
const sent = await sendConnectorMessage({
|
|
428
|
-
connectorId: selected.id,
|
|
429
|
-
channelId,
|
|
430
|
-
text: '',
|
|
431
|
-
mediaPath: voicePath,
|
|
432
|
-
mimeType: 'audio/mpeg',
|
|
433
|
-
fileName: fileName?.trim() || 'voicenote.mp3',
|
|
434
|
-
caption: caption?.trim() || undefined,
|
|
435
|
-
ptt: ptt ?? true,
|
|
436
|
-
})
|
|
437
|
-
const result = JSON.stringify({
|
|
438
|
-
status: 'voice_sent',
|
|
439
|
-
connectorId: sent.connectorId,
|
|
440
|
-
platform: sent.platform,
|
|
441
|
-
to: sent.channelId,
|
|
442
|
-
messageId: sent.messageId || null,
|
|
443
|
-
voiceFile: voicePath,
|
|
444
|
-
})
|
|
445
|
-
connectorTurnSendBudget.set(turnKey, {
|
|
446
|
-
count: (existingBudget?.count || 0) + 1,
|
|
447
|
-
at: now,
|
|
448
|
-
lastResult: result,
|
|
449
|
-
})
|
|
450
|
-
recentConnectorActionCache.set(voiceActionKey, { at: now, result })
|
|
451
|
-
return result
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const media = resolveConnectorMediaInput({
|
|
455
|
-
cwd: bctx.cwd,
|
|
456
|
-
mediaPath,
|
|
457
|
-
imageUrl,
|
|
458
|
-
fileUrl,
|
|
459
|
-
})
|
|
460
|
-
if (media.error) return media.error
|
|
461
|
-
|
|
462
|
-
const hasText = !!message?.trim()
|
|
463
|
-
const hasMedia = !!media.mediaPath || !!media.imageUrl || !!media.fileUrl
|
|
464
|
-
if (actionName === 'send' && !hasText && !hasMedia) {
|
|
465
|
-
return 'Error: message, media URL, or mediaPath is required for send action.'
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
let followUpText = followUpMessage?.trim() || ''
|
|
469
|
-
const followDelaySec = Number.isFinite(followUpDelaySec) ? Number(followUpDelaySec) : 300
|
|
470
|
-
|
|
471
|
-
const proactivePayload = followUpText || message?.trim() || ''
|
|
472
|
-
const significantAutonomousOutreach = autonomousTurn && isSignificantOutreachText(proactivePayload)
|
|
473
|
-
const urgentAutonomousOutreach = autonomousTurn && isUrgentOutreachText(proactivePayload)
|
|
474
|
-
const outreachBudgetKey = buildConnectorActionKey([selected.id, channelId])
|
|
475
|
-
const priorAutonomousOutreach = autonomousOutreachBudget.get(outreachBudgetKey)
|
|
476
|
-
if (
|
|
477
|
-
autonomousTurn
|
|
478
|
-
&& significantAutonomousOutreach
|
|
479
|
-
&& priorAutonomousOutreach
|
|
480
|
-
&& !urgentAutonomousOutreach
|
|
481
|
-
&& now - priorAutonomousOutreach.at <= AUTONOMOUS_OUTREACH_COOLDOWN_MS
|
|
482
|
-
) {
|
|
483
|
-
if (priorAutonomousOutreach.result) {
|
|
484
|
-
return normalizeDedupedReplayResult(priorAutonomousOutreach.result, {
|
|
485
|
-
connectorId: selected.id,
|
|
486
|
-
platform: selected.platform,
|
|
487
|
-
to: channelId,
|
|
488
|
-
})
|
|
489
|
-
}
|
|
490
|
-
return JSON.stringify({
|
|
491
|
-
status: 'sent',
|
|
492
|
-
connectorId: selected.id,
|
|
493
|
-
platform: selected.platform,
|
|
494
|
-
to: channelId,
|
|
495
|
-
deduped: true,
|
|
496
|
-
})
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (followUpText && !followupExplicitlyRequested && !significantAutonomousOutreach) {
|
|
500
|
-
followUpText = ''
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (actionName === 'schedule_followup') {
|
|
504
|
-
if (!followupExplicitlyRequested && !significantAutonomousOutreach) {
|
|
505
|
-
return 'Error: schedule_followup requires either an explicit user request or a significant autonomous event.'
|
|
506
|
-
}
|
|
507
|
-
const payload = followUpText || message?.trim() || ''
|
|
508
|
-
if (!payload) return 'Error: followUpMessage or message is required for schedule_followup action.'
|
|
509
|
-
const scheduleActionKey = buildConnectorActionKey([
|
|
510
|
-
sessionId,
|
|
511
|
-
actionName,
|
|
512
|
-
selected.id,
|
|
513
|
-
channelId,
|
|
514
|
-
payload,
|
|
515
|
-
Number.isFinite(delaySec) ? Number(delaySec) : followDelaySec,
|
|
516
|
-
])
|
|
517
|
-
const cachedSchedule = recentConnectorActionCache.get(scheduleActionKey)
|
|
518
|
-
if (cachedSchedule && now - cachedSchedule.at <= CONNECTOR_ACTION_DEDUPE_TTL_MS) {
|
|
519
|
-
return cachedSchedule.result
|
|
520
|
-
}
|
|
521
|
-
const scheduled = scheduleConnectorFollowUp({
|
|
522
|
-
connectorId: selected.id,
|
|
523
|
-
channelId,
|
|
524
|
-
text: payload,
|
|
525
|
-
delaySec: Number.isFinite(delaySec) ? Number(delaySec) : followDelaySec,
|
|
526
|
-
})
|
|
527
|
-
const result = JSON.stringify({
|
|
528
|
-
status: 'followup_scheduled',
|
|
529
|
-
connectorId: selected.id,
|
|
530
|
-
platform: selected.platform,
|
|
531
|
-
to: channelId,
|
|
532
|
-
followUpId: scheduled.followUpId,
|
|
533
|
-
sendAt: scheduled.sendAt,
|
|
534
|
-
})
|
|
535
|
-
connectorTurnSendBudget.set(turnKey, {
|
|
536
|
-
count: (existingBudget?.count || 0) + 1,
|
|
537
|
-
at: now,
|
|
538
|
-
lastResult: result,
|
|
539
|
-
})
|
|
540
|
-
if (autonomousTurn && significantAutonomousOutreach) {
|
|
541
|
-
autonomousOutreachBudget.set(outreachBudgetKey, { at: now, result })
|
|
542
|
-
}
|
|
543
|
-
recentConnectorActionCache.set(scheduleActionKey, { at: now, result })
|
|
544
|
-
return result
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const sendActionKey = buildConnectorActionKey([
|
|
548
|
-
sessionId,
|
|
549
|
-
actionName,
|
|
550
|
-
selected.id,
|
|
551
|
-
channelId,
|
|
552
|
-
message?.trim() || '',
|
|
553
|
-
media.mediaPath || '',
|
|
554
|
-
media.imageUrl || '',
|
|
555
|
-
media.fileUrl || '',
|
|
556
|
-
mimeType?.trim() || '',
|
|
557
|
-
fileName?.trim() || '',
|
|
558
|
-
caption?.trim() || '',
|
|
559
|
-
ptt ?? '',
|
|
560
|
-
followUpText,
|
|
561
|
-
followDelaySec,
|
|
562
|
-
])
|
|
563
|
-
const cachedSend = recentConnectorActionCache.get(sendActionKey)
|
|
564
|
-
if (cachedSend && now - cachedSend.at <= CONNECTOR_ACTION_DEDUPE_TTL_MS) {
|
|
565
|
-
return cachedSend.result
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const sent = await sendConnectorMessage({
|
|
569
|
-
connectorId: selected.id,
|
|
570
|
-
channelId,
|
|
571
|
-
text: message?.trim() || '',
|
|
572
|
-
imageUrl: media.imageUrl,
|
|
573
|
-
fileUrl: media.fileUrl,
|
|
574
|
-
mediaPath: media.mediaPath,
|
|
575
|
-
mimeType: mimeType?.trim() || undefined,
|
|
576
|
-
fileName: fileName?.trim() || undefined,
|
|
577
|
-
caption: caption?.trim() || undefined,
|
|
578
|
-
ptt: ptt ?? undefined,
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
let followup: { followUpId: string; sendAt: number } | null = null
|
|
582
|
-
if (followUpText) {
|
|
583
|
-
followup = scheduleConnectorFollowUp({
|
|
584
|
-
connectorId: selected.id,
|
|
585
|
-
channelId,
|
|
586
|
-
text: followUpText,
|
|
587
|
-
delaySec: followDelaySec,
|
|
588
|
-
})
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const result = JSON.stringify({
|
|
592
|
-
status: 'sent',
|
|
593
|
-
connectorId: sent.connectorId,
|
|
594
|
-
platform: sent.platform,
|
|
595
|
-
to: sent.channelId,
|
|
596
|
-
messageId: sent.messageId || null,
|
|
597
|
-
...(followup
|
|
598
|
-
? {
|
|
599
|
-
followUpId: followup.followUpId,
|
|
600
|
-
followUpSendAt: followup.sendAt,
|
|
601
|
-
}
|
|
602
|
-
: {}),
|
|
603
|
-
})
|
|
604
|
-
connectorTurnSendBudget.set(turnKey, {
|
|
605
|
-
count: (existingBudget?.count || 0) + 1,
|
|
606
|
-
at: now,
|
|
607
|
-
lastResult: result,
|
|
608
|
-
})
|
|
609
|
-
if (autonomousTurn && significantAutonomousOutreach) {
|
|
610
|
-
autonomousOutreachBudget.set(outreachBudgetKey, { at: now, result })
|
|
611
|
-
}
|
|
612
|
-
recentConnectorActionCache.set(sendActionKey, { at: now, result })
|
|
613
|
-
return result
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
if (actionName === 'message_react' || actionName === 'message_edit' || actionName === 'message_pin' || actionName === 'message_delete') {
|
|
617
|
-
if (!connectorId) return 'Error: connectorId is required for rich messaging actions.'
|
|
618
|
-
const { getRunningInstance } = await import('../connectors/manager')
|
|
619
|
-
const inst = getRunningInstance(connectorId)
|
|
620
|
-
if (!inst) return `Error: connector "${connectorId}" is not running.`
|
|
621
|
-
|
|
622
|
-
const targetChannel = to?.trim() || ''
|
|
623
|
-
const targetMessageId = message?.trim() || ''
|
|
624
|
-
if (!targetMessageId) return 'Error: message parameter (used as messageId) is required for rich messaging actions.'
|
|
625
|
-
|
|
626
|
-
try {
|
|
627
|
-
if (actionName === 'message_react') {
|
|
628
|
-
if (!inst.sendReaction) return 'Error: this connector does not support reactions.'
|
|
629
|
-
const emoji = caption?.trim() || '👍'
|
|
630
|
-
await inst.sendReaction(targetChannel, targetMessageId, emoji)
|
|
631
|
-
return JSON.stringify({ status: 'reacted', connectorId, messageId: targetMessageId, emoji })
|
|
632
|
-
}
|
|
633
|
-
if (actionName === 'message_edit') {
|
|
634
|
-
if (!inst.editMessage) return 'Error: this connector does not support message editing.'
|
|
635
|
-
const newText = caption?.trim() || ''
|
|
636
|
-
if (!newText) return 'Error: caption (new text) is required for message_edit.'
|
|
637
|
-
await inst.editMessage(targetChannel, targetMessageId, newText)
|
|
638
|
-
return JSON.stringify({ status: 'edited', connectorId, messageId: targetMessageId })
|
|
639
|
-
}
|
|
640
|
-
if (actionName === 'message_delete') {
|
|
641
|
-
if (!inst.deleteMessage) return 'Error: this connector does not support message deletion.'
|
|
642
|
-
await inst.deleteMessage(targetChannel, targetMessageId)
|
|
643
|
-
return JSON.stringify({ status: 'deleted', connectorId, messageId: targetMessageId })
|
|
644
|
-
}
|
|
645
|
-
if (actionName === 'message_pin') {
|
|
646
|
-
if (!inst.pinMessage) return 'Error: this connector does not support message pinning.'
|
|
647
|
-
await inst.pinMessage(targetChannel, targetMessageId)
|
|
648
|
-
return JSON.stringify({ status: 'pinned', connectorId, messageId: targetMessageId })
|
|
649
|
-
}
|
|
650
|
-
} catch (err: unknown) {
|
|
651
|
-
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
return 'Unknown action. Use list_running, list_targets, start, stop, send, send_voice_note, schedule_followup, or message_* actions.'
|
|
656
|
-
} catch (err: unknown) {
|
|
657
|
-
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
241
|
+
/**
|
|
242
|
+
* Core Connector Execution Logic
|
|
243
|
+
*/
|
|
244
|
+
interface ConnectorActionInput {
|
|
245
|
+
action?: string
|
|
246
|
+
connectorId?: string
|
|
247
|
+
platform?: string
|
|
248
|
+
to?: string
|
|
249
|
+
message?: string
|
|
250
|
+
voiceText?: string
|
|
251
|
+
voiceId?: string
|
|
252
|
+
imageUrl?: string
|
|
253
|
+
fileUrl?: string
|
|
254
|
+
mediaPath?: string
|
|
255
|
+
mimeType?: string
|
|
256
|
+
fileName?: string
|
|
257
|
+
caption?: string
|
|
258
|
+
delaySec?: number
|
|
259
|
+
followUpMessage?: string
|
|
260
|
+
followUpDelaySec?: number
|
|
261
|
+
approved?: boolean
|
|
262
|
+
ptt?: boolean
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface ConnectorActionContext {
|
|
266
|
+
cwd: string
|
|
267
|
+
resolveCurrentSession?: () => { messages?: Array<Record<string, unknown>>; id?: string } | null
|
|
268
|
+
ctx?: { sessionId?: string | null }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function executeConnectorAction(input: ConnectorActionInput, bctx: ConnectorActionContext) {
|
|
272
|
+
const normalized = normalizeToolInputArgs((input ?? {}) as Record<string, unknown>)
|
|
273
|
+
const {
|
|
274
|
+
action,
|
|
275
|
+
connectorId,
|
|
276
|
+
platform,
|
|
277
|
+
to,
|
|
278
|
+
message,
|
|
279
|
+
voiceText,
|
|
280
|
+
voiceId,
|
|
281
|
+
imageUrl,
|
|
282
|
+
fileUrl,
|
|
283
|
+
mediaPath,
|
|
284
|
+
mimeType,
|
|
285
|
+
fileName,
|
|
286
|
+
caption,
|
|
287
|
+
approved,
|
|
288
|
+
ptt,
|
|
289
|
+
} = normalized as ConnectorActionInput
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const actionName = String(action)
|
|
293
|
+
const { listRunningConnectors, sendConnectorMessage, getConnectorRecentChannelId } = await import('../connectors/manager')
|
|
294
|
+
const running = listRunningConnectors(platform || undefined)
|
|
295
|
+
|
|
296
|
+
if (actionName === 'list_running' || actionName === 'list_targets') {
|
|
297
|
+
return JSON.stringify(running)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (actionName === 'start') {
|
|
301
|
+
if (!connectorId) {
|
|
302
|
+
const allConnectors = loadConnectors()
|
|
303
|
+
const stopped = Object.values(allConnectors)
|
|
304
|
+
.filter((c) => !platform || c.platform === platform)
|
|
305
|
+
.filter((c) => !running.find((r) => r.id === c.id))
|
|
306
|
+
.map((c) => ({ id: c.id, name: c.name, platform: c.platform }))
|
|
307
|
+
if (!stopped.length) return 'All connectors are already running.'
|
|
308
|
+
return `Error: connectorId is required. Stopped connectors available to start: ${JSON.stringify(stopped)}`
|
|
309
|
+
}
|
|
310
|
+
const { startConnector: doStart } = await import('../connectors/manager')
|
|
311
|
+
await doStart(connectorId)
|
|
312
|
+
return JSON.stringify({ status: 'started', connectorId })
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (actionName === 'stop') {
|
|
316
|
+
if (!connectorId) return 'Error: connectorId is required for stop action.'
|
|
317
|
+
const { stopConnector: doStop } = await import('../connectors/manager')
|
|
318
|
+
await doStop(connectorId)
|
|
319
|
+
return JSON.stringify({ status: 'stopped', connectorId })
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const resolveSelectedConnector = () => {
|
|
323
|
+
if (!running.length) {
|
|
324
|
+
const allConnectors = loadConnectors()
|
|
325
|
+
const configured = Object.values(allConnectors)
|
|
326
|
+
.filter((c) => !platform || c.platform === platform)
|
|
327
|
+
.map((c) => ({ id: c.id, name: c.name, platform: c.platform, agentId: c.agentId || null }))
|
|
328
|
+
if (configured.length) {
|
|
329
|
+
return {
|
|
330
|
+
error: `Error: no running connectors found. Ask user to start one. Configured: ${JSON.stringify(configured)}`,
|
|
658
331
|
}
|
|
659
|
-
}
|
|
660
|
-
{
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
error: `Error: no running connectors. User needs to set one up in the Connectors panel.`,
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const selected = connectorId ? running.find((c) => c.id === connectorId) : running[0]
|
|
338
|
+
if (!selected) return { error: `Error: running connector not found: ${connectorId}` }
|
|
339
|
+
const connectors = loadConnectors()
|
|
340
|
+
const connector = connectors[selected.id]
|
|
341
|
+
if (!connector) return { error: `Error: connector not found: ${selected.id}` }
|
|
342
|
+
return { selected, connector }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (actionName === 'send' || actionName === 'send_voice_note' || actionName === 'schedule_followup') {
|
|
346
|
+
const settings = loadSettings()
|
|
347
|
+
if (settings.safetyRequireApprovalForOutbound === true && approved !== true) {
|
|
348
|
+
return 'Error: outbound connector sends require explicit approval. Re-run with approved=true after user confirmation.'
|
|
349
|
+
}
|
|
350
|
+
const now = Date.now()
|
|
351
|
+
pruneOldConnectorToolState(now)
|
|
352
|
+
const resolved = resolveSelectedConnector()
|
|
353
|
+
if ('error' in resolved) return resolved.error
|
|
354
|
+
const { selected, connector } = resolved
|
|
355
|
+
|
|
356
|
+
const target = pickChannelTarget({
|
|
357
|
+
connector,
|
|
358
|
+
to,
|
|
359
|
+
recentChannelId: getConnectorRecentChannelId(selected.id),
|
|
360
|
+
})
|
|
361
|
+
if (target.error) return target.error
|
|
362
|
+
|
|
363
|
+
let channelId = target.channelId
|
|
364
|
+
if (connector.platform === 'whatsapp') channelId = normalizeWhatsAppTarget(channelId)
|
|
365
|
+
|
|
366
|
+
const currentSession = bctx.resolveCurrentSession?.()
|
|
367
|
+
const latestUserTurn = parseLatestUserTurn(currentSession)
|
|
368
|
+
const sessionId = bctx.ctx?.sessionId || currentSession?.id || 'unknown-session'
|
|
369
|
+
const turnKey = buildConnectorActionKey([sessionId, latestUserTurn.time || 'no-user-turn'])
|
|
370
|
+
const multiOutboundAllowed = userExplicitlyWantsMultipleOutbound(latestUserTurn.text)
|
|
371
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
372
|
+
const _followupExplicitlyRequested = userExplicitlyRequestedFollowup(latestUserTurn.text)
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
374
|
+
const _autonomousTurn = isAutonomousSystemTurn(latestUserTurn.text)
|
|
375
|
+
const existingBudget = connectorTurnSendBudget.get(turnKey)
|
|
376
|
+
|
|
377
|
+
if (!multiOutboundAllowed && existingBudget && now - existingBudget.at <= CONNECTOR_TURN_SEND_TTL_MS && existingBudget.count >= 1) {
|
|
378
|
+
if (existingBudget.lastResult) {
|
|
379
|
+
return normalizeDedupedReplayResult(existingBudget.lastResult, { connectorId: selected.id, platform: selected.platform, to: channelId })
|
|
380
|
+
}
|
|
381
|
+
return JSON.stringify({ status: 'sent', connectorId: selected.id, platform: selected.platform, to: channelId, deduped: true })
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (actionName === 'send_voice_note') {
|
|
385
|
+
const ttsText = (voiceText || message || '').trim()
|
|
386
|
+
if (!ttsText) return 'Error: voiceText or message is required.'
|
|
387
|
+
const audioBuffer = await synthesizeElevenLabsMp3({ text: ttsText, voiceId: voiceId?.trim() || undefined })
|
|
388
|
+
const voiceFileName = `${Date.now()}-${genId()}-voicenote.mp3`
|
|
389
|
+
const voicePath = path.join(UPLOAD_DIR, voiceFileName)
|
|
390
|
+
fs.writeFileSync(voicePath, audioBuffer)
|
|
391
|
+
|
|
392
|
+
const sent = await sendConnectorMessage({
|
|
393
|
+
connectorId: selected.id, channelId, text: '', mediaPath: voicePath, mimeType: 'audio/mpeg',
|
|
394
|
+
fileName: fileName?.trim() || 'voicenote.mp3', caption: caption?.trim() || undefined, ptt: ptt ?? true,
|
|
395
|
+
})
|
|
396
|
+
const result = JSON.stringify({ status: 'voice_sent', connectorId: sent.connectorId, platform: sent.platform, to: sent.channelId, voiceFile: voicePath })
|
|
397
|
+
connectorTurnSendBudget.set(turnKey, { count: (existingBudget?.count || 0) + 1, at: now, lastResult: result })
|
|
398
|
+
return result
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const media = resolveConnectorMediaInput({ cwd: bctx.cwd, mediaPath, imageUrl, fileUrl })
|
|
402
|
+
if (media.error) return media.error
|
|
403
|
+
|
|
404
|
+
if (actionName === 'send' && !message?.trim() && !media.mediaPath && !media.imageUrl && !media.fileUrl) {
|
|
405
|
+
return 'Error: message or media required.'
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const sent = await sendConnectorMessage({
|
|
409
|
+
connectorId: selected.id, channelId, text: message?.trim() || '',
|
|
410
|
+
imageUrl: media.imageUrl, fileUrl: media.fileUrl, mediaPath: media.mediaPath,
|
|
411
|
+
mimeType: mimeType?.trim() || undefined, fileName: fileName?.trim() || undefined,
|
|
412
|
+
caption: caption?.trim() || undefined, ptt: ptt ?? undefined,
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
const result = JSON.stringify({ status: 'sent', connectorId: sent.connectorId, platform: sent.platform, to: sent.channelId, messageId: sent.messageId || null })
|
|
416
|
+
connectorTurnSendBudget.set(turnKey, { count: (existingBudget?.count || 0) + 1, at: now, lastResult: result })
|
|
417
|
+
return result
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return 'Unknown action.'
|
|
421
|
+
} catch (err: unknown) {
|
|
422
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
688
423
|
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Register as a Built-in Plugin
|
|
428
|
+
*/
|
|
429
|
+
const ConnectorPlugin: Plugin = {
|
|
430
|
+
name: 'Core Connectors',
|
|
431
|
+
description: 'Manage and send messages through chat platform connectors (WhatsApp, Telegram, Slack, etc.).',
|
|
432
|
+
hooks: {} as PluginHooks,
|
|
433
|
+
tools: [
|
|
434
|
+
{
|
|
435
|
+
name: 'connector_message_tool',
|
|
436
|
+
description: 'Send and manage outbound messages across chat platforms.',
|
|
437
|
+
parameters: {
|
|
438
|
+
type: 'object',
|
|
439
|
+
properties: {
|
|
440
|
+
action: { type: 'string', enum: ['list_running', 'start', 'stop', 'send', 'send_voice_note'] },
|
|
441
|
+
connectorId: { type: 'string' },
|
|
442
|
+
platform: { type: 'string' },
|
|
443
|
+
to: { type: 'string' },
|
|
444
|
+
message: { type: 'string' }
|
|
445
|
+
},
|
|
446
|
+
required: ['action']
|
|
447
|
+
},
|
|
448
|
+
execute: async (args, context) => executeConnectorAction(args as ConnectorActionInput, { ...context.session, cwd: context.session.cwd || process.cwd() })
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
getPluginManager().registerBuiltin('connectors', ConnectorPlugin)
|
|
689
454
|
|
|
690
|
-
|
|
455
|
+
/**
|
|
456
|
+
* Legacy Bridge
|
|
457
|
+
*/
|
|
458
|
+
export function buildConnectorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
459
|
+
if (!bctx.hasTool('manage_connectors')) return []
|
|
460
|
+
return [
|
|
461
|
+
tool(
|
|
462
|
+
async (args) => executeConnectorAction(args as ConnectorActionInput, bctx),
|
|
463
|
+
{
|
|
464
|
+
name: 'connector_message_tool',
|
|
465
|
+
description: ConnectorPlugin.tools![0].description,
|
|
466
|
+
schema: z.object({}).passthrough()
|
|
467
|
+
}
|
|
468
|
+
)
|
|
469
|
+
]
|
|
691
470
|
}
|