@swarmclawai/swarmclaw 1.2.3 → 1.2.5
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 +20 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/models/route.test.ts +60 -0
- package/src/app/api/providers/[id]/models/route.ts +33 -1
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/[id]/route.ts +30 -1
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +15 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +51 -25
- package/src/components/agents/inspector-panel.tsx +15 -4
- package/src/components/auth/setup-wizard/index.tsx +27 -18
- package/src/components/auth/setup-wizard/shared.tsx +2 -2
- package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
- package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
- package/src/components/auth/setup-wizard/types.ts +6 -4
- package/src/components/auth/setup-wizard/utils.test.ts +38 -8
- package/src/components/auth/setup-wizard/utils.ts +14 -8
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +150 -77
- package/src/components/providers/provider-sheet.tsx +102 -77
- package/src/components/shared/model-combobox.tsx +5 -4
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/agent-provider-options.test.ts +152 -0
- package/src/lib/agent-provider-options.ts +84 -0
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +78 -0
- package/src/lib/providers/index.ts +13 -10
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +6 -6
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +42 -215
- package/src/lib/server/storage-normalization.ts +98 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +288 -22
- package/src/views/settings/section-providers.tsx +2 -2
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
loadAgents, loadCredentials, decryptKey, loadSettings, loadSkills,
|
|
7
7
|
loadChatrooms, saveChatrooms,
|
|
8
8
|
} from '../storage'
|
|
9
|
+
import { getMessages } from '@/lib/server/messages/message-repository'
|
|
9
10
|
import { dedup, errorMessage, hmrSingleton } from '@/lib/shared-utils'
|
|
10
11
|
import path from 'path'
|
|
11
12
|
import { streamAgentChat } from '@/lib/server/chat-execution/stream-agent-chat'
|
|
@@ -22,7 +23,10 @@ import {
|
|
|
22
23
|
resolveApiKey as resolveApiKeyHelper,
|
|
23
24
|
} from '@/lib/server/chatrooms/chatroom-helpers'
|
|
24
25
|
import { filterHealthyChatroomAgents } from '@/lib/server/chatrooms/chatroom-health'
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ensureChatroomRoutingGuidance,
|
|
28
|
+
selectChatroomRecipients,
|
|
29
|
+
} from '@/lib/server/chatrooms/chatroom-routing'
|
|
26
30
|
import { markProviderFailure, markProviderSuccess } from '../provider-health'
|
|
27
31
|
import { buildIdentityContinuityContext } from '../identity-continuity'
|
|
28
32
|
import { buildRuntimeSkillPromptBlocks, resolveRuntimeSkills } from '@/lib/server/skills/runtime-skill-resolver'
|
|
@@ -630,11 +634,14 @@ async function routeMessageToChatroom(connector: Connector, msg: InboundMessage)
|
|
|
630
634
|
const threadContextBlock = buildConnectorThreadContextBlock(msg)
|
|
631
635
|
|
|
632
636
|
// Parse mentions from the message text
|
|
637
|
+
ensureChatroomRoutingGuidance(chatroom, agents)
|
|
633
638
|
let mentions = parseMentions(msg.text || '', agents, chatroom.agentIds)
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
639
|
+
if (mentions.length === 0 && !chatroom.autoAddress) {
|
|
640
|
+
mentions = await selectChatroomRecipients({
|
|
641
|
+
text: msg.text || '',
|
|
642
|
+
chatroom,
|
|
643
|
+
agentsById: agents,
|
|
644
|
+
})
|
|
638
645
|
}
|
|
639
646
|
// Auto-address: if enabled and still no mentions, address all agents
|
|
640
647
|
if (chatroom.autoAddress && mentions.length === 0) {
|
|
@@ -1245,7 +1252,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1245
1252
|
}
|
|
1246
1253
|
}
|
|
1247
1254
|
},
|
|
1248
|
-
history: modelHistoryTailWithAttribution(session.
|
|
1255
|
+
history: modelHistoryTailWithAttribution(getMessages(session.id), 50, 48_000),
|
|
1249
1256
|
})
|
|
1250
1257
|
settledConnectorToolEvents = [
|
|
1251
1258
|
...pruneIncompleteToolEvents(streamedConnectorToolEvents),
|
|
@@ -1300,7 +1307,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1300
1307
|
}
|
|
1301
1308
|
},
|
|
1302
1309
|
active: new Map(),
|
|
1303
|
-
loadHistory: () => modelHistoryTailWithAttribution(session.
|
|
1310
|
+
loadHistory: () => modelHistoryTailWithAttribution(getMessages(session.id), 50, 48_000),
|
|
1304
1311
|
})
|
|
1305
1312
|
mediaExtractionText = fullText
|
|
1306
1313
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
loadConnectors,
|
|
4
4
|
loadSession, upsertSession,
|
|
5
5
|
} from '../storage'
|
|
6
|
+
import { getMessages, replaceMessageAt } from '@/lib/server/messages/message-repository'
|
|
6
7
|
import { errorMessage } from '@/lib/shared-utils'
|
|
7
8
|
import path from 'path'
|
|
8
9
|
import { notify } from '../ws-hub'
|
|
@@ -247,7 +248,7 @@ export async function sendConnectorMessage(params: {
|
|
|
247
248
|
lastOutboundAt: Date.now(),
|
|
248
249
|
lastOutboundMessageId: result?.messageId || session.connectorContext?.lastOutboundMessageId || null,
|
|
249
250
|
}
|
|
250
|
-
const history =
|
|
251
|
+
const history = getMessages(session.id)
|
|
251
252
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
252
253
|
const entry = history[i]
|
|
253
254
|
if (entry?.role !== 'assistant') continue
|
|
@@ -255,17 +256,21 @@ export async function sendConnectorMessage(params: {
|
|
|
255
256
|
if (source.connectorId !== connectorId) continue
|
|
256
257
|
if (source.channelId !== channelId) continue
|
|
257
258
|
if (!source.messageId && result?.messageId) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
259
|
+
const updatedEntry = {
|
|
260
|
+
...entry,
|
|
261
|
+
source: {
|
|
262
|
+
platform: source.platform || connector.platform,
|
|
263
|
+
connectorId: source.connectorId || connectorId,
|
|
264
|
+
connectorName: source.connectorName || connector.name,
|
|
265
|
+
channelId: source.channelId || channelId,
|
|
266
|
+
senderId: source.senderId,
|
|
267
|
+
senderName: source.senderName,
|
|
268
|
+
messageId: result.messageId,
|
|
269
|
+
threadId: source.threadId || params.threadId,
|
|
270
|
+
replyToMessageId: source.replyToMessageId || params.replyToMessageId,
|
|
271
|
+
},
|
|
268
272
|
}
|
|
273
|
+
replaceMessageAt(session.id, i, updatedEntry)
|
|
269
274
|
}
|
|
270
275
|
break
|
|
271
276
|
}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
3
|
+
import {
|
|
4
|
+
deleteConnector,
|
|
5
|
+
loadConnector,
|
|
6
|
+
loadConnectorHealth,
|
|
7
|
+
loadConnectors,
|
|
8
|
+
upsertConnector,
|
|
9
|
+
} from '@/lib/server/connectors/connector-repository'
|
|
10
|
+
import {
|
|
11
|
+
buildConnectorAccessSnapshot,
|
|
12
|
+
resolveConnectorOwnerSenderId,
|
|
13
|
+
} from '@/lib/server/connectors/access'
|
|
14
|
+
import {
|
|
15
|
+
addAllowedSender,
|
|
16
|
+
approvePairingCode,
|
|
17
|
+
approvePendingSender,
|
|
18
|
+
clearSenderAddressingOverride,
|
|
19
|
+
normalizeSenderId,
|
|
20
|
+
parseAllowFromCsv,
|
|
21
|
+
parseDmAddressingMode,
|
|
22
|
+
parsePairingPolicy,
|
|
23
|
+
removeAllowedSender,
|
|
24
|
+
rejectPendingSender,
|
|
25
|
+
setSenderAddressingOverride,
|
|
26
|
+
senderMatchesAnyEntry,
|
|
27
|
+
} from '@/lib/server/connectors/pairing'
|
|
28
|
+
import {
|
|
29
|
+
ensureDaemonProcessRunning,
|
|
30
|
+
getDaemonConnectorRuntime,
|
|
31
|
+
listDaemonConnectorRuntime,
|
|
32
|
+
runDaemonConnectorAction,
|
|
33
|
+
} from '@/lib/server/daemon/controller'
|
|
34
|
+
import { log } from '@/lib/server/logger'
|
|
35
|
+
import { serviceFail, serviceOk } from '@/lib/server/service-result'
|
|
36
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
37
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
38
|
+
import type {
|
|
39
|
+
Connector,
|
|
40
|
+
ConnectorAccessMutationAction,
|
|
41
|
+
ConnectorAccessMutationResponse,
|
|
42
|
+
ConnectorHealthEvent,
|
|
43
|
+
} from '@/types'
|
|
44
|
+
import type { ServiceResult } from '@/lib/server/service-result'
|
|
45
|
+
import type { DaemonConnectorRuntimeState } from '@/lib/server/daemon/types'
|
|
46
|
+
|
|
47
|
+
function cloneConnector<T extends Connector>(connector: T): T {
|
|
48
|
+
return {
|
|
49
|
+
...connector,
|
|
50
|
+
config: connector.config ? { ...connector.config } : {},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function persistConnector(connector: Connector): void {
|
|
55
|
+
connector.updatedAt = Date.now()
|
|
56
|
+
upsertConnector(connector.id, connector)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyRuntimeFields(connector: Connector, runtime: DaemonConnectorRuntimeState | null): Connector {
|
|
60
|
+
connector.status = runtime?.status
|
|
61
|
+
? runtime.status
|
|
62
|
+
: connector.lastError
|
|
63
|
+
? 'error'
|
|
64
|
+
: 'stopped'
|
|
65
|
+
|
|
66
|
+
if (connector.platform === 'whatsapp') {
|
|
67
|
+
connector.authenticated = runtime?.authenticated
|
|
68
|
+
connector.hasCredentials = runtime?.hasCredentials
|
|
69
|
+
if (runtime?.qrDataUrl) connector.qrDataUrl = runtime.qrDataUrl
|
|
70
|
+
else delete connector.qrDataUrl
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (runtime?.reconnectAttempts !== undefined) {
|
|
74
|
+
const ext = connector as unknown as Record<string, unknown>
|
|
75
|
+
ext.reconnectAttempts = runtime.reconnectAttempts
|
|
76
|
+
ext.nextRetryAt = runtime.nextRetryAt
|
|
77
|
+
ext.reconnectError = runtime.reconnectError
|
|
78
|
+
ext.reconnectExhausted = runtime.reconnectExhausted
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (runtime?.presence && connector.status === 'running') {
|
|
82
|
+
connector.presence = runtime.presence
|
|
83
|
+
} else {
|
|
84
|
+
delete connector.presence
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return connector
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function setConnectorSenderList(connector: Connector, key: string, values: string[]): void {
|
|
91
|
+
if (!connector.config) connector.config = {}
|
|
92
|
+
if (values.length === 0) {
|
|
93
|
+
delete connector.config[key]
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
connector.config[key] = values.join(',')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function addConnectorSenderListEntry(connector: Connector, key: string, senderId: string): boolean {
|
|
100
|
+
const normalized = normalizeSenderId(senderId)
|
|
101
|
+
if (!normalized) return false
|
|
102
|
+
const current = parseAllowFromCsv(connector.config?.[key])
|
|
103
|
+
if (senderMatchesAnyEntry(normalized, current)) return false
|
|
104
|
+
setConnectorSenderList(connector, key, [...current, normalized])
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function removeConnectorSenderListEntry(connector: Connector, key: string, senderId: string): boolean {
|
|
109
|
+
const normalized = normalizeSenderId(senderId)
|
|
110
|
+
if (!normalized) return false
|
|
111
|
+
const current = parseAllowFromCsv(connector.config?.[key])
|
|
112
|
+
const next = current.filter((entry) => !senderMatchesAnyEntry(normalized, [entry]))
|
|
113
|
+
if (next.length === current.length) return false
|
|
114
|
+
setConnectorSenderList(connector, key, next)
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function requireSenderId(body: Record<string, unknown>): string {
|
|
119
|
+
const senderId = typeof body.senderId === 'string' ? body.senderId.trim() : ''
|
|
120
|
+
if (!senderId) throw new Error('senderId is required for this action')
|
|
121
|
+
return senderId
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function listConnectorsWithRuntime(): Promise<Record<string, Connector>> {
|
|
125
|
+
await ensureDaemonProcessRunning('api/connectors:get')
|
|
126
|
+
const connectors = Object.fromEntries(
|
|
127
|
+
Object.entries(loadConnectors()).map(([id, connector]) => [id, cloneConnector(connector)]),
|
|
128
|
+
) as Record<string, Connector>
|
|
129
|
+
const runtimeByConnector = await listDaemonConnectorRuntime()
|
|
130
|
+
for (const connector of Object.values(connectors)) {
|
|
131
|
+
applyRuntimeFields(connector, runtimeByConnector[connector.id] || null)
|
|
132
|
+
}
|
|
133
|
+
return connectors
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function getConnectorWithRuntime(id: string): Promise<Connector | null> {
|
|
137
|
+
await ensureDaemonProcessRunning('api/connectors/[id]:get')
|
|
138
|
+
const connector = loadConnector(id)
|
|
139
|
+
if (!connector) return null
|
|
140
|
+
const current = cloneConnector(connector)
|
|
141
|
+
return applyRuntimeFields(current, await getDaemonConnectorRuntime(id))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function createConnector(body: Record<string, unknown>): Connector {
|
|
145
|
+
const id = genId()
|
|
146
|
+
const connector: Connector = {
|
|
147
|
+
id,
|
|
148
|
+
name: (body.name as string) || `${String(body.platform || '')} Connector`,
|
|
149
|
+
platform: body.platform as Connector['platform'],
|
|
150
|
+
agentId: (body.agentId as string | null | undefined) || null,
|
|
151
|
+
chatroomId: (body.chatroomId as string | null | undefined) || null,
|
|
152
|
+
credentialId: (body.credentialId as string | null | undefined) || null,
|
|
153
|
+
config: body.config && typeof body.config === 'object' && !Array.isArray(body.config)
|
|
154
|
+
? body.config as Record<string, string>
|
|
155
|
+
: {},
|
|
156
|
+
isEnabled: false,
|
|
157
|
+
status: 'stopped',
|
|
158
|
+
lastError: null,
|
|
159
|
+
createdAt: Date.now(),
|
|
160
|
+
updatedAt: Date.now(),
|
|
161
|
+
}
|
|
162
|
+
upsertConnector(id, connector)
|
|
163
|
+
notify('connectors')
|
|
164
|
+
return connector
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function autoStartConnectorIfNeeded(connector: Connector, body: Record<string, unknown>): Promise<void> {
|
|
168
|
+
const hasCredentials = connector.platform === 'whatsapp'
|
|
169
|
+
|| connector.platform === 'openclaw'
|
|
170
|
+
|| (connector.platform === 'bluebubbles' && (!!connector.credentialId || !!connector.config.password))
|
|
171
|
+
|| !!connector.credentialId
|
|
172
|
+
if (!hasCredentials || body.autoStart === false) return
|
|
173
|
+
try {
|
|
174
|
+
await runDaemonConnectorAction(connector.id, 'start', 'connectors:auto-start')
|
|
175
|
+
} catch (err: unknown) {
|
|
176
|
+
log.warn('connectors', `Auto-start failed for connector ${connector.id}`, errorMessage(err))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function updateConnectorFromRoute(id: string, body: Record<string, unknown>): Promise<ServiceResult<Connector>> {
|
|
181
|
+
await ensureDaemonProcessRunning('api/connectors/[id]:put')
|
|
182
|
+
const connector = loadConnector(id)
|
|
183
|
+
if (!connector) return serviceFail(404, 'Connector not found')
|
|
184
|
+
|
|
185
|
+
if (body.action === 'start' || body.action === 'stop' || body.action === 'repair') {
|
|
186
|
+
try {
|
|
187
|
+
if (body.action === 'start') {
|
|
188
|
+
await runDaemonConnectorAction(id, 'start', 'api/connectors/[id]:action:start')
|
|
189
|
+
logActivity({ entityType: 'connector', entityId: id, action: 'started', actor: 'user', summary: `Connector started: "${connector.name}"` })
|
|
190
|
+
} else if (body.action === 'stop') {
|
|
191
|
+
await runDaemonConnectorAction(id, 'stop', 'api/connectors/[id]:action:stop')
|
|
192
|
+
logActivity({ entityType: 'connector', entityId: id, action: 'stopped', actor: 'user', summary: `Connector stopped: "${connector.name}"` })
|
|
193
|
+
} else {
|
|
194
|
+
await runDaemonConnectorAction(id, 'repair', 'api/connectors/[id]:action:repair')
|
|
195
|
+
logActivity({ entityType: 'connector', entityId: id, action: 'started', actor: 'user', summary: `Connector repaired: "${connector.name}"` })
|
|
196
|
+
}
|
|
197
|
+
} catch (err: unknown) {
|
|
198
|
+
log.error('connectors', `Action failed for connector ${id}`, errorMessage(err))
|
|
199
|
+
return serviceFail(500, 'Connector action failed')
|
|
200
|
+
}
|
|
201
|
+
notify('connectors')
|
|
202
|
+
const updated = await getConnectorWithRuntime(id)
|
|
203
|
+
return serviceOk(updated || connector)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const next = cloneConnector(connector)
|
|
207
|
+
if (body.name !== undefined) next.name = typeof body.name === 'string' ? body.name : next.name
|
|
208
|
+
if (body.agentId !== undefined) next.agentId = typeof body.agentId === 'string' || body.agentId === null ? body.agentId : next.agentId
|
|
209
|
+
if (body.chatroomId !== undefined) next.chatroomId = typeof body.chatroomId === 'string' || body.chatroomId === null ? body.chatroomId : next.chatroomId
|
|
210
|
+
if (body.credentialId !== undefined) next.credentialId = typeof body.credentialId === 'string' || body.credentialId === null ? body.credentialId : next.credentialId
|
|
211
|
+
if (body.config !== undefined) next.config = body.config && typeof body.config === 'object' && !Array.isArray(body.config) ? body.config as Record<string, string> : next.config
|
|
212
|
+
if (body.isEnabled !== undefined) next.isEnabled = typeof body.isEnabled === 'boolean' ? body.isEnabled : next.isEnabled
|
|
213
|
+
persistConnector(next)
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const runtime = await getDaemonConnectorRuntime(id)
|
|
217
|
+
const wasRunning = runtime?.status === 'running'
|
|
218
|
+
const shouldStop = body.isEnabled === false
|
|
219
|
+
const shouldReload = wasRunning && (
|
|
220
|
+
body.name !== undefined
|
|
221
|
+
|| body.agentId !== undefined
|
|
222
|
+
|| body.chatroomId !== undefined
|
|
223
|
+
|| body.credentialId !== undefined
|
|
224
|
+
|| body.config !== undefined
|
|
225
|
+
|| body.isEnabled !== undefined
|
|
226
|
+
)
|
|
227
|
+
const shouldStart = body.isEnabled === true && !wasRunning
|
|
228
|
+
if (shouldStop) {
|
|
229
|
+
await runDaemonConnectorAction(id, 'stop', 'api/connectors/[id]:reload:stop')
|
|
230
|
+
} else if (shouldReload || shouldStart) {
|
|
231
|
+
await runDaemonConnectorAction(id, 'start', 'api/connectors/[id]:reload:start')
|
|
232
|
+
}
|
|
233
|
+
} catch (err: unknown) {
|
|
234
|
+
log.warn('connectors', `Failed to reload connector ${id} after update`, errorMessage(err))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
notify('connectors')
|
|
238
|
+
return serviceOk(await getConnectorWithRuntime(id) || next)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function deleteConnectorFromRoute(id: string): Promise<ServiceResult<{ ok: true }>> {
|
|
242
|
+
const connector = loadConnector(id)
|
|
243
|
+
if (!connector) return serviceFail(404, 'Connector not found')
|
|
244
|
+
try {
|
|
245
|
+
await runDaemonConnectorAction(id, 'stop', 'api/connectors/[id]:delete')
|
|
246
|
+
} catch (err: unknown) {
|
|
247
|
+
log.warn('connectors', `Failed to stop connector ${id} during delete`, errorMessage(err))
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const { clearConnectorPairingState } = await import('@/lib/server/connectors/pairing')
|
|
251
|
+
clearConnectorPairingState(id)
|
|
252
|
+
} catch (err: unknown) {
|
|
253
|
+
log.warn('connectors', `Failed to clear pairing state for ${id}`, errorMessage(err))
|
|
254
|
+
}
|
|
255
|
+
deleteConnector(id)
|
|
256
|
+
notify('connectors')
|
|
257
|
+
return serviceOk({ ok: true as const })
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function getConnectorHealthForApi(id: string): { events: ConnectorHealthEvent[]; uptimePercent: number } | null {
|
|
261
|
+
const connector = loadConnector(id)
|
|
262
|
+
if (!connector) return null
|
|
263
|
+
const allHealth = loadConnectorHealth()
|
|
264
|
+
const events: ConnectorHealthEvent[] = []
|
|
265
|
+
for (const raw of Object.values(allHealth)) {
|
|
266
|
+
const entry = raw as ConnectorHealthEvent
|
|
267
|
+
if (entry.connectorId !== id) continue
|
|
268
|
+
events.push(entry)
|
|
269
|
+
}
|
|
270
|
+
events.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
|
|
271
|
+
return { events, uptimePercent: computeUptime(events) }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function computeUptime(events: ConnectorHealthEvent[]): number {
|
|
275
|
+
if (events.length === 0) return 0
|
|
276
|
+
const firstTime = new Date(events[0].timestamp).getTime()
|
|
277
|
+
const now = Date.now()
|
|
278
|
+
const totalMs = now - firstTime
|
|
279
|
+
if (totalMs <= 0) return 100
|
|
280
|
+
let uptimeMs = 0
|
|
281
|
+
let lastUpAt: number | null = null
|
|
282
|
+
for (const event of events) {
|
|
283
|
+
const time = new Date(event.timestamp).getTime()
|
|
284
|
+
if (event.event === 'started' || event.event === 'reconnected') {
|
|
285
|
+
if (lastUpAt === null) lastUpAt = time
|
|
286
|
+
} else if (event.event === 'stopped' || event.event === 'error' || event.event === 'disconnected') {
|
|
287
|
+
if (lastUpAt !== null) {
|
|
288
|
+
uptimeMs += time - lastUpAt
|
|
289
|
+
lastUpAt = null
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (lastUpAt !== null) uptimeMs += now - lastUpAt
|
|
294
|
+
return Math.round((uptimeMs / totalMs) * 10000) / 100
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function updateConnectorAccess(
|
|
298
|
+
connectorId: string,
|
|
299
|
+
body: Record<string, unknown>,
|
|
300
|
+
): Promise<ServiceResult<ConnectorAccessMutationResponse>> {
|
|
301
|
+
await ensureDaemonProcessRunning('api/connectors/[id]/access:put')
|
|
302
|
+
const connector = loadConnector(connectorId)
|
|
303
|
+
if (!connector) return serviceFail(404, 'Connector not found')
|
|
304
|
+
const current = cloneConnector(connector)
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const action = typeof body.action === 'string' ? body.action.trim().toLowerCase() as ConnectorAccessMutationAction : null
|
|
308
|
+
if (!action) {
|
|
309
|
+
return serviceFail(400, 'Missing access action')
|
|
310
|
+
}
|
|
311
|
+
let connectorChanged = false
|
|
312
|
+
let responseSenderId = typeof body.senderId === 'string' ? body.senderId.trim() : ''
|
|
313
|
+
const responseSenderIdAlt = typeof body.senderIdAlt === 'string' ? body.senderIdAlt.trim() : ''
|
|
314
|
+
let summary = `Updated access controls for "${current.name}".`
|
|
315
|
+
|
|
316
|
+
switch (action) {
|
|
317
|
+
case 'set_policy': {
|
|
318
|
+
const rawPolicy = typeof body.dmPolicy === 'string' ? body.dmPolicy.trim() : ''
|
|
319
|
+
if (!rawPolicy) delete current.config.dmPolicy
|
|
320
|
+
else current.config.dmPolicy = parsePairingPolicy(rawPolicy, 'open')
|
|
321
|
+
connectorChanged = true
|
|
322
|
+
summary = `Updated DM policy for "${current.name}".`
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
case 'set_dm_addressing_mode': {
|
|
326
|
+
const rawMode = typeof body.dmAddressingMode === 'string' ? body.dmAddressingMode.trim() : ''
|
|
327
|
+
const nextMode = parseDmAddressingMode(rawMode || 'open', 'open')
|
|
328
|
+
if (nextMode === 'open') delete current.config.dmAddressingMode
|
|
329
|
+
else current.config.dmAddressingMode = nextMode
|
|
330
|
+
connectorChanged = true
|
|
331
|
+
summary = `Updated DM addressing mode for "${current.name}" to ${nextMode}.`
|
|
332
|
+
break
|
|
333
|
+
}
|
|
334
|
+
case 'allow_sender': {
|
|
335
|
+
const senderId = requireSenderId(body)
|
|
336
|
+
addAllowedSender(current.id, senderId)
|
|
337
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
|
|
338
|
+
summary = `Allowed sender ${normalizeSenderId(senderId)} on "${current.name}".`
|
|
339
|
+
break
|
|
340
|
+
}
|
|
341
|
+
case 'remove_allowed_sender': {
|
|
342
|
+
const senderId = requireSenderId(body)
|
|
343
|
+
removeAllowedSender(current.id, senderId)
|
|
344
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'allowFrom', senderId) || connectorChanged
|
|
345
|
+
summary = `Removed connector-managed access for ${normalizeSenderId(senderId)} on "${current.name}".`
|
|
346
|
+
break
|
|
347
|
+
}
|
|
348
|
+
case 'block_sender': {
|
|
349
|
+
const senderId = requireSenderId(body)
|
|
350
|
+
connectorChanged = addConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
|
|
351
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'allowFrom', senderId) || connectorChanged
|
|
352
|
+
removeAllowedSender(current.id, senderId)
|
|
353
|
+
rejectPendingSender(current.id, senderId)
|
|
354
|
+
const ownerSenderId = resolveConnectorOwnerSenderId(current)
|
|
355
|
+
if (ownerSenderId && senderMatchesAnyEntry(senderId, [ownerSenderId])) {
|
|
356
|
+
delete current.config.ownerSenderId
|
|
357
|
+
connectorChanged = true
|
|
358
|
+
}
|
|
359
|
+
summary = `Blocked sender ${normalizeSenderId(senderId)} on "${current.name}".`
|
|
360
|
+
break
|
|
361
|
+
}
|
|
362
|
+
case 'unblock_sender': {
|
|
363
|
+
const senderId = requireSenderId(body)
|
|
364
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
|
|
365
|
+
summary = `Removed sender ${normalizeSenderId(senderId)} from the deny list on "${current.name}".`
|
|
366
|
+
break
|
|
367
|
+
}
|
|
368
|
+
case 'approve_pairing': {
|
|
369
|
+
if (typeof body.code === 'string' && body.code.trim()) {
|
|
370
|
+
const approved = approvePairingCode(current.id, body.code)
|
|
371
|
+
if (!approved.ok) {
|
|
372
|
+
return serviceFail(400, approved.reason || 'Pairing approval failed.')
|
|
373
|
+
}
|
|
374
|
+
if (approved.senderId) {
|
|
375
|
+
responseSenderId = approved.senderId
|
|
376
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', approved.senderId) || connectorChanged
|
|
377
|
+
}
|
|
378
|
+
summary = `Approved pairing on "${current.name}".`
|
|
379
|
+
} else {
|
|
380
|
+
const senderId = requireSenderId(body)
|
|
381
|
+
const approved = approvePendingSender(current.id, senderId)
|
|
382
|
+
if (!approved.ok) {
|
|
383
|
+
return serviceFail(400, approved.reason || 'Pairing approval failed.')
|
|
384
|
+
}
|
|
385
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
|
|
386
|
+
summary = `Approved pairing for ${normalizeSenderId(senderId)} on "${current.name}".`
|
|
387
|
+
}
|
|
388
|
+
break
|
|
389
|
+
}
|
|
390
|
+
case 'reject_pairing': {
|
|
391
|
+
const senderId = requireSenderId(body)
|
|
392
|
+
rejectPendingSender(current.id, senderId)
|
|
393
|
+
summary = `Rejected pairing for ${normalizeSenderId(senderId)} on "${current.name}".`
|
|
394
|
+
break
|
|
395
|
+
}
|
|
396
|
+
case 'set_owner': {
|
|
397
|
+
const senderId = requireSenderId(body)
|
|
398
|
+
const normalized = normalizeSenderId(senderId)
|
|
399
|
+
if (!normalized) return serviceFail(400, 'Could not normalize owner sender ID')
|
|
400
|
+
current.config.ownerSenderId = normalized
|
|
401
|
+
connectorChanged = true
|
|
402
|
+
connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', normalized) || connectorChanged
|
|
403
|
+
summary = `Set connector owner for "${current.name}" to ${normalized}.`
|
|
404
|
+
break
|
|
405
|
+
}
|
|
406
|
+
case 'clear_owner': {
|
|
407
|
+
if (current.config?.ownerSenderId) {
|
|
408
|
+
delete current.config.ownerSenderId
|
|
409
|
+
connectorChanged = true
|
|
410
|
+
}
|
|
411
|
+
summary = `Cleared connector owner override for "${current.name}".`
|
|
412
|
+
break
|
|
413
|
+
}
|
|
414
|
+
case 'set_sender_dm_addressing': {
|
|
415
|
+
const senderId = requireSenderId(body)
|
|
416
|
+
const rawMode = typeof body.dmAddressingMode === 'string' ? body.dmAddressingMode.trim() : ''
|
|
417
|
+
const nextMode = parseDmAddressingMode(rawMode || 'open', 'open')
|
|
418
|
+
setSenderAddressingOverride(current.id, senderId, nextMode)
|
|
419
|
+
summary = `Updated DM addressing override for ${normalizeSenderId(senderId)} on "${current.name}" to ${nextMode}.`
|
|
420
|
+
break
|
|
421
|
+
}
|
|
422
|
+
case 'clear_sender_dm_addressing': {
|
|
423
|
+
const senderId = requireSenderId(body)
|
|
424
|
+
clearSenderAddressingOverride(current.id, senderId)
|
|
425
|
+
summary = `Cleared DM addressing override for ${normalizeSenderId(senderId)} on "${current.name}".`
|
|
426
|
+
break
|
|
427
|
+
}
|
|
428
|
+
default:
|
|
429
|
+
return serviceFail(400, `Unsupported access action: ${action}`)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (connectorChanged) persistConnector(current)
|
|
433
|
+
logActivity({
|
|
434
|
+
entityType: 'connector',
|
|
435
|
+
entityId: current.id,
|
|
436
|
+
action: 'access-updated',
|
|
437
|
+
actor: 'user',
|
|
438
|
+
summary,
|
|
439
|
+
detail: { action },
|
|
440
|
+
})
|
|
441
|
+
notify('connectors')
|
|
442
|
+
return serviceOk({
|
|
443
|
+
ok: true,
|
|
444
|
+
snapshot: buildConnectorAccessSnapshot({
|
|
445
|
+
connector: current,
|
|
446
|
+
senderId: responseSenderId || null,
|
|
447
|
+
senderIdAlt: responseSenderIdAlt || null,
|
|
448
|
+
}),
|
|
449
|
+
})
|
|
450
|
+
} catch (err: unknown) {
|
|
451
|
+
return serviceFail(400, errorMessage(err))
|
|
452
|
+
}
|
|
453
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Connector, MessageSource } from '@/types'
|
|
2
|
-
import { loadConnectors } from '
|
|
2
|
+
import { loadConnectors } from './connector-repository'
|
|
3
|
+
import { getMessages, replaceMessageAt } from '@/lib/server/messages/message-repository'
|
|
3
4
|
import { notify } from '../ws-hub'
|
|
4
5
|
import { resolveConnectorSessionPolicy, shouldReplyToInboundMessage } from './policy'
|
|
5
6
|
import { runningConnectors } from './runtime-state'
|
|
@@ -68,7 +69,7 @@ export async function recordConnectorOutboundDelivery(params: {
|
|
|
68
69
|
lastOutboundMessageId: params.messageId || session.connectorContext?.lastOutboundMessageId || null,
|
|
69
70
|
threadId: params.inbound.threadId || session.connectorContext?.threadId || null,
|
|
70
71
|
}
|
|
71
|
-
const history =
|
|
72
|
+
const history = getMessages(session.id)
|
|
72
73
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
73
74
|
const entry = history[i]
|
|
74
75
|
if (entry?.role !== 'assistant') continue
|
|
@@ -76,17 +77,21 @@ export async function recordConnectorOutboundDelivery(params: {
|
|
|
76
77
|
if (source.connectorId !== connector.id) continue
|
|
77
78
|
if (source.channelId !== params.inbound.channelId) continue
|
|
78
79
|
if (!source.messageId && params.messageId) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
const updatedEntry = {
|
|
81
|
+
...entry,
|
|
82
|
+
source: {
|
|
83
|
+
platform: source.platform || connector.platform,
|
|
84
|
+
connectorId: source.connectorId || connector.id,
|
|
85
|
+
connectorName: source.connectorName || connector.name,
|
|
86
|
+
channelId: source.channelId || params.inbound.channelId,
|
|
87
|
+
senderId: source.senderId,
|
|
88
|
+
senderName: source.senderName,
|
|
89
|
+
messageId: params.messageId,
|
|
90
|
+
replyToMessageId: source.replyToMessageId || params.inbound.messageId,
|
|
91
|
+
threadId: source.threadId || params.inbound.threadId,
|
|
92
|
+
},
|
|
89
93
|
}
|
|
94
|
+
replaceMessageAt(session.id, i, updatedEntry)
|
|
90
95
|
}
|
|
91
96
|
break
|
|
92
97
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { log } from '@/lib/server/logger'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
-
import {
|
|
4
|
+
import { listCredentialIdsByProvider, resolveCredentialSecret } from '@/lib/server/credentials/credential-service'
|
|
5
|
+
import { loadSettings } from '../settings/settings-repository'
|
|
5
6
|
import { mimeFromPath } from './media'
|
|
6
7
|
import type { InboundMessage, InboundMedia } from './types'
|
|
7
8
|
import { errorMessage } from '@/lib/shared-utils'
|
|
@@ -90,25 +91,15 @@ function resolveOpenAiApiKey(preferredCredentialId?: string | null): string | nu
|
|
|
90
91
|
const envKey = String(process.env.SWARMCLAW_OPENAI_STT_API_KEY || process.env.OPENAI_API_KEY || '').trim()
|
|
91
92
|
if (envKey) return envKey
|
|
92
93
|
|
|
93
|
-
const creds = loadCredentials() as Record<string, { provider?: string; encryptedKey?: string }>
|
|
94
94
|
const candidates: string[] = []
|
|
95
95
|
if (preferredCredentialId) candidates.push(preferredCredentialId)
|
|
96
|
-
|
|
97
|
-
const provider = String(cred?.provider || '').trim().toLowerCase()
|
|
98
|
-
if (provider === 'openai') candidates.push(id)
|
|
99
|
-
}
|
|
96
|
+
candidates.push(...listCredentialIdsByProvider('openai'))
|
|
100
97
|
const seen = new Set<string>()
|
|
101
98
|
for (const id of candidates) {
|
|
102
99
|
if (!id || seen.has(id)) continue
|
|
103
100
|
seen.add(id)
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (provider !== 'openai') continue
|
|
107
|
-
if (!cred?.encryptedKey) continue
|
|
108
|
-
try {
|
|
109
|
-
const decrypted = decryptKey(cred.encryptedKey).trim()
|
|
110
|
-
if (decrypted) return decrypted
|
|
111
|
-
} catch { /* ignore invalid credential */ }
|
|
101
|
+
const decrypted = resolveCredentialSecret(id)?.trim()
|
|
102
|
+
if (decrypted) return decrypted
|
|
112
103
|
}
|
|
113
104
|
|
|
114
105
|
return null
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { genId } from '@/lib/id'
|
|
4
|
-
import { UPLOAD_DIR } from '../
|
|
4
|
+
import { UPLOAD_DIR } from '../upload-path'
|
|
5
5
|
import type { InboundMedia, InboundMediaType } from './types'
|
|
6
6
|
|
|
7
7
|
const MIME_EXT_MAP: Record<string, string> = {
|