@swarmclawai/swarmclaw 0.7.1 → 0.7.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 +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +948 -112
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +14 -40
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +28 -1103
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
4
|
+
import { getPluginManager } from '../plugins'
|
|
5
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
6
|
+
import type { ToolBuildContext } from './context'
|
|
7
|
+
|
|
8
|
+
type CalendarProvider = 'google' | 'outlook'
|
|
9
|
+
|
|
10
|
+
interface CalendarConfig {
|
|
11
|
+
provider: CalendarProvider
|
|
12
|
+
accessToken: string
|
|
13
|
+
calendarId: string
|
|
14
|
+
refreshToken: string
|
|
15
|
+
clientId: string
|
|
16
|
+
clientSecret: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getConfig(): CalendarConfig {
|
|
20
|
+
const ps = getPluginManager().getPluginSettings('calendar')
|
|
21
|
+
return {
|
|
22
|
+
provider: (ps.provider as CalendarProvider) || 'google',
|
|
23
|
+
accessToken: (ps.accessToken as string) || '',
|
|
24
|
+
calendarId: (ps.calendarId as string) || 'primary',
|
|
25
|
+
refreshToken: (ps.refreshToken as string) || '',
|
|
26
|
+
clientId: (ps.clientId as string) || '',
|
|
27
|
+
clientSecret: (ps.clientSecret as string) || '',
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Try to refresh the Google OAuth access token using the refresh token. */
|
|
32
|
+
async function refreshGoogleToken(cfg: CalendarConfig): Promise<string | null> {
|
|
33
|
+
if (!cfg.refreshToken || !cfg.clientId || !cfg.clientSecret) return null
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
38
|
+
body: new URLSearchParams({
|
|
39
|
+
grant_type: 'refresh_token',
|
|
40
|
+
refresh_token: cfg.refreshToken,
|
|
41
|
+
client_id: cfg.clientId,
|
|
42
|
+
client_secret: cfg.clientSecret,
|
|
43
|
+
}),
|
|
44
|
+
signal: AbortSignal.timeout(10_000),
|
|
45
|
+
})
|
|
46
|
+
if (!res.ok) return null
|
|
47
|
+
const data = await res.json()
|
|
48
|
+
const newToken = data?.access_token as string | undefined
|
|
49
|
+
if (newToken) {
|
|
50
|
+
getPluginManager().setPluginSettings('calendar', { ...cfg, accessToken: newToken })
|
|
51
|
+
}
|
|
52
|
+
return newToken || null
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function googleRequest(method: string, urlPath: string, cfg: CalendarConfig, body?: unknown): Promise<{ ok: boolean; data?: unknown; error?: string }> {
|
|
59
|
+
let token = cfg.accessToken
|
|
60
|
+
const baseUrl = 'https://www.googleapis.com/calendar/v3'
|
|
61
|
+
|
|
62
|
+
const doFetch = async (t: string) => {
|
|
63
|
+
const init: RequestInit = {
|
|
64
|
+
method,
|
|
65
|
+
headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' },
|
|
66
|
+
signal: AbortSignal.timeout(15_000),
|
|
67
|
+
}
|
|
68
|
+
if (body && method !== 'GET' && method !== 'DELETE') init.body = JSON.stringify(body)
|
|
69
|
+
return fetch(`${baseUrl}${urlPath}`, init)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let res = await doFetch(token)
|
|
73
|
+
if (res.status === 401) {
|
|
74
|
+
const refreshed = await refreshGoogleToken(cfg)
|
|
75
|
+
if (refreshed) {
|
|
76
|
+
token = refreshed
|
|
77
|
+
res = await doFetch(token)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const errText = await res.text().catch(() => '')
|
|
83
|
+
return { ok: false, error: `Google Calendar ${res.status}: ${errText.slice(0, 300)}` }
|
|
84
|
+
}
|
|
85
|
+
if (method === 'DELETE') return { ok: true }
|
|
86
|
+
const data = await res.json()
|
|
87
|
+
return { ok: true, data }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function outlookRequest(method: string, urlPath: string, cfg: CalendarConfig, body?: unknown): Promise<{ ok: boolean; data?: unknown; error?: string }> {
|
|
91
|
+
const baseUrl = 'https://graph.microsoft.com/v1.0/me'
|
|
92
|
+
const init: RequestInit = {
|
|
93
|
+
method,
|
|
94
|
+
headers: { Authorization: `Bearer ${cfg.accessToken}`, 'Content-Type': 'application/json' },
|
|
95
|
+
signal: AbortSignal.timeout(15_000),
|
|
96
|
+
}
|
|
97
|
+
if (body && method !== 'GET' && method !== 'DELETE') init.body = JSON.stringify(body)
|
|
98
|
+
const res = await fetch(`${baseUrl}${urlPath}`, init)
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
const errText = await res.text().catch(() => '')
|
|
101
|
+
return { ok: false, error: `Outlook ${res.status}: ${errText.slice(0, 300)}` }
|
|
102
|
+
}
|
|
103
|
+
if (method === 'DELETE') return { ok: true }
|
|
104
|
+
const data = await res.json()
|
|
105
|
+
return { ok: true, data }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function formatEvent(e: Record<string, unknown>): Record<string, unknown> {
|
|
109
|
+
return {
|
|
110
|
+
id: e.id,
|
|
111
|
+
summary: e.summary ?? e.subject,
|
|
112
|
+
start: (e.start as Record<string, unknown>)?.dateTime ?? (e.start as Record<string, unknown>)?.date ?? e.start,
|
|
113
|
+
end: (e.end as Record<string, unknown>)?.dateTime ?? (e.end as Record<string, unknown>)?.date ?? e.end,
|
|
114
|
+
location: e.location ?? (e.location as unknown as Record<string, unknown>)?.displayName,
|
|
115
|
+
description: typeof e.description === 'string' ? e.description.slice(0, 200) : (e.body as Record<string, unknown>)?.content?.toString().slice(0, 200),
|
|
116
|
+
status: e.status ?? e.showAs,
|
|
117
|
+
htmlLink: e.htmlLink ?? e.webLink,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function executeCalendar(args: Record<string, unknown>): Promise<string> {
|
|
122
|
+
const normalized = normalizeToolInputArgs(args)
|
|
123
|
+
const action = String(normalized.action || 'list')
|
|
124
|
+
const cfg = getConfig()
|
|
125
|
+
|
|
126
|
+
if (!cfg.accessToken) {
|
|
127
|
+
return 'Error: Calendar not configured. Ask the user to add their access token in Plugin Settings > Calendar.'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
switch (action) {
|
|
132
|
+
case 'list': {
|
|
133
|
+
const timeMin = String(normalized.timeMin || new Date().toISOString())
|
|
134
|
+
const timeMax = normalized.timeMax as string | undefined
|
|
135
|
+
const maxResults = Math.min(Number(normalized.maxResults) || 20, 50)
|
|
136
|
+
|
|
137
|
+
if (cfg.provider === 'outlook') {
|
|
138
|
+
const params = new URLSearchParams({
|
|
139
|
+
$top: String(maxResults),
|
|
140
|
+
$orderby: 'start/dateTime',
|
|
141
|
+
$filter: `start/dateTime ge '${timeMin}'${timeMax ? ` and end/dateTime le '${timeMax}'` : ''}`,
|
|
142
|
+
})
|
|
143
|
+
const r = await outlookRequest('GET', `/calendar/events?${params}`, cfg)
|
|
144
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
145
|
+
const events = ((r.data as Record<string, unknown>)?.value as Record<string, unknown>[]) ?? []
|
|
146
|
+
return JSON.stringify(events.map(formatEvent))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const params = new URLSearchParams({
|
|
150
|
+
timeMin,
|
|
151
|
+
maxResults: String(maxResults),
|
|
152
|
+
singleEvents: 'true',
|
|
153
|
+
orderBy: 'startTime',
|
|
154
|
+
})
|
|
155
|
+
if (timeMax) params.set('timeMax', timeMax)
|
|
156
|
+
const r = await googleRequest('GET', `/calendars/${encodeURIComponent(cfg.calendarId)}/events?${params}`, cfg)
|
|
157
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
158
|
+
const events = ((r.data as Record<string, unknown>)?.items as Record<string, unknown>[]) ?? []
|
|
159
|
+
return JSON.stringify(events.map(formatEvent))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'create': {
|
|
163
|
+
const summary = String(normalized.summary || normalized.title || '').trim()
|
|
164
|
+
if (!summary) return 'Error: "summary" (event title) is required.'
|
|
165
|
+
const start = String(normalized.start || '').trim()
|
|
166
|
+
const end = String(normalized.end || '').trim()
|
|
167
|
+
if (!start) return 'Error: "start" (ISO datetime) is required.'
|
|
168
|
+
|
|
169
|
+
const description = (normalized.description as string) || ''
|
|
170
|
+
const location = (normalized.location as string) || ''
|
|
171
|
+
|
|
172
|
+
if (cfg.provider === 'outlook') {
|
|
173
|
+
const body = {
|
|
174
|
+
subject: summary,
|
|
175
|
+
body: { contentType: 'text', content: description },
|
|
176
|
+
start: { dateTime: start, timeZone: 'UTC' },
|
|
177
|
+
end: { dateTime: end || new Date(new Date(start).getTime() + 3600_000).toISOString(), timeZone: 'UTC' },
|
|
178
|
+
location: { displayName: location },
|
|
179
|
+
}
|
|
180
|
+
const r = await outlookRequest('POST', '/calendar/events', cfg, body)
|
|
181
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
182
|
+
return `Event created: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const body = {
|
|
186
|
+
summary,
|
|
187
|
+
description,
|
|
188
|
+
location,
|
|
189
|
+
start: { dateTime: start, timeZone: 'UTC' },
|
|
190
|
+
end: { dateTime: end || new Date(new Date(start).getTime() + 3600_000).toISOString(), timeZone: 'UTC' },
|
|
191
|
+
}
|
|
192
|
+
const r = await googleRequest('POST', `/calendars/${encodeURIComponent(cfg.calendarId)}/events`, cfg, body)
|
|
193
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
194
|
+
return `Event created: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case 'update': {
|
|
198
|
+
const eventId = String(normalized.eventId || normalized.id || '').trim()
|
|
199
|
+
if (!eventId) return 'Error: "eventId" is required.'
|
|
200
|
+
const updates: Record<string, unknown> = {}
|
|
201
|
+
if (normalized.summary) updates.summary = String(normalized.summary)
|
|
202
|
+
if (normalized.description) updates.description = String(normalized.description)
|
|
203
|
+
if (normalized.location) updates.location = String(normalized.location)
|
|
204
|
+
if (normalized.start) updates.start = { dateTime: String(normalized.start), timeZone: 'UTC' }
|
|
205
|
+
if (normalized.end) updates.end = { dateTime: String(normalized.end), timeZone: 'UTC' }
|
|
206
|
+
|
|
207
|
+
if (cfg.provider === 'outlook') {
|
|
208
|
+
const outlookUpdates: Record<string, unknown> = {}
|
|
209
|
+
if (normalized.summary) outlookUpdates.subject = String(normalized.summary)
|
|
210
|
+
if (normalized.description) outlookUpdates.body = { contentType: 'text', content: String(normalized.description) }
|
|
211
|
+
if (normalized.location) outlookUpdates.location = { displayName: String(normalized.location) }
|
|
212
|
+
if (normalized.start) outlookUpdates.start = { dateTime: String(normalized.start), timeZone: 'UTC' }
|
|
213
|
+
if (normalized.end) outlookUpdates.end = { dateTime: String(normalized.end), timeZone: 'UTC' }
|
|
214
|
+
const r = await outlookRequest('PATCH', `/calendar/events/${eventId}`, cfg, outlookUpdates)
|
|
215
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
216
|
+
return `Event updated: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const r = await googleRequest('PATCH', `/calendars/${encodeURIComponent(cfg.calendarId)}/events/${eventId}`, cfg, updates)
|
|
220
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
221
|
+
return `Event updated: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case 'delete': {
|
|
225
|
+
const eventId = String(normalized.eventId || normalized.id || '').trim()
|
|
226
|
+
if (!eventId) return 'Error: "eventId" is required.'
|
|
227
|
+
|
|
228
|
+
if (cfg.provider === 'outlook') {
|
|
229
|
+
const r = await outlookRequest('DELETE', `/calendar/events/${eventId}`, cfg)
|
|
230
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
231
|
+
return `Event ${eventId} deleted.`
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const r = await googleRequest('DELETE', `/calendars/${encodeURIComponent(cfg.calendarId)}/events/${eventId}`, cfg)
|
|
235
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
236
|
+
return `Event ${eventId} deleted.`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case 'status': {
|
|
240
|
+
return JSON.stringify({
|
|
241
|
+
configured: true,
|
|
242
|
+
provider: cfg.provider,
|
|
243
|
+
calendarId: cfg.calendarId,
|
|
244
|
+
hasRefreshToken: !!cfg.refreshToken,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
default:
|
|
249
|
+
return `Error: Unknown action "${action}". Use: list, create, update, delete, status.`
|
|
250
|
+
}
|
|
251
|
+
} catch (err: unknown) {
|
|
252
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const CalendarPlugin: Plugin = {
|
|
257
|
+
name: 'Calendar',
|
|
258
|
+
enabledByDefault: false,
|
|
259
|
+
description: 'Manage Google Calendar or Outlook calendar events — list, create, update, delete.',
|
|
260
|
+
hooks: {
|
|
261
|
+
getCapabilityDescription: () =>
|
|
262
|
+
'I can manage calendar events using `calendar`: list upcoming events, create new ones, update or delete existing events. Supports Google Calendar and Outlook.',
|
|
263
|
+
} as PluginHooks,
|
|
264
|
+
tools: [
|
|
265
|
+
{
|
|
266
|
+
name: 'calendar',
|
|
267
|
+
description: 'Manage calendar events. Actions: list (upcoming events), create (new event), update (modify event), delete (remove event), status (check config).',
|
|
268
|
+
parameters: {
|
|
269
|
+
type: 'object',
|
|
270
|
+
properties: {
|
|
271
|
+
action: { type: 'string', enum: ['list', 'create', 'update', 'delete', 'status'], description: 'Action to perform' },
|
|
272
|
+
summary: { type: 'string', description: 'Event title (for create/update)' },
|
|
273
|
+
description: { type: 'string', description: 'Event description (for create/update)' },
|
|
274
|
+
location: { type: 'string', description: 'Event location (for create/update)' },
|
|
275
|
+
start: { type: 'string', description: 'Start datetime in ISO 8601 format (for create/update)' },
|
|
276
|
+
end: { type: 'string', description: 'End datetime in ISO 8601 format (for create/update). Defaults to 1 hour after start.' },
|
|
277
|
+
eventId: { type: 'string', description: 'Event ID (for update/delete)' },
|
|
278
|
+
timeMin: { type: 'string', description: 'List events starting from this ISO datetime (default: now)' },
|
|
279
|
+
timeMax: { type: 'string', description: 'List events up to this ISO datetime' },
|
|
280
|
+
maxResults: { type: 'number', description: 'Max events to return (default: 20, max: 50)' },
|
|
281
|
+
},
|
|
282
|
+
required: ['action'],
|
|
283
|
+
},
|
|
284
|
+
execute: async (args) => executeCalendar(args),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
ui: {
|
|
288
|
+
settingsFields: [
|
|
289
|
+
{
|
|
290
|
+
key: 'provider',
|
|
291
|
+
label: 'Calendar Provider',
|
|
292
|
+
type: 'select',
|
|
293
|
+
options: [
|
|
294
|
+
{ value: 'google', label: 'Google Calendar' },
|
|
295
|
+
{ value: 'outlook', label: 'Microsoft Outlook' },
|
|
296
|
+
],
|
|
297
|
+
defaultValue: 'google',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
key: 'accessToken',
|
|
301
|
+
label: 'Access Token',
|
|
302
|
+
type: 'secret',
|
|
303
|
+
required: true,
|
|
304
|
+
placeholder: 'ya29.a0...',
|
|
305
|
+
help: 'OAuth2 access token for the calendar API. For Google: generate via OAuth2 playground or a service account.',
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: 'refreshToken',
|
|
309
|
+
label: 'Refresh Token (Google)',
|
|
310
|
+
type: 'secret',
|
|
311
|
+
placeholder: '1//0e...',
|
|
312
|
+
help: 'Google OAuth2 refresh token. When set, the plugin auto-refreshes expired access tokens.',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
key: 'clientId',
|
|
316
|
+
label: 'Client ID (Google)',
|
|
317
|
+
type: 'text',
|
|
318
|
+
placeholder: '123456789.apps.googleusercontent.com',
|
|
319
|
+
help: 'Google OAuth2 client ID. Required for token refresh.',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
key: 'clientSecret',
|
|
323
|
+
label: 'Client Secret (Google)',
|
|
324
|
+
type: 'secret',
|
|
325
|
+
placeholder: 'GOCSPX-...',
|
|
326
|
+
help: 'Google OAuth2 client secret. Required for token refresh.',
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
key: 'calendarId',
|
|
330
|
+
label: 'Calendar ID',
|
|
331
|
+
type: 'text',
|
|
332
|
+
defaultValue: 'primary',
|
|
333
|
+
placeholder: 'primary',
|
|
334
|
+
help: 'Google Calendar ID (default: "primary"). For Outlook, this is ignored.',
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
getPluginManager().registerBuiltin('calendar', CalendarPlugin)
|
|
341
|
+
|
|
342
|
+
export function buildCalendarTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
343
|
+
if (!bctx.hasPlugin('calendar')) return []
|
|
344
|
+
|
|
345
|
+
return [
|
|
346
|
+
tool(
|
|
347
|
+
async (args) => executeCalendar(args),
|
|
348
|
+
{
|
|
349
|
+
name: 'calendar',
|
|
350
|
+
description: CalendarPlugin.tools![0].description,
|
|
351
|
+
schema: z.object({
|
|
352
|
+
action: z.enum(['list', 'create', 'update', 'delete', 'status']).describe('Action to perform'),
|
|
353
|
+
summary: z.string().optional().describe('Event title'),
|
|
354
|
+
description: z.string().optional().describe('Event description'),
|
|
355
|
+
location: z.string().optional().describe('Event location'),
|
|
356
|
+
start: z.string().optional().describe('Start datetime (ISO 8601)'),
|
|
357
|
+
end: z.string().optional().describe('End datetime (ISO 8601)'),
|
|
358
|
+
eventId: z.string().optional().describe('Event ID (for update/delete)'),
|
|
359
|
+
timeMin: z.string().optional().describe('List events from this datetime'),
|
|
360
|
+
timeMax: z.string().optional().describe('List events until this datetime'),
|
|
361
|
+
maxResults: z.number().optional().describe('Max results (default 20, max 50)'),
|
|
362
|
+
}),
|
|
363
|
+
},
|
|
364
|
+
),
|
|
365
|
+
]
|
|
366
|
+
}
|
|
@@ -88,7 +88,7 @@ getPluginManager().registerBuiltin('canvas', CanvasPlugin)
|
|
|
88
88
|
* Legacy Bridge
|
|
89
89
|
*/
|
|
90
90
|
export function buildCanvasTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
91
|
-
if (!bctx.
|
|
91
|
+
if (!bctx.hasPlugin('canvas')) return []
|
|
92
92
|
return [
|
|
93
93
|
tool(
|
|
94
94
|
async (args) => executeCanvasAction(args, { sessionId: bctx.ctx?.sessionId || undefined }),
|
|
@@ -107,7 +107,9 @@ async function executeChatroomAction(args: Record<string, unknown>, context: { a
|
|
|
107
107
|
const ChatroomPlugin: Plugin = {
|
|
108
108
|
name: 'Core Chatrooms',
|
|
109
109
|
description: 'Manage SwarmClaw routing rules and multi-agent chatrooms.',
|
|
110
|
-
hooks: {
|
|
110
|
+
hooks: {
|
|
111
|
+
getCapabilityDescription: () => 'I can create and participate in chatrooms (`manage_chatrooms`) for multi-agent collaboration with @mention-based discussions.',
|
|
112
|
+
} as PluginHooks,
|
|
111
113
|
tools: [
|
|
112
114
|
{
|
|
113
115
|
name: 'manage_chatrooms',
|
|
@@ -134,7 +136,7 @@ getPluginManager().registerBuiltin('chatroom', ChatroomPlugin)
|
|
|
134
136
|
* Legacy Bridge
|
|
135
137
|
*/
|
|
136
138
|
export function buildChatroomTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
137
|
-
if (!bctx.
|
|
139
|
+
if (!bctx.hasPlugin('manage_chatrooms')) return []
|
|
138
140
|
return [
|
|
139
141
|
tool(
|
|
140
142
|
async (args) => executeChatroomAction(args, { agentId: bctx.ctx?.agentId }),
|
|
@@ -60,8 +60,6 @@ function isAutonomousSystemTurn(userText: string): boolean {
|
|
|
60
60
|
if (!userText) return false
|
|
61
61
|
const text = userText.toUpperCase()
|
|
62
62
|
return text.includes('AGENT_HEARTBEAT_WAKE')
|
|
63
|
-
|| text.includes('SWARM_MAIN_MISSION_TICK')
|
|
64
|
-
|| text.includes('SWARM_MAIN_AUTO_FOLLOWUP')
|
|
65
63
|
|| text.includes('SWARM_HEARTBEAT_CHECK')
|
|
66
64
|
}
|
|
67
65
|
|
|
@@ -247,6 +245,9 @@ interface ConnectorActionInput {
|
|
|
247
245
|
platform?: string
|
|
248
246
|
to?: string
|
|
249
247
|
message?: string
|
|
248
|
+
messageId?: string
|
|
249
|
+
targetMessage?: 'last_inbound' | 'last_outbound'
|
|
250
|
+
emoji?: string
|
|
250
251
|
voiceText?: string
|
|
251
252
|
voiceId?: string
|
|
252
253
|
imageUrl?: string
|
|
@@ -255,9 +256,12 @@ interface ConnectorActionInput {
|
|
|
255
256
|
mimeType?: string
|
|
256
257
|
fileName?: string
|
|
257
258
|
caption?: string
|
|
259
|
+
replyToMessageId?: string
|
|
260
|
+
threadId?: string
|
|
258
261
|
delaySec?: number
|
|
259
262
|
followUpMessage?: string
|
|
260
263
|
followUpDelaySec?: number
|
|
264
|
+
dedupeKey?: string
|
|
261
265
|
approved?: boolean
|
|
262
266
|
ptt?: boolean
|
|
263
267
|
}
|
|
@@ -284,13 +288,25 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
284
288
|
mimeType,
|
|
285
289
|
fileName,
|
|
286
290
|
caption,
|
|
291
|
+
messageId,
|
|
292
|
+
targetMessage,
|
|
293
|
+
emoji,
|
|
294
|
+
replyToMessageId,
|
|
295
|
+
threadId,
|
|
296
|
+
dedupeKey,
|
|
287
297
|
approved,
|
|
288
298
|
ptt,
|
|
289
299
|
} = normalized as ConnectorActionInput
|
|
290
300
|
|
|
291
301
|
try {
|
|
292
302
|
const actionName = String(action)
|
|
293
|
-
const {
|
|
303
|
+
const {
|
|
304
|
+
listRunningConnectors,
|
|
305
|
+
sendConnectorMessage,
|
|
306
|
+
getConnectorRecentChannelId,
|
|
307
|
+
scheduleConnectorFollowUp,
|
|
308
|
+
performConnectorMessageAction,
|
|
309
|
+
} = await import('../connectors/manager')
|
|
294
310
|
const running = listRunningConnectors(platform || undefined)
|
|
295
311
|
|
|
296
312
|
if (actionName === 'list_running' || actionName === 'list_targets') {
|
|
@@ -342,6 +358,9 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
342
358
|
return { selected, connector }
|
|
343
359
|
}
|
|
344
360
|
|
|
361
|
+
const currentSession = bctx.resolveCurrentSession?.()
|
|
362
|
+
const sessionId = bctx.ctx?.sessionId || currentSession?.id || undefined
|
|
363
|
+
|
|
345
364
|
if (actionName === 'send' || actionName === 'send_voice_note' || actionName === 'schedule_followup') {
|
|
346
365
|
const settings = loadSettings()
|
|
347
366
|
if (settings.safetyRequireApprovalForOutbound === true && approved !== true) {
|
|
@@ -363,9 +382,7 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
363
382
|
let channelId = target.channelId
|
|
364
383
|
if (connector.platform === 'whatsapp') channelId = normalizeWhatsAppTarget(channelId)
|
|
365
384
|
|
|
366
|
-
const currentSession = bctx.resolveCurrentSession?.()
|
|
367
385
|
const latestUserTurn = parseLatestUserTurn(currentSession)
|
|
368
|
-
const sessionId = bctx.ctx?.sessionId || currentSession?.id || 'unknown-session'
|
|
369
386
|
const turnKey = buildConnectorActionKey([sessionId, latestUserTurn.time || 'no-user-turn'])
|
|
370
387
|
const multiOutboundAllowed = userExplicitlyWantsMultipleOutbound(latestUserTurn.text)
|
|
371
388
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -392,6 +409,9 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
392
409
|
const sent = await sendConnectorMessage({
|
|
393
410
|
connectorId: selected.id, channelId, text: '', mediaPath: voicePath, mimeType: 'audio/mpeg',
|
|
394
411
|
fileName: fileName?.trim() || 'voicenote.mp3', caption: caption?.trim() || undefined, ptt: ptt ?? true,
|
|
412
|
+
sessionId,
|
|
413
|
+
replyToMessageId: replyToMessageId?.trim() || undefined,
|
|
414
|
+
threadId: threadId?.trim() || undefined,
|
|
395
415
|
})
|
|
396
416
|
const result = JSON.stringify({ status: 'voice_sent', connectorId: sent.connectorId, platform: sent.platform, to: sent.channelId, voiceFile: voicePath })
|
|
397
417
|
connectorTurnSendBudget.set(turnKey, { count: (existingBudget?.count || 0) + 1, at: now, lastResult: result })
|
|
@@ -405,11 +425,54 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
405
425
|
return 'Error: message or media required.'
|
|
406
426
|
}
|
|
407
427
|
|
|
428
|
+
if (actionName === 'schedule_followup') {
|
|
429
|
+
const followupText = (normalized.followUpMessage as string | undefined)?.trim() || message?.trim() || ''
|
|
430
|
+
if (!followupText && !media.mediaPath && !media.imageUrl && !media.fileUrl) {
|
|
431
|
+
return 'Error: follow-up message or media required.'
|
|
432
|
+
}
|
|
433
|
+
const followupDelay = (() => {
|
|
434
|
+
const direct = Number(normalized.followUpDelaySec)
|
|
435
|
+
if (Number.isFinite(direct) && direct >= 0) return direct
|
|
436
|
+
const fallback = Number(normalized.delaySec)
|
|
437
|
+
if (Number.isFinite(fallback) && fallback >= 0) return fallback
|
|
438
|
+
return 300
|
|
439
|
+
})()
|
|
440
|
+
const scheduled = scheduleConnectorFollowUp({
|
|
441
|
+
connectorId: selected.id,
|
|
442
|
+
channelId,
|
|
443
|
+
text: followupText,
|
|
444
|
+
sessionId,
|
|
445
|
+
delaySec: followupDelay,
|
|
446
|
+
dedupeKey: dedupeKey?.trim() || undefined,
|
|
447
|
+
imageUrl: media.imageUrl,
|
|
448
|
+
fileUrl: media.fileUrl,
|
|
449
|
+
mediaPath: media.mediaPath,
|
|
450
|
+
mimeType: mimeType?.trim() || undefined,
|
|
451
|
+
fileName: fileName?.trim() || undefined,
|
|
452
|
+
caption: caption?.trim() || undefined,
|
|
453
|
+
replyToMessageId: replyToMessageId?.trim() || undefined,
|
|
454
|
+
threadId: threadId?.trim() || undefined,
|
|
455
|
+
ptt: ptt ?? undefined,
|
|
456
|
+
})
|
|
457
|
+
return JSON.stringify({
|
|
458
|
+
status: 'scheduled',
|
|
459
|
+
connectorId: selected.id,
|
|
460
|
+
platform: selected.platform,
|
|
461
|
+
to: channelId,
|
|
462
|
+
followUpId: scheduled.followUpId,
|
|
463
|
+
sendAt: scheduled.sendAt,
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
|
|
408
467
|
const sent = await sendConnectorMessage({
|
|
409
468
|
connectorId: selected.id, channelId, text: message?.trim() || '',
|
|
469
|
+
sessionId,
|
|
410
470
|
imageUrl: media.imageUrl, fileUrl: media.fileUrl, mediaPath: media.mediaPath,
|
|
411
471
|
mimeType: mimeType?.trim() || undefined, fileName: fileName?.trim() || undefined,
|
|
412
|
-
caption: caption?.trim() || undefined,
|
|
472
|
+
caption: caption?.trim() || undefined,
|
|
473
|
+
replyToMessageId: replyToMessageId?.trim() || undefined,
|
|
474
|
+
threadId: threadId?.trim() || undefined,
|
|
475
|
+
ptt: ptt ?? undefined,
|
|
413
476
|
})
|
|
414
477
|
|
|
415
478
|
const result = JSON.stringify({ status: 'sent', connectorId: sent.connectorId, platform: sent.platform, to: sent.channelId, messageId: sent.messageId || null })
|
|
@@ -417,6 +480,35 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
417
480
|
return result
|
|
418
481
|
}
|
|
419
482
|
|
|
483
|
+
if (actionName === 'react' || actionName === 'edit' || actionName === 'delete' || actionName === 'pin') {
|
|
484
|
+
const resolved = resolveSelectedConnector()
|
|
485
|
+
if ('error' in resolved) return resolved.error
|
|
486
|
+
const { selected } = resolved
|
|
487
|
+
const target = pickChannelTarget({
|
|
488
|
+
connector: resolved.connector,
|
|
489
|
+
to,
|
|
490
|
+
recentChannelId: getConnectorRecentChannelId(selected.id),
|
|
491
|
+
})
|
|
492
|
+
if (target.error) return target.error
|
|
493
|
+
const result = await performConnectorMessageAction({
|
|
494
|
+
connectorId: selected.id,
|
|
495
|
+
channelId: selected.platform === 'whatsapp' ? normalizeWhatsAppTarget(target.channelId) : target.channelId,
|
|
496
|
+
action: actionName,
|
|
497
|
+
messageId: messageId?.trim() || undefined,
|
|
498
|
+
emoji: emoji?.trim() || undefined,
|
|
499
|
+
text: message?.trim() || undefined,
|
|
500
|
+
sessionId,
|
|
501
|
+
targetMessage,
|
|
502
|
+
})
|
|
503
|
+
return JSON.stringify({
|
|
504
|
+
status: actionName,
|
|
505
|
+
connectorId: result.connectorId,
|
|
506
|
+
platform: result.platform,
|
|
507
|
+
to: result.channelId,
|
|
508
|
+
messageId: result.messageId || null,
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
|
|
420
512
|
return 'Unknown action.'
|
|
421
513
|
} catch (err: unknown) {
|
|
422
514
|
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -429,7 +521,10 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
429
521
|
const ConnectorPlugin: Plugin = {
|
|
430
522
|
name: 'Core Connectors',
|
|
431
523
|
description: 'Manage and send messages through chat platform connectors (WhatsApp, Telegram, Slack, etc.).',
|
|
432
|
-
hooks: {
|
|
524
|
+
hooks: {
|
|
525
|
+
getCapabilityDescription: () => 'I can manage messaging channels (`manage_connectors`) — WhatsApp, Telegram, Slack, Discord — and send proactive messages via `connector_message_tool`.',
|
|
526
|
+
getOperatingGuidance: () => 'Connectors: proactive outreach for significant events only. Keep messages concise, no duplicates.',
|
|
527
|
+
} as PluginHooks,
|
|
433
528
|
tools: [
|
|
434
529
|
{
|
|
435
530
|
name: 'connector_message_tool',
|
|
@@ -437,11 +532,20 @@ const ConnectorPlugin: Plugin = {
|
|
|
437
532
|
parameters: {
|
|
438
533
|
type: 'object',
|
|
439
534
|
properties: {
|
|
440
|
-
action: { type: 'string', enum: ['list_running', 'start', 'stop', 'send', 'send_voice_note'] },
|
|
535
|
+
action: { type: 'string', enum: ['list_running', 'start', 'stop', 'send', 'send_voice_note', 'schedule_followup', 'react', 'edit', 'delete', 'pin'] },
|
|
441
536
|
connectorId: { type: 'string' },
|
|
442
537
|
platform: { type: 'string' },
|
|
443
538
|
to: { type: 'string' },
|
|
444
|
-
message: { type: 'string' }
|
|
539
|
+
message: { type: 'string' },
|
|
540
|
+
messageId: { type: 'string' },
|
|
541
|
+
targetMessage: { type: 'string', enum: ['last_inbound', 'last_outbound'] },
|
|
542
|
+
emoji: { type: 'string' },
|
|
543
|
+
replyToMessageId: { type: 'string' },
|
|
544
|
+
threadId: { type: 'string' },
|
|
545
|
+
delaySec: { type: 'number' },
|
|
546
|
+
followUpMessage: { type: 'string' },
|
|
547
|
+
followUpDelaySec: { type: 'number' },
|
|
548
|
+
dedupeKey: { type: 'string' },
|
|
445
549
|
},
|
|
446
550
|
required: ['action']
|
|
447
551
|
},
|
|
@@ -456,7 +560,7 @@ getPluginManager().registerBuiltin('connectors', ConnectorPlugin)
|
|
|
456
560
|
* Legacy Bridge
|
|
457
561
|
*/
|
|
458
562
|
export function buildConnectorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
459
|
-
if (!bctx.
|
|
563
|
+
if (!bctx.hasPlugin('manage_connectors')) return []
|
|
460
564
|
return [
|
|
461
565
|
tool(
|
|
462
566
|
async (args) => executeConnectorAction(args as ConnectorActionInput, bctx),
|
|
@@ -14,25 +14,41 @@ export interface ToolContext {
|
|
|
14
14
|
export interface SessionToolsResult {
|
|
15
15
|
tools: StructuredToolInterface[]
|
|
16
16
|
cleanup: () => Promise<void>
|
|
17
|
+
/** Maps tool name → plugin ID for attribution in usage tracking */
|
|
18
|
+
toolToPluginMap: Record<string, string>
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface ToolBuildContext {
|
|
20
22
|
cwd: string
|
|
21
23
|
ctx: ToolContext | undefined
|
|
24
|
+
hasPlugin: (name: string) => boolean
|
|
25
|
+
/** @deprecated Use hasPlugin */
|
|
22
26
|
hasTool: (name: string) => boolean
|
|
23
27
|
cleanupFns: (() => Promise<void>)[]
|
|
24
28
|
commandTimeoutMs: number
|
|
25
29
|
claudeTimeoutMs: number
|
|
26
30
|
cliProcessTimeoutMs: number
|
|
27
|
-
persistDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode', id: string | null | undefined) => void
|
|
28
|
-
readStoredDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode') => string | null
|
|
31
|
+
persistDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini', id: string | null | undefined) => void
|
|
32
|
+
readStoredDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini') => string | null
|
|
29
33
|
resolveCurrentSession: () => any | null
|
|
30
|
-
|
|
34
|
+
activePlugins: string[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeWorkspaceAlias(cwd: string, filePath: string): string {
|
|
38
|
+
const trimmed = filePath.trim()
|
|
39
|
+
if (!trimmed) return trimmed
|
|
40
|
+
if (trimmed === '/workspace' || trimmed === 'workspace') return cwd
|
|
41
|
+
if (trimmed.startsWith('/workspace/')) return trimmed.slice('/workspace/'.length)
|
|
42
|
+
if (trimmed.startsWith('workspace/')) return trimmed.slice('workspace/'.length)
|
|
43
|
+
return trimmed
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
export function safePath(cwd: string, filePath: string): string {
|
|
34
|
-
const
|
|
35
|
-
|
|
47
|
+
const path = require('path')
|
|
48
|
+
const normalized = normalizeWorkspaceAlias(cwd, filePath)
|
|
49
|
+
const resolvedRoot = path.resolve(cwd)
|
|
50
|
+
const resolved = path.resolve(resolvedRoot, normalized)
|
|
51
|
+
if (!resolved.startsWith(resolvedRoot)) {
|
|
36
52
|
throw new Error('Path traversal not allowed')
|
|
37
53
|
}
|
|
38
54
|
return resolved
|