@swarmclawai/swarmclaw 1.2.0 → 1.2.1
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 +10 -0
- package/package.json +4 -1
- package/src/app/api/chats/[id]/deploy/route.ts +11 -6
- package/src/app/api/chats/[id]/devserver/route.ts +5 -2
- package/src/app/api/chats/[id]/messages/route.ts +7 -1
- package/src/app/api/credentials/[id]/route.ts +4 -1
- package/src/app/api/extensions/marketplace/route.ts +5 -2
- package/src/app/api/memory/maintenance/route.ts +5 -2
- package/src/app/api/preview-server/route.ts +14 -11
- package/src/app/api/system/status/route.ts +11 -0
- package/src/app/api/upload/route.ts +4 -1
- package/src/cli/index.js +7 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-files-editor.tsx +44 -32
- package/src/components/agents/personality-builder.tsx +13 -7
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +1 -0
- package/src/components/chat/message-list.tsx +25 -39
- package/src/components/chat/swarm-status-card.tsx +10 -3
- package/src/components/layout/daemon-indicator.tsx +7 -8
- package/src/components/layout/update-banner.tsx +8 -13
- package/src/components/logs/log-list.tsx +1 -1
- package/src/components/memory/memory-card.tsx +3 -1
- package/src/components/org-chart/org-chart-view.tsx +4 -0
- package/src/components/projects/project-list.tsx +4 -2
- package/src/components/projects/tabs/overview-tab.tsx +3 -2
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +12 -6
- package/src/components/shared/dir-browser.tsx +22 -18
- package/src/components/skills/skill-sheet.tsx +2 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +1 -1
- package/src/hooks/use-openclaw-gateway.ts +46 -27
- package/src/instrumentation.ts +10 -7
- package/src/lib/chat/chat.ts +18 -2
- package/src/lib/providers/anthropic.ts +6 -3
- package/src/lib/providers/claude-cli.ts +9 -3
- package/src/lib/providers/cli-utils.ts +15 -0
- package/src/lib/providers/codex-cli.ts +9 -3
- package/src/lib/providers/gemini-cli.ts +6 -2
- package/src/lib/providers/index.ts +4 -1
- package/src/lib/providers/ollama.ts +5 -2
- package/src/lib/providers/openai.ts +8 -5
- package/src/lib/providers/opencode-cli.ts +6 -2
- package/src/lib/server/agents/agent-registry.ts +20 -3
- package/src/lib/server/agents/main-agent-loop.ts +4 -3
- package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
- package/src/lib/server/chat-execution/chat-execution.ts +14 -2
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
- package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
- package/src/lib/server/chat-execution/message-classifier.ts +5 -2
- package/src/lib/server/chat-execution/post-stream-finalization.ts +4 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +52 -9
- package/src/lib/server/chat-execution/response-completeness.ts +5 -2
- package/src/lib/server/chat-execution/stream-agent-chat.ts +42 -12
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
- package/src/lib/server/connectors/bluebubbles.ts +7 -4
- package/src/lib/server/connectors/connector-inbound.ts +16 -13
- package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
- package/src/lib/server/connectors/connector-outbound.ts +6 -3
- package/src/lib/server/connectors/discord.ts +10 -7
- package/src/lib/server/connectors/email.ts +17 -14
- package/src/lib/server/connectors/googlechat.ts +7 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
- package/src/lib/server/connectors/matrix.ts +6 -3
- package/src/lib/server/connectors/openclaw.ts +20 -17
- package/src/lib/server/connectors/outbox.ts +4 -1
- package/src/lib/server/connectors/runtime-state.ts +19 -0
- package/src/lib/server/connectors/session-consolidation.ts +5 -2
- package/src/lib/server/connectors/signal.ts +9 -6
- package/src/lib/server/connectors/slack.ts +13 -10
- package/src/lib/server/connectors/teams.ts +8 -5
- package/src/lib/server/connectors/telegram.ts +15 -12
- package/src/lib/server/connectors/whatsapp.ts +32 -29
- package/src/lib/server/embeddings.ts +4 -1
- package/src/lib/server/link-understanding.ts +4 -1
- package/src/lib/server/memory/memory-abstract.ts +59 -0
- package/src/lib/server/memory/memory-db.ts +40 -14
- package/src/lib/server/missions/mission-service.ts +6 -3
- package/src/lib/server/openclaw/gateway.ts +8 -5
- package/src/lib/server/project-utils.ts +13 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
- package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
- package/src/lib/server/provider-health.ts +18 -0
- package/src/lib/server/query-expansion.ts +4 -1
- package/src/lib/server/runtime/alert-dispatch.ts +7 -6
- package/src/lib/server/runtime/daemon-state.ts +189 -50
- package/src/lib/server/runtime/heartbeat-service.ts +23 -0
- package/src/lib/server/runtime/idle-window.ts +4 -1
- package/src/lib/server/runtime/perf.ts +4 -1
- package/src/lib/server/runtime/process-manager.ts +7 -4
- package/src/lib/server/runtime/queue.ts +31 -28
- package/src/lib/server/runtime/scheduler.ts +9 -6
- package/src/lib/server/runtime/session-run-manager.ts +3 -0
- package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
- package/src/lib/server/sandbox/novnc-auth.ts +10 -0
- package/src/lib/server/session-tools/context.ts +14 -0
- package/src/lib/server/session-tools/discovery.ts +9 -6
- package/src/lib/server/session-tools/index.ts +3 -1
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/subagent.ts +23 -2
- package/src/lib/server/session-tools/wallet.ts +4 -1
- package/src/lib/server/skills/clawhub-client.ts +4 -1
- package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
- package/src/lib/server/skills/skill-eligibility.ts +6 -0
- package/src/lib/server/solana.ts +6 -0
- package/src/lib/server/storage-auth.ts +5 -5
- package/src/lib/server/storage-normalization.ts +4 -0
- package/src/lib/server/storage.ts +19 -8
- package/src/lib/server/tasks/task-followups.ts +4 -1
- package/src/lib/server/tool-loop-detection.ts +8 -3
- package/src/lib/server/tool-planning.ts +226 -0
- package/src/lib/server/tool-retry.ts +4 -3
- package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
- package/src/lib/server/ws-hub.ts +5 -2
- package/src/lib/strip-internal-metadata.test.ts +44 -4
- package/src/lib/strip-internal-metadata.ts +20 -6
- package/src/stores/use-approval-store.ts +7 -1
- package/src/stores/use-chat-store.ts +5 -1
- package/src/types/index.ts +6 -0
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
buildCredentialAwarenessSection,
|
|
29
29
|
} from '@/lib/server/chat-execution/prompt-sections'
|
|
30
30
|
|
|
31
|
+
import { log } from '@/lib/server/logger'
|
|
31
32
|
import { logExecution } from '@/lib/server/execution-log'
|
|
32
33
|
import { buildCurrentDateTimePromptContext } from '@/lib/server/prompt-runtime-context'
|
|
33
34
|
import { expandExtensionIds } from '@/lib/server/tool-aliases'
|
|
@@ -40,6 +41,7 @@ import { routeTaskIntent } from '@/lib/server/capability-router'
|
|
|
40
41
|
import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
|
|
41
42
|
import { resolveSessionToolPolicy } from '@/lib/server/tool-capability-policy'
|
|
42
43
|
import { ToolLoopTracker } from '@/lib/server/tool-loop-detection'
|
|
44
|
+
import { isHeartbeatSource } from '@/lib/server/runtime/heartbeat-source'
|
|
43
45
|
import { isCurrentThreadRecallRequest } from '@/lib/server/memory/memory-policy'
|
|
44
46
|
import {
|
|
45
47
|
resolveEffectiveSessionMemoryScopeMode,
|
|
@@ -99,6 +101,8 @@ import {
|
|
|
99
101
|
type MessageClassification,
|
|
100
102
|
} from '@/lib/server/chat-execution/message-classifier'
|
|
101
103
|
|
|
104
|
+
const TAG = 'stream-agent-chat'
|
|
105
|
+
|
|
102
106
|
// LangGraph unhandledRejection handler has been moved to src/instrumentation.ts
|
|
103
107
|
// to avoid re-registration on every HMR reload.
|
|
104
108
|
|
|
@@ -175,6 +179,8 @@ interface StreamAgentChatOpts {
|
|
|
175
179
|
fallbackCredentialIds?: string[]
|
|
176
180
|
signal?: AbortSignal
|
|
177
181
|
promptMode?: PromptMode
|
|
182
|
+
/** Run source (e.g. 'heartbeat', 'chat', 'scheduler') — used for heartbeat-specific tuning. */
|
|
183
|
+
source?: string
|
|
178
184
|
}
|
|
179
185
|
|
|
180
186
|
export interface StreamAgentChatResult {
|
|
@@ -207,6 +213,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
207
213
|
async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
|
|
208
214
|
const startTs = Date.now()
|
|
209
215
|
const { session, message, imagePath, imageUrl, attachedFiles, apiKey, systemPrompt, extraSystemContext, write, history, fallbackCredentialIds, signal } = opts
|
|
216
|
+
const isHeartbeat = isHeartbeatSource(opts.source)
|
|
210
217
|
const promptMode: PromptMode = opts.promptMode ?? resolvePromptMode(session)
|
|
211
218
|
const isMinimalPrompt = promptMode === 'minimal'
|
|
212
219
|
const isConnectorSession = isDirectConnectorSession(session)
|
|
@@ -366,7 +373,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
366
373
|
const extensionContextParts = await collectCapabilityAgentContext(session, sessionExtensions, message, history)
|
|
367
374
|
promptParts.push(...extensionContextParts)
|
|
368
375
|
} catch (err: unknown) {
|
|
369
|
-
|
|
376
|
+
log.error(TAG, 'Capability context injection failed:', err instanceof Error ? err.message : String(err))
|
|
370
377
|
}
|
|
371
378
|
|
|
372
379
|
// Project context — full mode only
|
|
@@ -410,11 +417,15 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
410
417
|
{
|
|
411
418
|
const agents = loadAgents()
|
|
412
419
|
const agentForMemory = session.agentId ? agents[session.agentId] : null
|
|
413
|
-
const
|
|
420
|
+
const memoryResult = await buildProactiveMemorySection(
|
|
414
421
|
session, agentForMemory, message, activeProjectContext.projectRoot,
|
|
415
422
|
isMinimalPrompt, currentThreadRecallRequest,
|
|
416
423
|
)
|
|
417
|
-
if (
|
|
424
|
+
if (memoryResult.section) promptParts.push(memoryResult.section)
|
|
425
|
+
// Persist injection dedup counts so repeated memories are suppressed
|
|
426
|
+
if (Object.keys(memoryResult.injectedIds).length > 0) {
|
|
427
|
+
session.injectedMemoryIds = memoryResult.injectedIds
|
|
428
|
+
}
|
|
418
429
|
}
|
|
419
430
|
|
|
420
431
|
// Goal anchor — keeps the agent focused when context is long.
|
|
@@ -429,9 +440,26 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
429
440
|
const budget = isMinimalPrompt ? MINIMAL_PROMPT_BUDGET : DEFAULT_PROMPT_BUDGET
|
|
430
441
|
const budgetResult = applyPromptBudget(promptParts, budget)
|
|
431
442
|
if (budgetResult.truncated) {
|
|
432
|
-
|
|
443
|
+
log.warn(TAG, `Prompt truncated: ${budgetResult.originalChars} chars → ${budget.maxTotalChars} chars (mode=${promptMode})`)
|
|
433
444
|
} else if (isOverWarningThreshold(budgetResult.originalChars, budget)) {
|
|
434
|
-
|
|
445
|
+
log.warn(TAG, `Prompt near budget: ${budgetResult.originalChars}/${budget.maxTotalChars} chars (mode=${promptMode})`)
|
|
446
|
+
}
|
|
447
|
+
// Emit prompt section telemetry (no-op when perf disabled)
|
|
448
|
+
if (perf.isEnabled()) {
|
|
449
|
+
const sectionSizes: Record<string, number> = {}
|
|
450
|
+
for (let i = 0; i < rawPromptParts.length; i++) {
|
|
451
|
+
const part = rawPromptParts[i]
|
|
452
|
+
const headerMatch = part.match(/^##?\s+(.+?)[\n\r]/)
|
|
453
|
+
const label = headerMatch ? headerMatch[1].slice(0, 30) : `section_${i}`
|
|
454
|
+
sectionSizes[label] = (sectionSizes[label] || 0) + part.length
|
|
455
|
+
}
|
|
456
|
+
perf.start('prompt', 'budget', {
|
|
457
|
+
sessionId: session.id,
|
|
458
|
+
totalChars: budgetResult.originalChars,
|
|
459
|
+
budgetMax: budget.maxTotalChars,
|
|
460
|
+
truncated: budgetResult.truncated,
|
|
461
|
+
sections: sectionSizes,
|
|
462
|
+
})()
|
|
435
463
|
}
|
|
436
464
|
let prompt = budgetResult.prompt
|
|
437
465
|
|
|
@@ -443,6 +471,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
443
471
|
...(typeof settings.toolLoopFrequencyWarn === 'number' && { toolFrequencyWarn: settings.toolLoopFrequencyWarn }),
|
|
444
472
|
...(typeof settings.toolLoopFrequencyCritical === 'number' && { toolFrequencyCritical: settings.toolLoopFrequencyCritical }),
|
|
445
473
|
...(typeof settings.toolLoopCircuitBreaker === 'number' && { circuitBreaker: settings.toolLoopCircuitBreaker }),
|
|
474
|
+
// Heartbeat runs are brief status checks — tighten thresholds significantly
|
|
475
|
+
...(isHeartbeat && { toolFrequencyWarn: 8, toolFrequencyCritical: 15 }),
|
|
446
476
|
})
|
|
447
477
|
const emittedPreToolWarnings = new Set<string>()
|
|
448
478
|
const recursionLimit = getAgentLoopRecursionLimit(runtime)
|
|
@@ -453,14 +483,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
453
483
|
|
|
454
484
|
async function buildContentForFile(filePath: string): Promise<LangChainContentPart | string | null> {
|
|
455
485
|
if (!fs.existsSync(filePath)) {
|
|
456
|
-
|
|
486
|
+
log.info(TAG, `FILE NOT FOUND: ${filePath}`)
|
|
457
487
|
return null
|
|
458
488
|
}
|
|
459
489
|
const name = filePath.split('/').pop() || 'file'
|
|
460
490
|
if (IMAGE_EXTS.test(filePath)) {
|
|
461
491
|
const buf = fs.readFileSync(filePath)
|
|
462
492
|
if (buf.length === 0) {
|
|
463
|
-
|
|
493
|
+
log.warn(TAG, `Image file is empty: ${filePath}`)
|
|
464
494
|
return `[Attached image: ${name} — file is empty]`
|
|
465
495
|
}
|
|
466
496
|
const data = buf.toString('base64')
|
|
@@ -587,8 +617,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
587
617
|
summarize,
|
|
588
618
|
})
|
|
589
619
|
effectiveHistory = result.messages
|
|
590
|
-
|
|
591
|
-
`
|
|
620
|
+
log.info(TAG,
|
|
621
|
+
`Auto-compacted ${session.id}: ${recentHistory.length} → ${effectiveHistory.length} msgs` +
|
|
592
622
|
` (prompt history ${promptHistoryTokens} tokens)` +
|
|
593
623
|
(result.summaryAdded ? ' (LLM summary)' : ' (sliding window fallback)'),
|
|
594
624
|
)
|
|
@@ -783,7 +813,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
783
813
|
// Init turn state and limits
|
|
784
814
|
// -------------------------------------------------------------------------
|
|
785
815
|
const state = new ChatTurnState()
|
|
786
|
-
const limits = new ContinuationLimits(isConnectorSession)
|
|
816
|
+
const limits = new ContinuationLimits(isConnectorSession, isHeartbeat)
|
|
787
817
|
const routingDecision = routeTaskIntent(message, sessionExtensions, null)
|
|
788
818
|
const explicitRequiredToolNames = getExplicitRequiredToolNames(message, sessionExtensions)
|
|
789
819
|
|
|
@@ -1069,9 +1099,9 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
1069
1099
|
|
|
1070
1100
|
if (!shouldContinue) break
|
|
1071
1101
|
|
|
1072
|
-
//
|
|
1102
|
+
// Partial reset on loop_recovery: clear frequency counts but preserve circuit breaker + repeat history
|
|
1073
1103
|
if (shouldContinue === 'loop_recovery') {
|
|
1074
|
-
loopTracker.
|
|
1104
|
+
loopTracker.resetFrequencyCounts()
|
|
1075
1105
|
}
|
|
1076
1106
|
|
|
1077
1107
|
const continuationAssistantText = shouldContinue === 'memory_write_followthrough'
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* can recall cross-context conversations (chatroom ↔ direct chat).
|
|
6
6
|
*/
|
|
7
7
|
import type { Agent } from '@/types'
|
|
8
|
+
import { log } from '@/lib/server/logger'
|
|
9
|
+
|
|
10
|
+
const TAG = 'chatroom-memory-bridge'
|
|
8
11
|
|
|
9
12
|
/** Truncate text to a max length, collapsing whitespace */
|
|
10
13
|
function truncate(text: string, max: number): string {
|
|
@@ -58,7 +61,7 @@ export async function persistChatroomInteractionMemory(params: {
|
|
|
58
61
|
})
|
|
59
62
|
} catch (err: unknown) {
|
|
60
63
|
// Non-critical — log and continue
|
|
61
|
-
|
|
64
|
+
log.warn(TAG, 'Failed to persist interaction memory:', err instanceof Error ? err.message : String(err))
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -126,9 +129,9 @@ export async function summarizeAndConsolidateChatroomMemories(params: {
|
|
|
126
129
|
})
|
|
127
130
|
} catch (llmErr: unknown) {
|
|
128
131
|
// LLM summarization is best-effort
|
|
129
|
-
|
|
132
|
+
log.warn(TAG, 'LLM summarization failed:', llmErr instanceof Error ? llmErr.message : String(llmErr))
|
|
130
133
|
}
|
|
131
134
|
} catch (err: unknown) {
|
|
132
|
-
|
|
135
|
+
log.warn(TAG, 'Failed to consolidate memories:', err instanceof Error ? err.message : String(err))
|
|
133
136
|
}
|
|
134
137
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import crypto from 'node:crypto'
|
|
2
3
|
import type { PlatformConnector, ConnectorInstance, InboundMessage, InboundMedia } from './types'
|
|
3
4
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
4
5
|
|
|
6
|
+
const TAG = 'bluebubbles'
|
|
7
|
+
|
|
5
8
|
const DEFAULT_TIMEOUT_MS = 10_000
|
|
6
9
|
const DEFAULT_WEBHOOK_PATH = '/api/connectors/{id}/webhook'
|
|
7
10
|
|
|
@@ -283,8 +286,8 @@ const bluebubbles: PlatformConnector = {
|
|
|
283
286
|
throw new Error(`BlueBubbles ping failed (${pingRes.status})`)
|
|
284
287
|
}
|
|
285
288
|
|
|
286
|
-
|
|
287
|
-
|
|
289
|
+
log.info(TAG, `Connected to ${serverUrl}`)
|
|
290
|
+
log.info(TAG, `Inbound webhook endpoint: ${DEFAULT_WEBHOOK_PATH.replace('{id}', connector.id)}`)
|
|
288
291
|
|
|
289
292
|
return {
|
|
290
293
|
connector,
|
|
@@ -302,7 +305,7 @@ const bluebubbles: PlatformConnector = {
|
|
|
302
305
|
stopped = true
|
|
303
306
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
304
307
|
delete (globalThis as any)[handlerKey]
|
|
305
|
-
|
|
308
|
+
log.info(TAG, 'Connector stopped')
|
|
306
309
|
},
|
|
307
310
|
}
|
|
308
311
|
},
|
|
@@ -351,7 +354,7 @@ async function sendBlueBubblesText(params: {
|
|
|
351
354
|
// BlueBubbles may return empty body on success in some setups.
|
|
352
355
|
const message = getErrorMessage(err)
|
|
353
356
|
if (!message.toLowerCase().includes('json')) {
|
|
354
|
-
|
|
357
|
+
log.warn(TAG, `Unable to parse send response body: ${message}`)
|
|
355
358
|
}
|
|
356
359
|
return {}
|
|
357
360
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { genId } from '@/lib/id'
|
|
2
3
|
import {
|
|
3
4
|
loadConnectors,
|
|
@@ -95,6 +96,8 @@ import {
|
|
|
95
96
|
} from '@/lib/server/runtime/session-run-manager'
|
|
96
97
|
import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution'
|
|
97
98
|
|
|
99
|
+
const TAG = 'connector-inbound'
|
|
100
|
+
|
|
98
101
|
type ConnectorSession = Session
|
|
99
102
|
type CurrentChannelConnectorDelivery = {
|
|
100
103
|
mode: 'text' | 'voice_note'
|
|
@@ -205,14 +208,14 @@ async function routeOrDebounceInbound(connector: Connector, msg: InboundMessage)
|
|
|
205
208
|
clearTimeout(pending.timer)
|
|
206
209
|
pending.timer = setTimeout(() => {
|
|
207
210
|
void flushDebouncedInbound(debounceKey).catch((err: unknown) => {
|
|
208
|
-
|
|
211
|
+
log.warn(TAG, `Debounced inbound flush failed: ${errorMessage(err)}`)
|
|
209
212
|
})
|
|
210
213
|
}, policy.inboundDebounceMs)
|
|
211
214
|
pending.timer.unref?.()
|
|
212
215
|
} else {
|
|
213
216
|
const timer = setTimeout(() => {
|
|
214
217
|
void flushDebouncedInbound(debounceKey).catch((err: unknown) => {
|
|
215
|
-
|
|
218
|
+
log.warn(TAG, `Debounced inbound flush failed: ${errorMessage(err)}`)
|
|
216
219
|
})
|
|
217
220
|
}, policy.inboundDebounceMs)
|
|
218
221
|
timer.unref?.()
|
|
@@ -756,7 +759,7 @@ async function routeMessageToChatroom(connector: Connector, msg: InboundMessage)
|
|
|
756
759
|
} catch (err: unknown) {
|
|
757
760
|
const errMsg = errorMessage(err)
|
|
758
761
|
markProviderFailure(agent.provider, errMsg)
|
|
759
|
-
|
|
762
|
+
log.error(TAG, `Chatroom agent ${agent.name} error:`, errMsg)
|
|
760
763
|
}
|
|
761
764
|
}
|
|
762
765
|
|
|
@@ -785,9 +788,9 @@ async function routeMessageToChatroom(connector: Connector, msg: InboundMessage)
|
|
|
785
788
|
replyToMessageId: replyOptions.replyToMessageId,
|
|
786
789
|
threadId: replyOptions.threadId,
|
|
787
790
|
})
|
|
788
|
-
|
|
791
|
+
log.info(TAG, `Sent chatroom media to ${msg.platform}: ${path.basename(file.path)}`)
|
|
789
792
|
} catch (err: unknown) {
|
|
790
|
-
|
|
793
|
+
log.error(TAG, `Failed to send chatroom media ${path.basename(file.path)}:`, errorMessage(err))
|
|
791
794
|
}
|
|
792
795
|
}
|
|
793
796
|
}
|
|
@@ -1105,7 +1108,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1105
1108
|
})
|
|
1106
1109
|
} catch (err: unknown) {
|
|
1107
1110
|
const errText = errorMessage(err)
|
|
1108
|
-
|
|
1111
|
+
log.error(TAG, 'queued follow-up delivery failed:', errText)
|
|
1109
1112
|
try {
|
|
1110
1113
|
const { sendConnectorMessage } = await import('./connector-outbound')
|
|
1111
1114
|
await sendConnectorMessage({
|
|
@@ -1120,7 +1123,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1120
1123
|
}
|
|
1121
1124
|
}).catch(async (err: unknown) => {
|
|
1122
1125
|
const errText = errorMessage(err)
|
|
1123
|
-
|
|
1126
|
+
log.error(TAG, 'queued follow-up run failed:', errText)
|
|
1124
1127
|
try {
|
|
1125
1128
|
const { sendConnectorMessage } = await import('./connector-outbound')
|
|
1126
1129
|
await sendConnectorMessage({
|
|
@@ -1175,7 +1178,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1175
1178
|
if (transcript) currentChannelDeliveryRef.current?.transcripts.push(transcript)
|
|
1176
1179
|
}
|
|
1177
1180
|
const hasTools = getEnabledCapabilityIds(session).length > 0 && session.provider !== 'claude-cli'
|
|
1178
|
-
|
|
1181
|
+
log.info(TAG, `Routing message to agent "${agent.name}" (${session.provider}/${session.model}), hasTools=${!!hasTools}`)
|
|
1179
1182
|
|
|
1180
1183
|
if (hasTools) {
|
|
1181
1184
|
try {
|
|
@@ -1269,10 +1272,10 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1269
1272
|
// Use finalResponse for connectors — strips intermediate planning/tool-use text
|
|
1270
1273
|
fullText = result.finalResponse || result.fullText
|
|
1271
1274
|
mediaExtractionText = [result.fullText || '', ...toolMediaOutputs].filter(Boolean).join('\n\n')
|
|
1272
|
-
|
|
1275
|
+
log.info(TAG, `streamAgentChat returned ${result.fullText.length} chars total, ${fullText.length} chars final`)
|
|
1273
1276
|
} catch (err: unknown) {
|
|
1274
1277
|
const message = errorMessage(err)
|
|
1275
|
-
|
|
1278
|
+
log.error(TAG, 'streamAgentChat error:', message)
|
|
1276
1279
|
return `[Error] ${message}`
|
|
1277
1280
|
}
|
|
1278
1281
|
} else {
|
|
@@ -1324,7 +1327,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1324
1327
|
} else {
|
|
1325
1328
|
await maybeSendStatusReaction(connector, msg, 'silent')
|
|
1326
1329
|
}
|
|
1327
|
-
|
|
1330
|
+
log.info(TAG, 'Agent returned hidden control sentinel — suppressing outbound reply')
|
|
1328
1331
|
logExecution(session.id, 'decision', 'Agent suppressed outbound (NO_MESSAGE)', {
|
|
1329
1332
|
agentId: agent.id,
|
|
1330
1333
|
detail: { platform: msg.platform, channelId: msg.channelId },
|
|
@@ -1375,7 +1378,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1375
1378
|
replyToMessageId: replyOptions.replyToMessageId,
|
|
1376
1379
|
threadId: replyOptions.threadId,
|
|
1377
1380
|
})
|
|
1378
|
-
|
|
1381
|
+
log.info(TAG, `Sent media to ${msg.platform}: ${path.basename(file.path)}`)
|
|
1379
1382
|
logExecution(session.id, 'outbound', 'Connector media sent', {
|
|
1380
1383
|
agentId: agent.id,
|
|
1381
1384
|
detail: {
|
|
@@ -1386,7 +1389,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1386
1389
|
},
|
|
1387
1390
|
})
|
|
1388
1391
|
} catch (err: unknown) {
|
|
1389
|
-
|
|
1392
|
+
log.error(TAG, `Failed to send media ${path.basename(file.path)}:`, errorMessage(err))
|
|
1390
1393
|
logExecution(session.id, 'error', 'Connector media send failed', {
|
|
1391
1394
|
agentId: agent.id,
|
|
1392
1395
|
detail: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { genId } from '@/lib/id'
|
|
2
3
|
import {
|
|
3
4
|
loadConnectors, saveConnectors,
|
|
@@ -19,6 +20,8 @@ import {
|
|
|
19
20
|
} from './reconnect-state'
|
|
20
21
|
import { connectorRuntimeState, runningConnectors } from './runtime-state'
|
|
21
22
|
|
|
23
|
+
const TAG = 'connector-lifecycle'
|
|
24
|
+
|
|
22
25
|
const running = runningConnectors
|
|
23
26
|
const {
|
|
24
27
|
lastInboundChannelByConnector,
|
|
@@ -91,7 +94,7 @@ export async function getPlatform(platform: string) {
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
} catch (err: unknown) {
|
|
94
|
-
|
|
97
|
+
log.warn(TAG, `Failed to check extensions for platform "${platform}":`, errorMessage(err))
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
throw new Error(`Unknown platform: ${platform}`)
|
|
@@ -184,7 +187,7 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
|
|
|
184
187
|
const typedInstance = instance as ConnectorInstance
|
|
185
188
|
if (!typedInstance.onCrash) {
|
|
186
189
|
typedInstance.onCrash = (error: string) => {
|
|
187
|
-
|
|
190
|
+
log.warn(TAG, `onCrash fired for "${connector.name}" (${connectorId}): ${error}`)
|
|
188
191
|
running.delete(connectorId)
|
|
189
192
|
recordHealthEvent(connectorId, 'disconnected', `Crash callback: ${error}`)
|
|
190
193
|
|
|
@@ -217,7 +220,7 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
|
|
|
217
220
|
clearReconnectState(connectorId)
|
|
218
221
|
notify('connectors')
|
|
219
222
|
|
|
220
|
-
|
|
223
|
+
log.info(TAG, `Started ${connector.platform} connector: ${connector.name}`)
|
|
221
224
|
logActivity({ entityType: 'connector', entityId: connectorId, action: 'started', actor: 'system', summary: `Connector "${connector.name}" (${connector.platform}) started` })
|
|
222
225
|
recordHealthEvent(connectorId, 'started', `${connector.platform} connector "${connector.name}" started`)
|
|
223
226
|
} catch (err: unknown) {
|
|
@@ -277,7 +280,7 @@ export async function stopConnector(
|
|
|
277
280
|
notify('connectors')
|
|
278
281
|
}
|
|
279
282
|
|
|
280
|
-
|
|
283
|
+
log.info(TAG, `Stopped connector: ${connectorId}`)
|
|
281
284
|
logActivity({ entityType: 'connector', entityId: connectorId, action: 'stopped', actor: 'system', summary: `Connector stopped` })
|
|
282
285
|
recordHealthEvent(connectorId, 'stopped', `Connector stopped`)
|
|
283
286
|
}
|
|
@@ -340,10 +343,10 @@ export async function autoStartConnectors(): Promise<void> {
|
|
|
340
343
|
for (const connector of Object.values(connectors) as Connector[]) {
|
|
341
344
|
if (connector.isEnabled && !running.has(connector.id)) {
|
|
342
345
|
try {
|
|
343
|
-
|
|
346
|
+
log.info(TAG, `Auto-starting ${connector.platform} connector: ${connector.name}`)
|
|
344
347
|
await startConnector(connector.id)
|
|
345
348
|
} catch (err: unknown) {
|
|
346
|
-
|
|
349
|
+
log.error(TAG, `Failed to auto-start ${connector.name}:`, err instanceof Error ? err.message : err)
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
}
|
|
@@ -434,14 +437,14 @@ export async function checkConnectorHealth(): Promise<void> {
|
|
|
434
437
|
if (instance.isAlive()) {
|
|
435
438
|
// Connector is healthy — clear any reconnect state
|
|
436
439
|
if (connectorReconnectStateStore.has(id)) {
|
|
437
|
-
|
|
440
|
+
log.info(TAG, `Connector "${instance.connector.name}" recovered`)
|
|
438
441
|
clearReconnectState(id)
|
|
439
442
|
}
|
|
440
443
|
continue
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
// Connector is dead but still in the running Map
|
|
444
|
-
|
|
447
|
+
log.warn(TAG, `Connector "${instance.connector.name}" (${id}) isAlive=false — removing from running`)
|
|
445
448
|
recordHealthEvent(id, 'disconnected', `Connector "${instance.connector.name}" detected as dead (isAlive=false)`)
|
|
446
449
|
notifyOrchestrators(`Connector ${instance.connector.name || id} status: disconnected`, `connector-status:${id}`)
|
|
447
450
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import {
|
|
2
3
|
loadConnectors,
|
|
3
4
|
loadSession, upsertSession,
|
|
@@ -19,6 +20,8 @@ import { enqueueConnectorOutbox } from './outbox'
|
|
|
19
20
|
import { connectorRuntimeState, runningConnectors } from './runtime-state'
|
|
20
21
|
import { recordHealthEvent, startConnector } from './connector-lifecycle'
|
|
21
22
|
|
|
23
|
+
const TAG = 'connector-outbound'
|
|
24
|
+
|
|
22
25
|
const running = runningConnectors
|
|
23
26
|
const { recentOutbound } = connectorRuntimeState
|
|
24
27
|
const OUTBOUND_DEDUP_TTL_MS = 30_000
|
|
@@ -161,7 +164,7 @@ export async function sendConnectorMessage(params: {
|
|
|
161
164
|
|
|
162
165
|
// Apply NO_MESSAGE filter at the delivery layer so all outbound paths respect it
|
|
163
166
|
if ((suppressHiddenText || isNoMessage(sanitizedText)) && !params.imageUrl && !params.fileUrl && !params.mediaPath) {
|
|
164
|
-
|
|
167
|
+
log.info(TAG, 'sendConnectorMessage: NO_MESSAGE — suppressing outbound send')
|
|
165
168
|
return { connectorId, platform: connector.platform, channelId: params.channelId, suppressed: true }
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -178,7 +181,7 @@ export async function sendConnectorMessage(params: {
|
|
|
178
181
|
text: sanitizedText,
|
|
179
182
|
dedupeKey: params.dedupeKey,
|
|
180
183
|
})) {
|
|
181
|
-
|
|
184
|
+
log.info(TAG, `sendConnectorMessage: duplicate suppressed for ${connectorId}:${channelId}`)
|
|
182
185
|
return { connectorId, platform: connector.platform, channelId, suppressed: true }
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -226,7 +229,7 @@ export async function sendConnectorMessage(params: {
|
|
|
226
229
|
} catch (err: unknown) {
|
|
227
230
|
if (!isRecoverableConnectorSendError(err)) throw err
|
|
228
231
|
const errMsg = errorMessage(err)
|
|
229
|
-
|
|
232
|
+
log.warn(TAG, `Outbound send failed for ${connectorId}; attempting automatic restart`, { error: errMsg })
|
|
230
233
|
recordHealthEvent(connectorId, 'disconnected', `Outbound send failed: ${errMsg}`)
|
|
231
234
|
await startConnector(connectorId)
|
|
232
235
|
result = await sendThroughCurrentInstance()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { Client, GatewayIntentBits, Events, Partials, AttachmentBuilder } from 'discord.js'
|
|
2
3
|
import fs from 'fs'
|
|
3
4
|
import path from 'path'
|
|
@@ -8,6 +9,8 @@ import { deliverChunkedConnectorText } from './delivery'
|
|
|
8
9
|
import { downloadInboundMediaToUpload, inferInboundMediaType } from './media'
|
|
9
10
|
import { errorMessage } from '@/lib/shared-utils'
|
|
10
11
|
|
|
12
|
+
const TAG = 'discord'
|
|
13
|
+
|
|
11
14
|
function buildDiscordThreadTitle(params: {
|
|
12
15
|
threadName?: string
|
|
13
16
|
channelName?: string
|
|
@@ -89,7 +92,7 @@ async function hydrateDiscordThreadContext(message: any, inbound: InboundMessage
|
|
|
89
92
|
}].filter((entry) => entry.text.trim().length > 0)
|
|
90
93
|
}
|
|
91
94
|
} catch (err: unknown) {
|
|
92
|
-
|
|
95
|
+
log.warn(TAG, `Thread context bootstrap failed: ${errorMessage(err)}`)
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -128,7 +131,7 @@ const discord: PlatformConnector = {
|
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
client.on(Events.MessageCreate, async (message) => {
|
|
131
|
-
|
|
134
|
+
log.info(TAG, `Message from ${message.author.username} in ${message.channel.type === 1 ? 'DM' : '#' + ('name' in message.channel ? (message.channel as any).name : message.channelId)}: ${message.content.slice(0, 80)}`)
|
|
132
135
|
// Ignore bot messages
|
|
133
136
|
if (message.author.bot) return
|
|
134
137
|
|
|
@@ -155,7 +158,7 @@ const discord: PlatformConnector = {
|
|
|
155
158
|
}
|
|
156
159
|
} catch (err: unknown) {
|
|
157
160
|
const errMsg = errorMessage(err)
|
|
158
|
-
|
|
161
|
+
log.warn(TAG, `Media download failed (${attachment.name || 'file'}):`, errMsg)
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
media.push({
|
|
@@ -215,7 +218,7 @@ const discord: PlatformConnector = {
|
|
|
215
218
|
},
|
|
216
219
|
})
|
|
217
220
|
} catch (err: any) {
|
|
218
|
-
|
|
221
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
219
222
|
try {
|
|
220
223
|
await message.reply('Sorry, I encountered an error processing your message.')
|
|
221
224
|
} catch { /* ignore */ }
|
|
@@ -223,7 +226,7 @@ const discord: PlatformConnector = {
|
|
|
223
226
|
})
|
|
224
227
|
|
|
225
228
|
await client.login(botToken)
|
|
226
|
-
|
|
229
|
+
log.info(TAG, `Bot logged in as ${client.user?.tag}`)
|
|
227
230
|
|
|
228
231
|
const instance: ConnectorInstance = {
|
|
229
232
|
connector,
|
|
@@ -284,7 +287,7 @@ const discord: PlatformConnector = {
|
|
|
284
287
|
},
|
|
285
288
|
async stop() {
|
|
286
289
|
client.destroy()
|
|
287
|
-
|
|
290
|
+
log.info(TAG, 'Bot disconnected')
|
|
288
291
|
},
|
|
289
292
|
}
|
|
290
293
|
|
|
@@ -293,7 +296,7 @@ const discord: PlatformConnector = {
|
|
|
293
296
|
instance.onCrash?.('Discord session invalidated')
|
|
294
297
|
})
|
|
295
298
|
client.on('shardError', (error) => {
|
|
296
|
-
|
|
299
|
+
log.error(TAG, 'Shard error:', error.message)
|
|
297
300
|
})
|
|
298
301
|
|
|
299
302
|
return instance
|
|
@@ -5,6 +5,9 @@ import type { Connector } from '@/types'
|
|
|
5
5
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
6
6
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
7
7
|
import { errorMessage } from '@/lib/shared-utils'
|
|
8
|
+
import { log } from '@/lib/server/logger'
|
|
9
|
+
|
|
10
|
+
const TAG = 'email'
|
|
8
11
|
|
|
9
12
|
interface EmailConfig {
|
|
10
13
|
imapHost: string
|
|
@@ -79,7 +82,7 @@ const email: PlatformConnector = {
|
|
|
79
82
|
try {
|
|
80
83
|
await imap.connect()
|
|
81
84
|
connected = true
|
|
82
|
-
|
|
85
|
+
log.info(TAG, `IMAP connected to ${config.imapHost}`)
|
|
83
86
|
|
|
84
87
|
// Get the current highest UID as highwater mark (don't process old messages)
|
|
85
88
|
const lock = await imap.getMailboxLock(folder)
|
|
@@ -87,14 +90,14 @@ const email: PlatformConnector = {
|
|
|
87
90
|
const status = await imap.status(folder, { uidNext: true })
|
|
88
91
|
// uidNext is the next UID that will be assigned; current highest is uidNext - 1
|
|
89
92
|
highwaterUid = typeof status.uidNext === 'number' ? status.uidNext - 1 : 0
|
|
90
|
-
|
|
93
|
+
log.info(TAG, `Initial highwater UID: ${highwaterUid} in ${folder}`)
|
|
91
94
|
} finally {
|
|
92
95
|
lock.release()
|
|
93
96
|
}
|
|
94
97
|
} catch (err: unknown) {
|
|
95
98
|
connected = false
|
|
96
99
|
const msg = errorMessage(err)
|
|
97
|
-
|
|
100
|
+
log.error(TAG, `IMAP connection failed: ${msg}`)
|
|
98
101
|
throw err
|
|
99
102
|
}
|
|
100
103
|
}
|
|
@@ -107,7 +110,7 @@ const email: PlatformConnector = {
|
|
|
107
110
|
lock = await imap.getMailboxLock(folder)
|
|
108
111
|
} catch (err: unknown) {
|
|
109
112
|
const msg = errorMessage(err)
|
|
110
|
-
|
|
113
|
+
log.error(TAG, `Failed to acquire mailbox lock: ${msg}`)
|
|
111
114
|
connected = false
|
|
112
115
|
return
|
|
113
116
|
}
|
|
@@ -127,7 +130,7 @@ const email: PlatformConnector = {
|
|
|
127
130
|
await processMessage(msg)
|
|
128
131
|
} catch (err: unknown) {
|
|
129
132
|
const errMsg = errorMessage(err)
|
|
130
|
-
|
|
133
|
+
log.error(TAG, `Error processing message UID ${msg.uid}: ${errMsg}`)
|
|
131
134
|
}
|
|
132
135
|
if (msg.uid > highwaterUid) {
|
|
133
136
|
highwaterUid = msg.uid
|
|
@@ -137,7 +140,7 @@ const email: PlatformConnector = {
|
|
|
137
140
|
const errMsg = errorMessage(err)
|
|
138
141
|
// A fetch on an empty range can throw; that's normal
|
|
139
142
|
if (!errMsg.includes('Nothing to fetch')) {
|
|
140
|
-
|
|
143
|
+
log.error(TAG, `Poll error: ${errMsg}`)
|
|
141
144
|
}
|
|
142
145
|
} finally {
|
|
143
146
|
lock.release()
|
|
@@ -154,7 +157,7 @@ const email: PlatformConnector = {
|
|
|
154
157
|
|
|
155
158
|
// Filter by subject prefix if configured
|
|
156
159
|
if (config.subjectPrefix && !subject.startsWith(config.subjectPrefix)) {
|
|
157
|
-
|
|
160
|
+
log.info(TAG, `Skipping message from ${fromAddr} — subject "${subject}" doesn't match prefix "${config.subjectPrefix}"`)
|
|
158
161
|
return
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -166,11 +169,11 @@ const email: PlatformConnector = {
|
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
if (!bodyText.trim()) {
|
|
169
|
-
|
|
172
|
+
log.info(TAG, `Skipping empty message from ${fromAddr}`)
|
|
170
173
|
return
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
|
|
176
|
+
log.info(TAG, `New message from ${fromName} <${fromAddr}>: ${subject}`)
|
|
174
177
|
|
|
175
178
|
// Use the sender's email as channelId
|
|
176
179
|
const channelId = fromAddr
|
|
@@ -199,7 +202,7 @@ const email: PlatformConnector = {
|
|
|
199
202
|
await sendReply(channelId, reply.visibleText)
|
|
200
203
|
} catch (err: unknown) {
|
|
201
204
|
const errMsg = errorMessage(err)
|
|
202
|
-
|
|
205
|
+
log.error(TAG, `Error handling message from ${fromAddr}: ${errMsg}`)
|
|
203
206
|
}
|
|
204
207
|
}
|
|
205
208
|
|
|
@@ -222,7 +225,7 @@ const email: PlatformConnector = {
|
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
await smtp.sendMail(mailOptions)
|
|
225
|
-
|
|
228
|
+
log.info(TAG, `Reply sent to ${to}`)
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
// Connect and start polling
|
|
@@ -231,11 +234,11 @@ const email: PlatformConnector = {
|
|
|
231
234
|
pollTimer = setInterval(() => {
|
|
232
235
|
pollForNewMessages().catch((err: unknown) => {
|
|
233
236
|
const msg = errorMessage(err)
|
|
234
|
-
|
|
237
|
+
log.error(TAG, `Poll interval error: ${msg}`)
|
|
235
238
|
})
|
|
236
239
|
}, pollMs)
|
|
237
240
|
|
|
238
|
-
|
|
241
|
+
log.info(TAG, `Connector started — polling every ${config.pollIntervalSec || 60}s`)
|
|
239
242
|
|
|
240
243
|
return {
|
|
241
244
|
connector,
|
|
@@ -259,7 +262,7 @@ const email: PlatformConnector = {
|
|
|
259
262
|
// Connection may already be closed
|
|
260
263
|
}
|
|
261
264
|
connected = false
|
|
262
|
-
|
|
265
|
+
log.info(TAG, `Connector stopped`)
|
|
263
266
|
},
|
|
264
267
|
}
|
|
265
268
|
},
|