@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- 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/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- 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/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- 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 +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- 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 +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- 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 +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- 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/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -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 +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- 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 +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- 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 +994 -130
- 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 +189 -10
- 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/daemon-state.ts +62 -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/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- 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 +31 -964
- 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 +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- 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 +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- 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 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- 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 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- 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/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -4,7 +4,9 @@ import { active, loadSessions } from './storage'
|
|
|
4
4
|
import { executeSessionChatTurn, type ExecuteChatTurnResult } from './chat-execution'
|
|
5
5
|
import { loadRuntimeSettings } from './runtime-settings'
|
|
6
6
|
import { log } from './logger'
|
|
7
|
-
import {
|
|
7
|
+
import { isInternalHeartbeatRun } from './heartbeat-source'
|
|
8
|
+
import { cleanupSessionBrowser } from './session-tools/web'
|
|
9
|
+
import { cancelDelegationJobsForParentSession } from './delegation-jobs'
|
|
8
10
|
|
|
9
11
|
export type SessionRunStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
10
12
|
export type SessionQueueMode = 'followup' | 'steer' | 'collect'
|
|
@@ -122,6 +124,23 @@ function emitRunMeta(entry: QueueEntry, status: SessionRunStatus, extra?: Record
|
|
|
122
124
|
})
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
function markRunningEntryCancelled(entry: QueueEntry, reason: string) {
|
|
128
|
+
if (entry.run.status === 'cancelled') return
|
|
129
|
+
entry.run.status = 'cancelled'
|
|
130
|
+
entry.run.endedAt = now()
|
|
131
|
+
entry.run.error = reason
|
|
132
|
+
emitRunMeta(entry, 'cancelled', { reason })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function abortSessionRuntime(entry: QueueEntry, reason: string) {
|
|
136
|
+
markRunningEntryCancelled(entry, reason)
|
|
137
|
+
entry.signalController.abort()
|
|
138
|
+
try { active.get(entry.run.sessionId)?.kill?.() } catch { /* noop */ }
|
|
139
|
+
active.delete(entry.run.sessionId)
|
|
140
|
+
try { cleanupSessionBrowser(entry.run.sessionId) } catch { /* noop */ }
|
|
141
|
+
try { cancelDelegationJobsForParentSession(entry.run.sessionId, reason) } catch { /* noop */ }
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
function executionKeyForSession(sessionId: string): string {
|
|
126
145
|
return `session:${sessionId}`
|
|
127
146
|
}
|
|
@@ -170,7 +189,7 @@ export function cancelAllHeartbeatRuns(reason = 'Heartbeat disabled globally'):
|
|
|
170
189
|
if (!queue.length) continue
|
|
171
190
|
const keep: QueueEntry[] = []
|
|
172
191
|
for (const entry of queue) {
|
|
173
|
-
const isHeartbeat = entry.run.internal
|
|
192
|
+
const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
|
|
174
193
|
if (!isHeartbeat) {
|
|
175
194
|
keep.push(entry)
|
|
176
195
|
continue
|
|
@@ -187,45 +206,15 @@ export function cancelAllHeartbeatRuns(reason = 'Heartbeat disabled globally'):
|
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
for (const entry of state.runningByExecution.values()) {
|
|
190
|
-
const isHeartbeat = entry.run.internal
|
|
209
|
+
const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
|
|
191
210
|
if (!isHeartbeat) continue
|
|
192
211
|
abortedRunning += 1
|
|
193
|
-
entry
|
|
194
|
-
try { active.get(entry.run.sessionId)?.kill?.() } catch { /* noop */ }
|
|
212
|
+
abortSessionRuntime(entry, reason)
|
|
195
213
|
}
|
|
196
214
|
|
|
197
215
|
return { cancelledQueued, abortedRunning }
|
|
198
216
|
}
|
|
199
217
|
|
|
200
|
-
function scheduleMainLoopFollowup(sessionId: string, followup: MainLoopFollowupRequest) {
|
|
201
|
-
const delayMs = Math.max(0, Math.trunc(followup.delayMs || 0))
|
|
202
|
-
setTimeout(() => {
|
|
203
|
-
try {
|
|
204
|
-
const sessions = loadSessions()
|
|
205
|
-
const session = sessions[sessionId]
|
|
206
|
-
if (!session || !isMainMissionSession(session)) return
|
|
207
|
-
enqueueSessionRun({
|
|
208
|
-
sessionId,
|
|
209
|
-
message: followup.message,
|
|
210
|
-
internal: true,
|
|
211
|
-
source: 'main-loop-followup',
|
|
212
|
-
mode: 'collect',
|
|
213
|
-
dedupeKey: followup.dedupeKey,
|
|
214
|
-
})
|
|
215
|
-
} catch (err: any) {
|
|
216
|
-
log.warn('session-run', `Failed to enqueue main-loop followup for ${sessionId}`, err?.message || String(err))
|
|
217
|
-
}
|
|
218
|
-
}, delayMs)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function isMainMissionSession(session: Record<string, unknown>): boolean {
|
|
222
|
-
const id = typeof session.id === 'string' ? session.id.trim() : ''
|
|
223
|
-
const sessionType = typeof session.sessionType === 'string' ? session.sessionType : ''
|
|
224
|
-
if (id.startsWith('agent-thread-')) return true
|
|
225
|
-
if (sessionType === 'orchestrated') return true
|
|
226
|
-
return false
|
|
227
|
-
}
|
|
228
|
-
|
|
229
218
|
async function drainExecution(executionKey: string): Promise<void> {
|
|
230
219
|
if (state.runningByExecution.has(executionKey)) return
|
|
231
220
|
const q = queueForExecution(executionKey)
|
|
@@ -269,49 +258,25 @@ async function drainExecution(executionKey: string): Promise<void> {
|
|
|
269
258
|
})
|
|
270
259
|
|
|
271
260
|
const failed = !!result.error
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
message: next.message,
|
|
277
|
-
internal: next.run.internal,
|
|
278
|
-
source: next.run.source,
|
|
279
|
-
resultText: result.text,
|
|
280
|
-
error: result.error,
|
|
281
|
-
toolEvents: result.toolEvents,
|
|
282
|
-
inputTokens: result.inputTokens,
|
|
283
|
-
outputTokens: result.outputTokens,
|
|
284
|
-
estimatedCost: result.estimatedCost,
|
|
285
|
-
})
|
|
286
|
-
} catch (mainLoopErr: any) {
|
|
287
|
-
log.warn('session-run', `Main-loop update failed for ${next.run.id}`, mainLoopErr?.message || String(mainLoopErr))
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
next.run.status = failed ? 'failed' : 'completed'
|
|
291
|
-
next.run.endedAt = now()
|
|
292
|
-
next.run.error = result.error
|
|
261
|
+
const aborted = next.signalController.signal.aborted
|
|
262
|
+
next.run.status = aborted ? 'cancelled' : (failed ? 'failed' : 'completed')
|
|
263
|
+
next.run.endedAt = next.run.endedAt || now()
|
|
264
|
+
next.run.error = aborted ? (next.run.error || 'Cancelled') : result.error
|
|
293
265
|
next.run.resultPreview = result.text?.slice(0, 280)
|
|
294
266
|
emitRunMeta(next, next.run.status, {
|
|
295
267
|
persisted: result.persisted,
|
|
296
268
|
hasText: !!result.text,
|
|
297
|
-
error:
|
|
269
|
+
error: next.run.error || null,
|
|
298
270
|
})
|
|
299
271
|
log.info('session-run', `Run finished ${next.run.id}`, {
|
|
300
272
|
sessionId: next.run.sessionId,
|
|
301
273
|
status: next.run.status,
|
|
302
274
|
persisted: result.persisted,
|
|
303
275
|
hasText: !!result.text,
|
|
304
|
-
error:
|
|
276
|
+
error: next.run.error || null,
|
|
305
277
|
durationMs: (next.run.endedAt || now()) - (next.run.startedAt || now()),
|
|
306
278
|
})
|
|
307
279
|
next.resolve(result)
|
|
308
|
-
if (!failed && followup) {
|
|
309
|
-
scheduleMainLoopFollowup(next.run.sessionId, followup)
|
|
310
|
-
log.info('session-run', `Queued main-loop followup after ${next.run.id}`, {
|
|
311
|
-
sessionId: next.run.sessionId,
|
|
312
|
-
delayMs: followup.delayMs,
|
|
313
|
-
})
|
|
314
|
-
}
|
|
315
280
|
} catch (err: any) {
|
|
316
281
|
const aborted = next.signalController.signal.aborted
|
|
317
282
|
next.run.status = aborted ? 'cancelled' : 'failed'
|
|
@@ -324,19 +289,6 @@ async function drainExecution(executionKey: string): Promise<void> {
|
|
|
324
289
|
error: next.run.error,
|
|
325
290
|
durationMs: (next.run.endedAt || now()) - (next.run.startedAt || now()),
|
|
326
291
|
})
|
|
327
|
-
try {
|
|
328
|
-
handleMainLoopRunResult({
|
|
329
|
-
sessionId: next.run.sessionId,
|
|
330
|
-
message: next.message,
|
|
331
|
-
internal: next.run.internal,
|
|
332
|
-
source: next.run.source,
|
|
333
|
-
resultText: '',
|
|
334
|
-
error: next.run.error,
|
|
335
|
-
toolEvents: [],
|
|
336
|
-
})
|
|
337
|
-
} catch {
|
|
338
|
-
// Main-loop bookkeeping failures should not affect queue execution.
|
|
339
|
-
}
|
|
340
292
|
next.reject(err instanceof Error ? err : new Error(next.run.error))
|
|
341
293
|
} finally {
|
|
342
294
|
if (runtimeTimer) clearTimeout(runtimeTimer)
|
|
@@ -528,7 +480,9 @@ export function getSessionRunState(sessionId: string): {
|
|
|
528
480
|
const running = state.runningByExecution.get(executionKey)
|
|
529
481
|
const queued = queueForExecution(executionKey).filter((entry) => entry.run.sessionId === sessionId).length
|
|
530
482
|
return {
|
|
531
|
-
runningRunId: running?.run.sessionId === sessionId
|
|
483
|
+
runningRunId: (running?.run.sessionId === sessionId && running.run.status === 'running')
|
|
484
|
+
? running.run.id
|
|
485
|
+
: undefined,
|
|
532
486
|
queueLength: queued,
|
|
533
487
|
}
|
|
534
488
|
}
|
|
@@ -562,8 +516,7 @@ export function cancelSessionRuns(sessionId: string, reason = 'Cancelled'): { ca
|
|
|
562
516
|
let cancelledRunning = false
|
|
563
517
|
if (running && running.run.sessionId === sessionId) {
|
|
564
518
|
cancelledRunning = true
|
|
565
|
-
running
|
|
566
|
-
try { active.get(sessionId)?.kill?.() } catch { /* noop */ }
|
|
519
|
+
abortSessionRuntime(running, reason)
|
|
567
520
|
}
|
|
568
521
|
const cancelledQueued = cancelPendingForSession(sessionId, reason)
|
|
569
522
|
return { cancelledQueued, cancelledRunning }
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { describe, it } from 'node:test'
|
|
5
|
+
|
|
6
|
+
const thisFile = new URL(import.meta.url).pathname
|
|
7
|
+
const toolsDir = path.dirname(thisFile)
|
|
8
|
+
const serverDir = path.resolve(toolsDir, '..')
|
|
9
|
+
|
|
10
|
+
function readToolSource(fileName: string): string {
|
|
11
|
+
return fs.readFileSync(path.join(toolsDir, fileName), 'utf-8')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readServerSource(fileName: string): string {
|
|
15
|
+
return fs.readFileSync(path.join(serverDir, fileName), 'utf-8')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('browser workflow surface', () => {
|
|
19
|
+
it('advertises the higher-level browser actions in web.ts', () => {
|
|
20
|
+
const src = readToolSource('web.ts')
|
|
21
|
+
for (const action of ['read_page', 'extract_links', 'extract_form_fields', 'extract_table', 'fill_form', 'submit_form', 'scroll_until', 'download_file', 'complete_web_task']) {
|
|
22
|
+
assert.equal(src.includes(`'${action}'`), true, `web.ts should expose ${action}`)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('supports the shorthand form-map path for fill_form', () => {
|
|
27
|
+
const src = readToolSource('web.ts')
|
|
28
|
+
assert.equal(src.includes('params.form'), true)
|
|
29
|
+
assert.equal(src.includes('fields is required for fill_form.'), true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('flags pages that require human-provided input', () => {
|
|
33
|
+
const src = readToolSource('web.ts')
|
|
34
|
+
assert.equal(src.includes("type: 'human_input_required'"), true)
|
|
35
|
+
assert.equal(src.includes('Ask the human instead of guessing'), true)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('durable wait surface', () => {
|
|
40
|
+
it('advertises the durable wait actions in monitor.ts', () => {
|
|
41
|
+
const src = readToolSource('monitor.ts')
|
|
42
|
+
for (const action of ['wait_until', 'wait_for_http', 'wait_for_file', 'wait_for_task', 'wait_for_webhook', 'wait_for_page_change']) {
|
|
43
|
+
assert.equal(src.includes(`'${action}'`), true, `monitor.ts should expose ${action}`)
|
|
44
|
+
}
|
|
45
|
+
assert.equal(src.includes('createDurableWatch'), true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('routes schedule_wake through durable watch storage', () => {
|
|
49
|
+
const src = readToolSource('schedule.ts')
|
|
50
|
+
assert.equal(src.includes('createWatchJob'), true)
|
|
51
|
+
assert.equal(src.includes("type: 'time'"), true)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('sandbox surface', () => {
|
|
56
|
+
it('advertises a Deno-only sandbox and steers simple APIs to http_request', () => {
|
|
57
|
+
const src = readToolSource('sandbox.ts')
|
|
58
|
+
assert.equal(src.includes("enum: ['javascript', 'typescript']"), true)
|
|
59
|
+
assert.equal(src.includes('http_request'), true)
|
|
60
|
+
assert.equal(src.includes('plugin_creator'), true)
|
|
61
|
+
assert.equal(src.includes('manage_schedules'), true)
|
|
62
|
+
assert.equal(src.includes('openclaw_sandbox'), false)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('delegation job handles', () => {
|
|
67
|
+
it('exposes subagent control actions', () => {
|
|
68
|
+
const src = readToolSource('subagent.ts')
|
|
69
|
+
for (const action of ['status', 'list', 'wait', 'cancel']) {
|
|
70
|
+
assert.equal(src.includes(`action === '${action}'`), true, `subagent.ts should handle ${action}`)
|
|
71
|
+
}
|
|
72
|
+
assert.equal(src.includes('createDelegationJob'), true)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('builds delegate context from the invoking session and uses job records', () => {
|
|
76
|
+
const src = readToolSource('delegate.ts')
|
|
77
|
+
assert.equal(src.includes('buildDelegateContextFromSessionish'), true)
|
|
78
|
+
assert.equal(src.includes('createDelegationJob'), true)
|
|
79
|
+
assert.equal(src.includes('waitForDelegateJob'), true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('scheduler and daemon recover the durable autonomy jobs', () => {
|
|
83
|
+
const schedulerSrc = readServerSource('scheduler.ts')
|
|
84
|
+
const daemonSrc = readServerSource('daemon-state.ts')
|
|
85
|
+
assert.equal(schedulerSrc.includes('processDueWatchJobs'), true)
|
|
86
|
+
assert.equal(daemonSrc.includes('recoverStaleDelegationJobs'), true)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('primitive plugin surfaces', () => {
|
|
91
|
+
it('advertises mailbox and human-loop actions', () => {
|
|
92
|
+
const mailboxSrc = readToolSource('mailbox.ts')
|
|
93
|
+
const humanSrc = readToolSource('human-loop.ts')
|
|
94
|
+
for (const action of ['list_messages', 'list_threads', 'search_messages', 'read_message', 'download_attachment', 'reply', 'wait_for_email']) {
|
|
95
|
+
assert.equal(mailboxSrc.includes(`'${action}'`), true, `mailbox.ts should expose ${action}`)
|
|
96
|
+
}
|
|
97
|
+
for (const action of ['request_input', 'request_approval', 'wait_for_reply', 'wait_for_approval', 'list_mailbox', 'ack_mailbox', 'status']) {
|
|
98
|
+
assert.equal(humanSrc.includes(`'${action}'`), true, `human-loop.ts should expose ${action}`)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('advertises document, extract, table, and crawl actions', () => {
|
|
103
|
+
const documentSrc = readToolSource('document.ts')
|
|
104
|
+
const extractSrc = readToolSource('extract.ts')
|
|
105
|
+
const tableSrc = readToolSource('table.ts')
|
|
106
|
+
const crawlSrc = readToolSource('crawl.ts')
|
|
107
|
+
|
|
108
|
+
for (const action of ['read', 'metadata', 'ocr', 'extract_tables', 'store', 'list', 'search', 'get', 'delete']) {
|
|
109
|
+
assert.equal(documentSrc.includes(`'${action}'`), true, `document.ts should expose ${action}`)
|
|
110
|
+
}
|
|
111
|
+
for (const action of ['extract_structured', 'summarize', 'status']) {
|
|
112
|
+
assert.equal(extractSrc.includes(`'${action}'`), true, `extract.ts should expose ${action}`)
|
|
113
|
+
}
|
|
114
|
+
for (const action of ['read', 'load_csv', 'load_xlsx', 'summarize', 'filter', 'sort', 'group', 'pivot', 'dedupe', 'join', 'write']) {
|
|
115
|
+
assert.equal(tableSrc.includes(`'${action}'`), true, `table.ts should expose ${action}`)
|
|
116
|
+
}
|
|
117
|
+
for (const action of ['crawl_site', 'follow_pagination', 'extract_sitemap', 'dedupe_pages', 'batch_extract']) {
|
|
118
|
+
assert.equal(crawlSrc.includes(`'${action}'`), true, `crawl.ts should expose ${action}`)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('registers the primitive plugins in builtin-plugins.ts', () => {
|
|
123
|
+
const src = readServerSource('builtin-plugins.ts')
|
|
124
|
+
for (const moduleName of ['mailbox', 'human-loop', 'document', 'extract', 'table', 'crawl']) {
|
|
125
|
+
assert.equal(src.includes(`session-tools/${moduleName}`), true, `builtin-plugins.ts should import ${moduleName}`)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -3,7 +3,6 @@ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
|
3
3
|
import type { Plugin, PluginHooks } from '@/types'
|
|
4
4
|
import { getPluginManager } from '../plugins'
|
|
5
5
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
6
|
-
import { loadSettings } from '../storage'
|
|
7
6
|
import type { ToolBuildContext } from './context'
|
|
8
7
|
|
|
9
8
|
type CalendarProvider = 'google' | 'outlook'
|
|
@@ -18,8 +17,7 @@ interface CalendarConfig {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
function getConfig(): CalendarConfig {
|
|
21
|
-
const
|
|
22
|
-
const ps = (settings.pluginSettings as Record<string, Record<string, unknown>> | undefined)?.calendar ?? {}
|
|
20
|
+
const ps = getPluginManager().getPluginSettings('calendar')
|
|
23
21
|
return {
|
|
24
22
|
provider: (ps.provider as CalendarProvider) || 'google',
|
|
25
23
|
accessToken: (ps.accessToken as string) || '',
|
|
@@ -49,15 +47,7 @@ async function refreshGoogleToken(cfg: CalendarConfig): Promise<string | null> {
|
|
|
49
47
|
const data = await res.json()
|
|
50
48
|
const newToken = data?.access_token as string | undefined
|
|
51
49
|
if (newToken) {
|
|
52
|
-
|
|
53
|
-
const settings = loadSettings()
|
|
54
|
-
const pluginSettings = (settings.pluginSettings as Record<string, Record<string, unknown>> | undefined) ?? {}
|
|
55
|
-
const calSettings = pluginSettings.calendar ?? {}
|
|
56
|
-
calSettings.accessToken = newToken
|
|
57
|
-
pluginSettings.calendar = calSettings
|
|
58
|
-
settings.pluginSettings = pluginSettings
|
|
59
|
-
const { saveSettings } = await import('../storage')
|
|
60
|
-
saveSettings(settings)
|
|
50
|
+
getPluginManager().setPluginSettings('calendar', { ...cfg, accessToken: newToken })
|
|
61
51
|
}
|
|
62
52
|
return newToken || null
|
|
63
53
|
} catch {
|
|
@@ -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)}`
|
|
@@ -440,11 +532,20 @@ const ConnectorPlugin: Plugin = {
|
|
|
440
532
|
parameters: {
|
|
441
533
|
type: 'object',
|
|
442
534
|
properties: {
|
|
443
|
-
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'] },
|
|
444
536
|
connectorId: { type: 'string' },
|
|
445
537
|
platform: { type: 'string' },
|
|
446
538
|
to: { type: 'string' },
|
|
447
|
-
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' },
|
|
448
549
|
},
|
|
449
550
|
required: ['action']
|
|
450
551
|
},
|
|
@@ -34,9 +34,21 @@ export interface ToolBuildContext {
|
|
|
34
34
|
activePlugins: string[]
|
|
35
35
|
}
|
|
36
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
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
export function safePath(cwd: string, filePath: string): string {
|
|
38
|
-
const
|
|
39
|
-
|
|
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)) {
|
|
40
52
|
throw new Error('Path traversal not allowed')
|
|
41
53
|
}
|
|
42
54
|
return resolved
|