@swarmclawai/swarmclaw 0.6.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -45
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +29 -6
- package/src/components/home/home-view.tsx +20 -14
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +73 -21
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +19 -7
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +170 -66
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +66 -64
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +11 -1
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +66 -31
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -14,19 +14,28 @@ import {
|
|
|
14
14
|
active,
|
|
15
15
|
} from './storage'
|
|
16
16
|
import { getProvider } from '@/lib/providers'
|
|
17
|
-
import { estimateCost,
|
|
17
|
+
import { estimateCost, checkAgentBudgetLimits } from './cost'
|
|
18
18
|
import { log } from './logger'
|
|
19
19
|
import { logExecution } from './execution-log'
|
|
20
20
|
import { streamAgentChat } from './stream-agent-chat'
|
|
21
21
|
import { runLinkUnderstanding } from './link-understanding'
|
|
22
22
|
import { buildSessionTools } from './session-tools'
|
|
23
|
+
import type { StructuredToolInterface } from '@langchain/core/tools'
|
|
24
|
+
import type { Session } from '@/types'
|
|
23
25
|
import { stripMainLoopMetaForPersistence } from './main-agent-loop'
|
|
24
26
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
25
27
|
import { getMemoryDb } from './memory-db'
|
|
26
28
|
import { routeTaskIntent } from './capability-router'
|
|
27
29
|
import { notify } from './ws-hub'
|
|
28
30
|
import { resolveConcreteToolPolicyBlock, resolveSessionToolPolicy } from './tool-capability-policy'
|
|
31
|
+
import { toolIdMatches } from './tool-aliases'
|
|
29
32
|
import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
|
|
33
|
+
import {
|
|
34
|
+
getCachedLlmResponse,
|
|
35
|
+
resolveLlmResponseCacheConfig,
|
|
36
|
+
setCachedLlmResponse,
|
|
37
|
+
type LlmResponseCacheKeyInput,
|
|
38
|
+
} from './llm-response-cache'
|
|
30
39
|
import type { Message, MessageToolEvent, SSEEvent, UsageRecord } from '@/types'
|
|
31
40
|
import { markProviderFailure, markProviderSuccess, rankDelegatesByHealth } from './provider-health'
|
|
32
41
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
@@ -148,20 +157,32 @@ function requestedToolNamesFromMessage(message: string): string[] {
|
|
|
148
157
|
'manage_connectors',
|
|
149
158
|
'manage_sessions',
|
|
150
159
|
'manage_secrets',
|
|
160
|
+
'manage_capabilities',
|
|
161
|
+
'manage_platform',
|
|
162
|
+
'manage_chatrooms',
|
|
163
|
+
'search_marketplace',
|
|
164
|
+
'monitor_tool',
|
|
165
|
+
'plugin_creator_tool',
|
|
151
166
|
'memory_tool',
|
|
167
|
+
'wallet_tool',
|
|
168
|
+
'http_request',
|
|
169
|
+
'send_file',
|
|
152
170
|
'browser',
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'read_file',
|
|
157
|
-
'write_file',
|
|
158
|
-
'list_files',
|
|
159
|
-
'copy_file',
|
|
160
|
-
'move_file',
|
|
161
|
-
'delete_file',
|
|
171
|
+
'web',
|
|
172
|
+
'shell',
|
|
173
|
+
'files',
|
|
162
174
|
'edit_file',
|
|
163
|
-
'
|
|
164
|
-
'
|
|
175
|
+
'sandbox_exec',
|
|
176
|
+
'sandbox_list_runtimes',
|
|
177
|
+
'git',
|
|
178
|
+
'canvas',
|
|
179
|
+
'delegate',
|
|
180
|
+
'schedule_wake',
|
|
181
|
+
'spawn_subagent',
|
|
182
|
+
'context_status',
|
|
183
|
+
'context_summarize',
|
|
184
|
+
'openclaw_nodes',
|
|
185
|
+
'openclaw_workspace',
|
|
165
186
|
]
|
|
166
187
|
return candidates.filter((name) => lower.includes(name.toLowerCase()))
|
|
167
188
|
}
|
|
@@ -305,12 +326,12 @@ function extractDelegationTask(message: string, toolName: string): string | null
|
|
|
305
326
|
}
|
|
306
327
|
|
|
307
328
|
function hasToolEnabled(session: SessionWithTools, toolName: string): boolean {
|
|
308
|
-
return
|
|
329
|
+
return toolIdMatches(session?.tools || [], toolName)
|
|
309
330
|
}
|
|
310
331
|
|
|
311
332
|
function enabledDelegationTools(session: SessionWithTools): DelegateTool[] {
|
|
312
333
|
const tools: DelegateTool[] = []
|
|
313
|
-
if (hasToolEnabled(session, 'claude_code')) tools.push('delegate_to_claude_code')
|
|
334
|
+
if (hasToolEnabled(session, 'claude_code') || hasToolEnabled(session, 'delegate')) tools.push('delegate_to_claude_code')
|
|
314
335
|
if (hasToolEnabled(session, 'codex_cli')) tools.push('delegate_to_codex_cli')
|
|
315
336
|
if (hasToolEnabled(session, 'opencode_cli')) tools.push('delegate_to_opencode_cli')
|
|
316
337
|
return tools
|
|
@@ -334,9 +355,10 @@ function getTodaySpendUsd(): number {
|
|
|
334
355
|
let total = 0
|
|
335
356
|
for (const records of Object.values(usage)) {
|
|
336
357
|
for (const record of records || []) {
|
|
337
|
-
const
|
|
358
|
+
const rec = record as Record<string, unknown>
|
|
359
|
+
const ts = typeof rec?.timestamp === 'number' ? rec.timestamp : 0
|
|
338
360
|
if (ts < minTs) continue
|
|
339
|
-
const cost = typeof
|
|
361
|
+
const cost = typeof rec?.estimatedCost === 'number' ? rec.estimatedCost : 0
|
|
340
362
|
if (Number.isFinite(cost) && cost > 0) total += cost
|
|
341
363
|
}
|
|
342
364
|
}
|
|
@@ -390,7 +412,7 @@ function syncSessionFromAgent(sessionId: string): void {
|
|
|
390
412
|
}
|
|
391
413
|
}
|
|
392
414
|
|
|
393
|
-
function buildAgentSystemPrompt(session:
|
|
415
|
+
function buildAgentSystemPrompt(session: Session): string | undefined {
|
|
394
416
|
if (!session.agentId) return undefined
|
|
395
417
|
const agents = loadAgents()
|
|
396
418
|
const agent = agents[session.agentId]
|
|
@@ -514,7 +536,7 @@ function normalizeMemoryText(value: string): string {
|
|
|
514
536
|
}
|
|
515
537
|
|
|
516
538
|
function shouldStoreAutoMemoryNote(opts: {
|
|
517
|
-
session:
|
|
539
|
+
session: Session
|
|
518
540
|
source: string
|
|
519
541
|
internal: boolean
|
|
520
542
|
message: string
|
|
@@ -537,7 +559,7 @@ function shouldStoreAutoMemoryNote(opts: {
|
|
|
537
559
|
}
|
|
538
560
|
|
|
539
561
|
function storeAutoMemoryNote(opts: {
|
|
540
|
-
session:
|
|
562
|
+
session: Session
|
|
541
563
|
message: string
|
|
542
564
|
response: string
|
|
543
565
|
source: string
|
|
@@ -564,12 +586,12 @@ function storeAutoMemoryNote(opts: {
|
|
|
564
586
|
}
|
|
565
587
|
}
|
|
566
588
|
const created = db.add({
|
|
567
|
-
agentId: session.agentId,
|
|
568
|
-
sessionId: session.id,
|
|
589
|
+
agentId: session.agentId as string,
|
|
590
|
+
sessionId: session.id as string,
|
|
569
591
|
category: 'execution',
|
|
570
592
|
title,
|
|
571
593
|
content,
|
|
572
|
-
}
|
|
594
|
+
})
|
|
573
595
|
session.lastAutoMemoryAt = now
|
|
574
596
|
return created?.id || null
|
|
575
597
|
} catch {
|
|
@@ -578,9 +600,9 @@ function storeAutoMemoryNote(opts: {
|
|
|
578
600
|
}
|
|
579
601
|
|
|
580
602
|
export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promise<ExecuteChatTurnResult> {
|
|
603
|
+
const { message } = input
|
|
581
604
|
const {
|
|
582
605
|
sessionId,
|
|
583
|
-
message,
|
|
584
606
|
imagePath,
|
|
585
607
|
imageUrl,
|
|
586
608
|
attachedFiles,
|
|
@@ -623,16 +645,17 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
623
645
|
onEvent?.({ t: 'err', text: `Capability policy blocked tools for this run: ${blockedSummary}` })
|
|
624
646
|
}
|
|
625
647
|
|
|
626
|
-
// --- Agent
|
|
648
|
+
// --- Agent spend-limit enforcement (hourly/daily/monthly) ---
|
|
627
649
|
if (session.agentId) {
|
|
628
650
|
const agentsMap = loadAgents()
|
|
629
651
|
const agent = agentsMap[session.agentId]
|
|
630
|
-
if (agent
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
652
|
+
if (agent) {
|
|
653
|
+
const budgetCheck = checkAgentBudgetLimits(agent)
|
|
654
|
+
const action = agent.budgetAction || 'warn'
|
|
655
|
+
|
|
656
|
+
if (budgetCheck.exceeded.length > 0) {
|
|
657
|
+
const budgetError = budgetCheck.exceeded.map((entry) => entry.message).join(' ')
|
|
634
658
|
if (action === 'block') {
|
|
635
|
-
const budgetError = budgetResult.message || `Agent budget exceeded: $${budgetResult.spend.toFixed(4)} / $${budgetResult.budget.toFixed(2)}`
|
|
636
659
|
onEvent?.({ t: 'err', text: budgetError })
|
|
637
660
|
|
|
638
661
|
let persisted = false
|
|
@@ -657,7 +680,10 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
657
680
|
}
|
|
658
681
|
}
|
|
659
682
|
// budgetAction === 'warn': emit a warning but continue
|
|
660
|
-
onEvent?.({ t: 'status', text: JSON.stringify({ budgetWarning:
|
|
683
|
+
onEvent?.({ t: 'status', text: JSON.stringify({ budgetWarning: budgetError }) })
|
|
684
|
+
} else if (budgetCheck.warnings.length > 0) {
|
|
685
|
+
const warningText = budgetCheck.warnings.map((entry) => entry.message).join(' ')
|
|
686
|
+
onEvent?.({ t: 'status', text: JSON.stringify({ budgetWarning: warningText }) })
|
|
661
687
|
}
|
|
662
688
|
}
|
|
663
689
|
}
|
|
@@ -745,7 +771,11 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
745
771
|
const accumulatedUsage = { inputTokens: 0, outputTokens: 0, estimatedCost: 0 }
|
|
746
772
|
|
|
747
773
|
let thinkingText = ''
|
|
774
|
+
let streamingPartialText = ''
|
|
748
775
|
const emit = (ev: SSEEvent) => {
|
|
776
|
+
if (ev.t === 'd' && typeof ev.text === 'string') {
|
|
777
|
+
streamingPartialText += ev.text
|
|
778
|
+
}
|
|
749
779
|
if (ev.t === 'err' && typeof ev.text === 'string') {
|
|
750
780
|
const trimmed = ev.text.trim()
|
|
751
781
|
if (trimmed) {
|
|
@@ -771,6 +801,36 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
771
801
|
onEvent?.(ev)
|
|
772
802
|
}
|
|
773
803
|
|
|
804
|
+
// Periodic partial save so a browser refresh doesn't lose the in-flight response.
|
|
805
|
+
let lastPartialSaveLen = 0
|
|
806
|
+
const PARTIAL_SAVE_INTERVAL_MS = 5000
|
|
807
|
+
const partialSaveTimer = setInterval(() => {
|
|
808
|
+
if (streamingPartialText.length > lastPartialSaveLen) {
|
|
809
|
+
lastPartialSaveLen = streamingPartialText.length
|
|
810
|
+
try {
|
|
811
|
+
const fresh = loadSessions()
|
|
812
|
+
const current = fresh[sessionId]
|
|
813
|
+
if (!current) return
|
|
814
|
+
const partialMsg: Message = {
|
|
815
|
+
role: 'assistant',
|
|
816
|
+
text: streamingPartialText,
|
|
817
|
+
time: Date.now(),
|
|
818
|
+
streaming: true,
|
|
819
|
+
toolEvents: toolEvents.length ? [...toolEvents] : undefined,
|
|
820
|
+
}
|
|
821
|
+
const lastMsg = current.messages.at(-1)
|
|
822
|
+
if (lastMsg?.streaming) {
|
|
823
|
+
current.messages[current.messages.length - 1] = partialMsg
|
|
824
|
+
} else {
|
|
825
|
+
current.messages.push(partialMsg)
|
|
826
|
+
}
|
|
827
|
+
fresh[sessionId] = current
|
|
828
|
+
saveSessions(fresh)
|
|
829
|
+
notify(`messages:${sessionId}`)
|
|
830
|
+
} catch { /* partial save is best-effort */ }
|
|
831
|
+
}
|
|
832
|
+
}, PARTIAL_SAVE_INTERVAL_MS)
|
|
833
|
+
|
|
774
834
|
const parseAndEmit = (raw: string) => {
|
|
775
835
|
const lines = raw.split('\n').filter(Boolean)
|
|
776
836
|
for (const line of lines) {
|
|
@@ -798,6 +858,9 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
798
858
|
// Capture provider-reported usage for the direct (non-tools) path.
|
|
799
859
|
// Uses a mutable object because TS can't track callback mutations on plain variables.
|
|
800
860
|
const directUsage = { inputTokens: 0, outputTokens: 0, received: false }
|
|
861
|
+
const responseCacheConfig = resolveLlmResponseCacheConfig(appSettings)
|
|
862
|
+
let responseCacheHit = false
|
|
863
|
+
let responseCacheInput: LlmResponseCacheKeyInput | null = null
|
|
801
864
|
const hasTools = !!sessionForRun.tools?.length && !NON_LANGGRAPH_PROVIDER_IDS.has(providerType)
|
|
802
865
|
|
|
803
866
|
let durationMs = 0
|
|
@@ -811,30 +874,75 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
811
874
|
: undefined
|
|
812
875
|
|
|
813
876
|
console.log(`[chat-execution] provider=${providerType}, hasTools=${hasTools}, imagePath=${imagePath || 'none'}, attachedFiles=${attachedFiles?.length || 0}, tools=${(sessionForRun.tools || []).length}`)
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
877
|
+
if (hasTools) {
|
|
878
|
+
fullResponse = (await streamAgentChat({
|
|
879
|
+
session: sessionForRun,
|
|
880
|
+
message: message,
|
|
881
|
+
imagePath,
|
|
882
|
+
attachedFiles,
|
|
883
|
+
apiKey,
|
|
884
|
+
systemPrompt,
|
|
885
|
+
write: (raw) => parseAndEmit(raw),
|
|
886
|
+
history: heartbeatHistory ?? applyContextClearBoundary(getSessionMessages(sessionId)),
|
|
887
|
+
signal: abortController.signal,
|
|
888
|
+
})).fullText
|
|
889
|
+
} else {
|
|
890
|
+
const directHistorySnapshot = isAutoRunNoHistory
|
|
891
|
+
? getSessionMessages(sessionId).slice(-6)
|
|
892
|
+
: applyContextClearBoundary(getSessionMessages(sessionId))
|
|
893
|
+
responseCacheInput = {
|
|
894
|
+
provider: providerType,
|
|
895
|
+
model: sessionForRun.model,
|
|
896
|
+
apiEndpoint: sessionForRun.apiEndpoint || '',
|
|
897
|
+
systemPrompt,
|
|
898
|
+
message: message,
|
|
899
|
+
imagePath,
|
|
900
|
+
imageUrl,
|
|
901
|
+
attachedFiles,
|
|
902
|
+
history: directHistorySnapshot,
|
|
903
|
+
}
|
|
904
|
+
const canUseResponseCache = !internal && responseCacheConfig.enabled
|
|
905
|
+
const cached = canUseResponseCache
|
|
906
|
+
? getCachedLlmResponse(responseCacheInput, responseCacheConfig)
|
|
907
|
+
: null
|
|
908
|
+
if (cached) {
|
|
909
|
+
responseCacheHit = true
|
|
910
|
+
fullResponse = cached.text
|
|
911
|
+
emit({
|
|
912
|
+
t: 'md',
|
|
913
|
+
text: JSON.stringify({
|
|
914
|
+
cache: {
|
|
915
|
+
hit: true,
|
|
916
|
+
ageMs: cached.ageMs,
|
|
917
|
+
provider: cached.provider,
|
|
918
|
+
model: cached.model,
|
|
919
|
+
},
|
|
920
|
+
}),
|
|
921
|
+
})
|
|
922
|
+
emit({ t: 'd', text: cached.text })
|
|
923
|
+
} else {
|
|
924
|
+
fullResponse = await provider.handler.streamChat({
|
|
827
925
|
session: sessionForRun,
|
|
828
|
-
message,
|
|
926
|
+
message: message,
|
|
829
927
|
imagePath,
|
|
830
928
|
apiKey,
|
|
831
929
|
systemPrompt,
|
|
832
930
|
write: (raw: string) => parseAndEmit(raw),
|
|
833
931
|
active,
|
|
834
|
-
loadHistory:
|
|
932
|
+
loadHistory: (sid: string) => {
|
|
933
|
+
if (sid === sessionId) return directHistorySnapshot
|
|
934
|
+
return isAutoRunNoHistory
|
|
935
|
+
? getSessionMessages(sid).slice(-6)
|
|
936
|
+
: applyContextClearBoundary(getSessionMessages(sid))
|
|
937
|
+
},
|
|
835
938
|
onUsage: (u) => { directUsage.inputTokens = u.inputTokens; directUsage.outputTokens = u.outputTokens; directUsage.received = true },
|
|
836
939
|
signal: abortController.signal,
|
|
837
940
|
})
|
|
941
|
+
if (canUseResponseCache && responseCacheInput && fullResponse) {
|
|
942
|
+
setCachedLlmResponse(responseCacheInput, fullResponse, responseCacheConfig)
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
838
946
|
durationMs = Date.now() - startTs
|
|
839
947
|
} catch (err: unknown) {
|
|
840
948
|
errorMessage = err instanceof Error ? err.message : String(err)
|
|
@@ -848,6 +956,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
848
956
|
error: failureText,
|
|
849
957
|
})
|
|
850
958
|
} finally {
|
|
959
|
+
clearInterval(partialSaveTimer)
|
|
851
960
|
active.delete(sessionId)
|
|
852
961
|
if (signal) signal.removeEventListener('abort', abortFromOutside)
|
|
853
962
|
}
|
|
@@ -858,7 +967,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
858
967
|
|
|
859
968
|
// Record usage for the direct (non-tools) streamChat path.
|
|
860
969
|
// streamAgentChat already calls appendUsage internally for the tools path.
|
|
861
|
-
if (!hasTools && fullResponse && !errorMessage) {
|
|
970
|
+
if (!hasTools && fullResponse && !errorMessage && !responseCacheHit) {
|
|
862
971
|
const inputTokens = directUsage.received ? directUsage.inputTokens : Math.ceil(message.length / 4)
|
|
863
972
|
const outputTokens = directUsage.received ? directUsage.outputTokens : Math.ceil(fullResponse.length / 4)
|
|
864
973
|
const totalTokens = inputTokens + outputTokens
|
|
@@ -893,6 +1002,54 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
893
1002
|
: null
|
|
894
1003
|
const calledNames = new Set((toolEvents || []).map((t) => t.name))
|
|
895
1004
|
|
|
1005
|
+
const translateToolInvocation = (
|
|
1006
|
+
requestedName: string,
|
|
1007
|
+
rawArgs: Record<string, unknown>,
|
|
1008
|
+
): { toolName: string; args: Record<string, unknown> } => {
|
|
1009
|
+
if (requestedName === 'web_search') {
|
|
1010
|
+
return {
|
|
1011
|
+
toolName: 'web',
|
|
1012
|
+
args: {
|
|
1013
|
+
action: 'search',
|
|
1014
|
+
query: typeof rawArgs.query === 'string' ? rawArgs.query : message.trim(),
|
|
1015
|
+
maxResults: typeof rawArgs.maxResults === 'number' ? rawArgs.maxResults : 5,
|
|
1016
|
+
},
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (requestedName === 'web_fetch') {
|
|
1020
|
+
return {
|
|
1021
|
+
toolName: 'web',
|
|
1022
|
+
args: {
|
|
1023
|
+
action: 'fetch',
|
|
1024
|
+
url: rawArgs.url,
|
|
1025
|
+
},
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (requestedName === 'delegate_to_claude_code') {
|
|
1029
|
+
return { toolName: 'delegate', args: { ...rawArgs, backend: 'claude' } }
|
|
1030
|
+
}
|
|
1031
|
+
if (requestedName === 'delegate_to_codex_cli') {
|
|
1032
|
+
return { toolName: 'delegate', args: { ...rawArgs, backend: 'codex' } }
|
|
1033
|
+
}
|
|
1034
|
+
if (requestedName === 'delegate_to_opencode_cli') {
|
|
1035
|
+
return { toolName: 'delegate', args: { ...rawArgs, backend: 'opencode' } }
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const managePrefix = 'manage_'
|
|
1039
|
+
if (requestedName.startsWith(managePrefix) && requestedName !== 'manage_platform') {
|
|
1040
|
+
const resource = requestedName.slice(managePrefix.length)
|
|
1041
|
+
if (resource) {
|
|
1042
|
+
const { action, id, data, ...rest } = rawArgs
|
|
1043
|
+
return {
|
|
1044
|
+
toolName: 'manage_platform',
|
|
1045
|
+
args: { resource, action, id, data, ...rest },
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
return { toolName: requestedName, args: rawArgs }
|
|
1051
|
+
}
|
|
1052
|
+
|
|
896
1053
|
const invokeSessionTool = async (toolName: string, args: Record<string, unknown>, failurePrefix: string): Promise<boolean> => {
|
|
897
1054
|
const blockedReason = resolveConcreteToolPolicyBlock(toolName, toolPolicy, appSettings)
|
|
898
1055
|
if (blockedReason) {
|
|
@@ -916,11 +1073,12 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
916
1073
|
mcpDisabledTools: agent?.mcpDisabledTools,
|
|
917
1074
|
})
|
|
918
1075
|
try {
|
|
919
|
-
const
|
|
1076
|
+
const translated = translateToolInvocation(toolName, args)
|
|
1077
|
+
const selectedTool = tools.find((t) => t?.name === translated.toolName) as StructuredToolInterface | undefined
|
|
920
1078
|
if (!selectedTool?.invoke) return false
|
|
921
|
-
const toolInput = JSON.stringify(args)
|
|
1079
|
+
const toolInput = JSON.stringify(translated.args)
|
|
922
1080
|
emit({ t: 'tool_call', toolName, toolInput })
|
|
923
|
-
const toolOutput = await selectedTool.invoke(args)
|
|
1081
|
+
const toolOutput = await selectedTool.invoke(translated.args)
|
|
924
1082
|
const outputText = typeof toolOutput === 'string' ? toolOutput : JSON.stringify(toolOutput)
|
|
925
1083
|
emit({ t: 'tool_result', toolName, toolOutput: outputText })
|
|
926
1084
|
// Don't overwrite fullResponse with raw tool output — it's already captured
|
|
@@ -932,8 +1090,8 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
932
1090
|
}
|
|
933
1091
|
calledNames.add(toolName)
|
|
934
1092
|
return true
|
|
935
|
-
} catch (forceErr:
|
|
936
|
-
emit({ t: 'err', text: `${failurePrefix}: ${forceErr
|
|
1093
|
+
} catch (forceErr: unknown) {
|
|
1094
|
+
emit({ t: 'err', text: `${failurePrefix}: ${forceErr instanceof Error ? forceErr.message : String(forceErr)}` })
|
|
937
1095
|
return false
|
|
938
1096
|
} finally {
|
|
939
1097
|
await cleanup()
|
|
@@ -1120,8 +1278,8 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1120
1278
|
let changed = false
|
|
1121
1279
|
const persistField = (key: string, value: unknown) => {
|
|
1122
1280
|
const normalized = normalizeResumeId(value)
|
|
1123
|
-
if ((current as
|
|
1124
|
-
;(current as
|
|
1281
|
+
if ((current as Record<string, unknown>)[key] !== normalized) {
|
|
1282
|
+
;(current as Record<string, unknown>)[key] = normalized
|
|
1125
1283
|
changed = true
|
|
1126
1284
|
}
|
|
1127
1285
|
}
|
|
@@ -1135,10 +1293,12 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1135
1293
|
const currentResume = (current.delegateResumeIds && typeof current.delegateResumeIds === 'object')
|
|
1136
1294
|
? current.delegateResumeIds
|
|
1137
1295
|
: {}
|
|
1296
|
+
const sr = sourceResume as Record<string, unknown>
|
|
1297
|
+
const cr = currentResume as Record<string, unknown>
|
|
1138
1298
|
const nextResume = {
|
|
1139
|
-
claudeCode: normalizeResumeId(
|
|
1140
|
-
codex: normalizeResumeId(
|
|
1141
|
-
opencode: normalizeResumeId(
|
|
1299
|
+
claudeCode: normalizeResumeId(sr.claudeCode ?? cr.claudeCode),
|
|
1300
|
+
codex: normalizeResumeId(sr.codex ?? cr.codex),
|
|
1301
|
+
opencode: normalizeResumeId(sr.opencode ?? cr.opencode),
|
|
1142
1302
|
}
|
|
1143
1303
|
if (JSON.stringify(currentResume) !== JSON.stringify(nextResume)) {
|
|
1144
1304
|
current.delegateResumeIds = nextResume
|
|
@@ -1147,7 +1307,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1147
1307
|
}
|
|
1148
1308
|
|
|
1149
1309
|
if (shouldPersistAssistant) {
|
|
1150
|
-
const persistedKind = internal && source
|
|
1310
|
+
const persistedKind = internal && source === 'heartbeat' ? 'heartbeat' : 'chat'
|
|
1151
1311
|
const persistedText = heartbeatClassification === 'strip'
|
|
1152
1312
|
? textForPersistence.replace(/HEARTBEAT_OK/gi, '').trim()
|
|
1153
1313
|
: textForPersistence
|
|
@@ -1161,7 +1321,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1161
1321
|
kind: persistedKind,
|
|
1162
1322
|
}
|
|
1163
1323
|
const previous = current.messages.at(-1)
|
|
1164
|
-
if (shouldReplaceRecentAssistantMessage({
|
|
1324
|
+
if (previous?.streaming || shouldReplaceRecentAssistantMessage({
|
|
1165
1325
|
previous,
|
|
1166
1326
|
nextToolEvents: toolEvents,
|
|
1167
1327
|
nextKind: persistedKind,
|
|
@@ -1188,12 +1348,13 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1188
1348
|
// Target routing for non-suppressed heartbeat alerts
|
|
1189
1349
|
if (isHeartbeatRun && heartbeatConfig?.target && heartbeatConfig.target !== 'none' && heartbeatConfig.showAlerts !== false) {
|
|
1190
1350
|
try {
|
|
1351
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1191
1352
|
const { listRunningConnectors, sendConnectorMessage } = require('./connectors/manager')
|
|
1192
1353
|
let connectorId: string | undefined
|
|
1193
1354
|
let channelId: string | undefined
|
|
1194
1355
|
if (heartbeatConfig.target === 'last') {
|
|
1195
1356
|
const running = listRunningConnectors()
|
|
1196
|
-
const first = running.find((c:
|
|
1357
|
+
const first = running.find((c: { recentChannelId?: string }) => c.recentChannelId)
|
|
1197
1358
|
if (first) {
|
|
1198
1359
|
connectorId = first.id
|
|
1199
1360
|
channelId = first.recentChannelId
|
|
@@ -1234,14 +1395,14 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1234
1395
|
session: current,
|
|
1235
1396
|
source,
|
|
1236
1397
|
internal,
|
|
1237
|
-
message,
|
|
1398
|
+
message: message,
|
|
1238
1399
|
response: textForPersistence,
|
|
1239
1400
|
now: Date.now(),
|
|
1240
1401
|
})
|
|
1241
1402
|
if (autoMemoryEligible) {
|
|
1242
1403
|
const storedId = storeAutoMemoryNote({
|
|
1243
1404
|
session: current,
|
|
1244
|
-
message,
|
|
1405
|
+
message: message,
|
|
1245
1406
|
response: textForPersistence,
|
|
1246
1407
|
source,
|
|
1247
1408
|
now: Date.now(),
|
|
@@ -1250,7 +1411,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1250
1411
|
}
|
|
1251
1412
|
|
|
1252
1413
|
// Don't extend idle timeout for heartbeat runs — only user-initiated activity counts
|
|
1253
|
-
if (source !== 'heartbeat' && source !== 'main-loop-followup') {
|
|
1414
|
+
if (source !== 'heartbeat' && source !== 'heartbeat-wake' && source !== 'main-loop-followup') {
|
|
1254
1415
|
current.lastActiveAt = Date.now()
|
|
1255
1416
|
}
|
|
1256
1417
|
fresh[sessionId] = current
|
|
@@ -4,23 +4,99 @@ export interface ClawHubSearchResult {
|
|
|
4
4
|
skills: ClawHubSkill[]
|
|
5
5
|
total: number
|
|
6
6
|
page: number
|
|
7
|
+
nextCursor?: string | null
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
const CLAWHUB_BASE_URL = process.env.CLAWHUB_API_URL || 'https://clawhub.
|
|
10
|
+
const CLAWHUB_BASE_URL = process.env.CLAWHUB_API_URL || 'https://clawhub.ai/api/v1'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Raw shape returned by the ClawHub `/skills` endpoint.
|
|
14
|
+
* Fields are mapped to our internal `ClawHubSkill` type.
|
|
15
|
+
*/
|
|
16
|
+
interface ClawHubRawItem {
|
|
17
|
+
slug: string
|
|
18
|
+
displayName?: string
|
|
19
|
+
name?: string
|
|
20
|
+
summary?: string
|
|
21
|
+
description?: string
|
|
22
|
+
author?: string | { name?: string }
|
|
23
|
+
tags?: Record<string, string> | string[]
|
|
24
|
+
stats?: { downloads?: number; installsAllTime?: number; stars?: number }
|
|
25
|
+
latestVersion?: { version?: string; changelog?: string }
|
|
26
|
+
metadata?: Record<string, unknown> | null
|
|
27
|
+
url?: string
|
|
28
|
+
createdAt?: number
|
|
29
|
+
updatedAt?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function mapRawToSkill(raw: ClawHubRawItem): ClawHubSkill {
|
|
33
|
+
const name = raw.displayName || raw.name || raw.slug
|
|
34
|
+
const description = raw.summary || raw.description || ''
|
|
35
|
+
const author = typeof raw.author === 'string'
|
|
36
|
+
? raw.author
|
|
37
|
+
: raw.author?.name || 'community'
|
|
38
|
+
const tags = Array.isArray(raw.tags)
|
|
39
|
+
? raw.tags
|
|
40
|
+
: raw.tags ? Object.keys(raw.tags) : []
|
|
41
|
+
const downloads = raw.stats?.installsAllTime ?? raw.stats?.downloads ?? 0
|
|
42
|
+
const version = raw.latestVersion?.version || '1.0.0'
|
|
43
|
+
return {
|
|
44
|
+
id: raw.slug,
|
|
45
|
+
name,
|
|
46
|
+
description,
|
|
47
|
+
author,
|
|
48
|
+
tags,
|
|
49
|
+
downloads,
|
|
50
|
+
url: raw.url || `https://clawhub.ai/skills/${raw.slug}`,
|
|
51
|
+
version,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
10
54
|
|
|
11
55
|
export async function searchClawHub(query: string, page = 1, limit = 20): Promise<ClawHubSearchResult> {
|
|
12
56
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
57
|
+
const params = new URLSearchParams({ limit: String(limit) })
|
|
58
|
+
if (query) params.set('q', query)
|
|
59
|
+
if (page > 1) params.set('page', String(page))
|
|
60
|
+
|
|
61
|
+
const url = `${CLAWHUB_BASE_URL}/skills?${params}`
|
|
62
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8000) })
|
|
15
63
|
if (!res.ok) throw new Error(`ClawHub responded with ${res.status}`)
|
|
16
|
-
|
|
17
|
-
|
|
64
|
+
|
|
65
|
+
const data = await res.json() as { items?: ClawHubRawItem[]; skills?: ClawHubRawItem[]; nextCursor?: string | null; total?: number }
|
|
66
|
+
|
|
67
|
+
// ClawHub v1 returns { items, nextCursor }; fall back to { skills, total } for compat
|
|
68
|
+
const rawItems = data.items || data.skills || []
|
|
69
|
+
const skills = rawItems.map(mapRawToSkill)
|
|
70
|
+
const total = data.total ?? (data.nextCursor ? skills.length + 1 : skills.length)
|
|
71
|
+
|
|
72
|
+
return { skills, total, page, nextCursor: data.nextCursor }
|
|
73
|
+
} catch (err: unknown) {
|
|
74
|
+
console.warn('[clawhub] search failed:', err instanceof Error ? err.message : String(err))
|
|
18
75
|
return { skills: [], total: 0, page }
|
|
19
76
|
}
|
|
20
77
|
}
|
|
21
78
|
|
|
22
79
|
export async function fetchSkillContent(rawUrl: string): Promise<string> {
|
|
23
|
-
|
|
80
|
+
// ClawHub skill pages are at /skills/<slug> — try raw content endpoint first
|
|
81
|
+
let contentUrl = rawUrl
|
|
82
|
+
if (contentUrl.startsWith('https://clawhub.ai/skills/') && !contentUrl.includes('/raw')) {
|
|
83
|
+
const slug = contentUrl.replace('https://clawhub.ai/skills/', '').replace(/\/$/, '')
|
|
84
|
+
// Try the raw content API first
|
|
85
|
+
const rawApiUrl = `${CLAWHUB_BASE_URL}/skills/${slug}/content`
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(rawApiUrl, { signal: AbortSignal.timeout(8000) })
|
|
88
|
+
if (res.ok) {
|
|
89
|
+
const data = await res.json() as { content?: string }
|
|
90
|
+
if (data.content) return data.content
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Fall through to direct fetch
|
|
94
|
+
}
|
|
95
|
+
// Try the raw endpoint pattern
|
|
96
|
+
contentUrl = `https://clawhub.ai/skills/${slug}/raw`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const res = await fetch(contentUrl, { signal: AbortSignal.timeout(10000) })
|
|
24
100
|
if (!res.ok) throw new Error(`Failed to fetch skill content: ${res.status}`)
|
|
25
101
|
return res.text()
|
|
26
102
|
}
|
|
@@ -335,6 +335,7 @@ export function isCurrentGeneration(connectorId: string, gen: number): boolean {
|
|
|
335
335
|
|
|
336
336
|
/** Get platform implementation lazily */
|
|
337
337
|
export async function getPlatform(platform: string) {
|
|
338
|
+
// 1. Check Built-ins
|
|
338
339
|
switch (platform) {
|
|
339
340
|
case 'discord': return (await import('./discord')).default
|
|
340
341
|
case 'telegram': return (await import('./telegram')).default
|
|
@@ -347,8 +348,33 @@ export async function getPlatform(platform: string) {
|
|
|
347
348
|
case 'googlechat': return (await import('./googlechat')).default
|
|
348
349
|
case 'matrix': return (await import('./matrix')).default
|
|
349
350
|
case 'email': return (await import('./email')).default
|
|
350
|
-
default: throw new Error(`Unknown platform: ${platform}`)
|
|
351
351
|
}
|
|
352
|
+
|
|
353
|
+
// 2. Check Plugin-provided connectors
|
|
354
|
+
try {
|
|
355
|
+
const { getPluginManager } = await import('../plugins')
|
|
356
|
+
const manager = getPluginManager()
|
|
357
|
+
const pluginConnectors = manager.getConnectors()
|
|
358
|
+
const found = pluginConnectors.find(c => c.id === platform)
|
|
359
|
+
|
|
360
|
+
if (found) {
|
|
361
|
+
return {
|
|
362
|
+
start: async (connector: Connector, token: string, onMessage: (msg: InboundMessage) => Promise<string>) => {
|
|
363
|
+
const stop = found.startListener ? await found.startListener(onMessage) : () => {}
|
|
364
|
+
return {
|
|
365
|
+
connector,
|
|
366
|
+
stop: async () => { if (stop) await stop() },
|
|
367
|
+
sendMessage: found.sendMessage,
|
|
368
|
+
authenticated: true,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} catch (err: unknown) {
|
|
374
|
+
console.warn(`[connector] Failed to check plugins for platform "${platform}":`, err instanceof Error ? err.message : String(err))
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
throw new Error(`Unknown platform: ${platform}`)
|
|
352
378
|
}
|
|
353
379
|
|
|
354
380
|
export function formatMediaLine(media: InboundMedia): string {
|