@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.
Files changed (123) hide show
  1. package/README.md +10 -0
  2. package/package.json +4 -1
  3. package/src/app/api/chats/[id]/deploy/route.ts +11 -6
  4. package/src/app/api/chats/[id]/devserver/route.ts +5 -2
  5. package/src/app/api/chats/[id]/messages/route.ts +7 -1
  6. package/src/app/api/credentials/[id]/route.ts +4 -1
  7. package/src/app/api/extensions/marketplace/route.ts +5 -2
  8. package/src/app/api/memory/maintenance/route.ts +5 -2
  9. package/src/app/api/preview-server/route.ts +14 -11
  10. package/src/app/api/system/status/route.ts +11 -0
  11. package/src/app/api/upload/route.ts +4 -1
  12. package/src/cli/index.js +7 -0
  13. package/src/cli/spec.js +1 -0
  14. package/src/components/agents/agent-files-editor.tsx +44 -32
  15. package/src/components/agents/personality-builder.tsx +13 -7
  16. package/src/components/agents/trash-list.tsx +1 -1
  17. package/src/components/chat/message-bubble.tsx +1 -0
  18. package/src/components/chat/message-list.tsx +25 -39
  19. package/src/components/chat/swarm-status-card.tsx +10 -3
  20. package/src/components/layout/daemon-indicator.tsx +7 -8
  21. package/src/components/layout/update-banner.tsx +8 -13
  22. package/src/components/logs/log-list.tsx +1 -1
  23. package/src/components/memory/memory-card.tsx +3 -1
  24. package/src/components/org-chart/org-chart-view.tsx +4 -0
  25. package/src/components/projects/project-list.tsx +4 -2
  26. package/src/components/projects/tabs/overview-tab.tsx +3 -2
  27. package/src/components/secrets/secret-sheet.tsx +1 -1
  28. package/src/components/secrets/secrets-list.tsx +1 -1
  29. package/src/components/shared/agent-switch-dialog.tsx +12 -6
  30. package/src/components/shared/dir-browser.tsx +22 -18
  31. package/src/components/skills/skill-sheet.tsx +2 -3
  32. package/src/components/tasks/task-list.tsx +1 -1
  33. package/src/components/tasks/task-sheet.tsx +1 -1
  34. package/src/hooks/use-openclaw-gateway.ts +46 -27
  35. package/src/instrumentation.ts +10 -7
  36. package/src/lib/chat/chat.ts +18 -2
  37. package/src/lib/providers/anthropic.ts +6 -3
  38. package/src/lib/providers/claude-cli.ts +9 -3
  39. package/src/lib/providers/cli-utils.ts +15 -0
  40. package/src/lib/providers/codex-cli.ts +9 -3
  41. package/src/lib/providers/gemini-cli.ts +6 -2
  42. package/src/lib/providers/index.ts +4 -1
  43. package/src/lib/providers/ollama.ts +5 -2
  44. package/src/lib/providers/openai.ts +8 -5
  45. package/src/lib/providers/opencode-cli.ts +6 -2
  46. package/src/lib/server/agents/agent-registry.ts +20 -3
  47. package/src/lib/server/agents/main-agent-loop.ts +4 -3
  48. package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
  49. package/src/lib/server/chat-execution/chat-execution.ts +14 -2
  50. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
  51. package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
  52. package/src/lib/server/chat-execution/message-classifier.ts +5 -2
  53. package/src/lib/server/chat-execution/post-stream-finalization.ts +4 -1
  54. package/src/lib/server/chat-execution/prompt-builder.ts +11 -1
  55. package/src/lib/server/chat-execution/prompt-sections.ts +52 -9
  56. package/src/lib/server/chat-execution/response-completeness.ts +5 -2
  57. package/src/lib/server/chat-execution/stream-agent-chat.ts +42 -12
  58. package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
  59. package/src/lib/server/connectors/bluebubbles.ts +7 -4
  60. package/src/lib/server/connectors/connector-inbound.ts +16 -13
  61. package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
  62. package/src/lib/server/connectors/connector-outbound.ts +6 -3
  63. package/src/lib/server/connectors/discord.ts +10 -7
  64. package/src/lib/server/connectors/email.ts +17 -14
  65. package/src/lib/server/connectors/googlechat.ts +7 -4
  66. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
  67. package/src/lib/server/connectors/matrix.ts +6 -3
  68. package/src/lib/server/connectors/openclaw.ts +20 -17
  69. package/src/lib/server/connectors/outbox.ts +4 -1
  70. package/src/lib/server/connectors/runtime-state.ts +19 -0
  71. package/src/lib/server/connectors/session-consolidation.ts +5 -2
  72. package/src/lib/server/connectors/signal.ts +9 -6
  73. package/src/lib/server/connectors/slack.ts +13 -10
  74. package/src/lib/server/connectors/teams.ts +8 -5
  75. package/src/lib/server/connectors/telegram.ts +15 -12
  76. package/src/lib/server/connectors/whatsapp.ts +32 -29
  77. package/src/lib/server/embeddings.ts +4 -1
  78. package/src/lib/server/link-understanding.ts +4 -1
  79. package/src/lib/server/memory/memory-abstract.ts +59 -0
  80. package/src/lib/server/memory/memory-db.ts +40 -14
  81. package/src/lib/server/missions/mission-service.ts +6 -3
  82. package/src/lib/server/openclaw/gateway.ts +8 -5
  83. package/src/lib/server/project-utils.ts +13 -0
  84. package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
  85. package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
  86. package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
  87. package/src/lib/server/provider-health.ts +18 -0
  88. package/src/lib/server/query-expansion.ts +4 -1
  89. package/src/lib/server/runtime/alert-dispatch.ts +7 -6
  90. package/src/lib/server/runtime/daemon-state.ts +189 -50
  91. package/src/lib/server/runtime/heartbeat-service.ts +23 -0
  92. package/src/lib/server/runtime/idle-window.ts +4 -1
  93. package/src/lib/server/runtime/perf.ts +4 -1
  94. package/src/lib/server/runtime/process-manager.ts +7 -4
  95. package/src/lib/server/runtime/queue.ts +31 -28
  96. package/src/lib/server/runtime/scheduler.ts +9 -6
  97. package/src/lib/server/runtime/session-run-manager.ts +3 -0
  98. package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
  99. package/src/lib/server/sandbox/novnc-auth.ts +10 -0
  100. package/src/lib/server/session-tools/context.ts +14 -0
  101. package/src/lib/server/session-tools/discovery.ts +9 -6
  102. package/src/lib/server/session-tools/index.ts +3 -1
  103. package/src/lib/server/session-tools/platform.ts +1 -1
  104. package/src/lib/server/session-tools/subagent.ts +23 -2
  105. package/src/lib/server/session-tools/wallet.ts +4 -1
  106. package/src/lib/server/skills/clawhub-client.ts +4 -1
  107. package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
  108. package/src/lib/server/skills/skill-eligibility.ts +6 -0
  109. package/src/lib/server/solana.ts +6 -0
  110. package/src/lib/server/storage-auth.ts +5 -5
  111. package/src/lib/server/storage-normalization.ts +4 -0
  112. package/src/lib/server/storage.ts +19 -8
  113. package/src/lib/server/tasks/task-followups.ts +4 -1
  114. package/src/lib/server/tool-loop-detection.ts +8 -3
  115. package/src/lib/server/tool-planning.ts +226 -0
  116. package/src/lib/server/tool-retry.ts +4 -3
  117. package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
  118. package/src/lib/server/ws-hub.ts +5 -2
  119. package/src/lib/strip-internal-metadata.test.ts +44 -4
  120. package/src/lib/strip-internal-metadata.ts +20 -6
  121. package/src/stores/use-approval-store.ts +7 -1
  122. package/src/stores/use-chat-store.ts +5 -1
  123. 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
- console.error('[stream-agent-chat] Capability context injection failed:', err instanceof Error ? err.message : String(err))
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 memoryBlock = await buildProactiveMemorySection(
420
+ const memoryResult = await buildProactiveMemorySection(
414
421
  session, agentForMemory, message, activeProjectContext.projectRoot,
415
422
  isMinimalPrompt, currentThreadRecallRequest,
416
423
  )
417
- if (memoryBlock) promptParts.push(memoryBlock)
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
- console.warn(`[stream-agent-chat] Prompt truncated: ${budgetResult.originalChars} chars → ${budget.maxTotalChars} chars (mode=${promptMode})`)
443
+ log.warn(TAG, `Prompt truncated: ${budgetResult.originalChars} chars → ${budget.maxTotalChars} chars (mode=${promptMode})`)
433
444
  } else if (isOverWarningThreshold(budgetResult.originalChars, budget)) {
434
- console.warn(`[stream-agent-chat] Prompt near budget: ${budgetResult.originalChars}/${budget.maxTotalChars} chars (mode=${promptMode})`)
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
- console.log(`[stream-agent-chat] FILE NOT FOUND: ${filePath}`)
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
- console.warn(`[stream-agent-chat] Image file is empty: ${filePath}`)
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
- console.log(
591
- `[stream-agent-chat] Auto-compacted ${session.id}: ${recentHistory.length} → ${effectiveHistory.length} msgs` +
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
- // Reset tool loop tracker on loop_recovery so the agent gets a fresh frequency budget
1102
+ // Partial reset on loop_recovery: clear frequency counts but preserve circuit breaker + repeat history
1073
1103
  if (shouldContinue === 'loop_recovery') {
1074
- loopTracker.reset()
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
- console.warn('[chatroom-memory-bridge] Failed to persist interaction memory:', err instanceof Error ? err.message : String(err))
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
- console.warn('[chatroom-memory-bridge] LLM summarization failed:', llmErr instanceof Error ? llmErr.message : String(llmErr))
132
+ log.warn(TAG, 'LLM summarization failed:', llmErr instanceof Error ? llmErr.message : String(llmErr))
130
133
  }
131
134
  } catch (err: unknown) {
132
- console.warn('[chatroom-memory-bridge] Failed to consolidate memories:', err instanceof Error ? err.message : String(err))
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
- console.log(`[bluebubbles] Connected to ${serverUrl}`)
287
- console.log(`[bluebubbles] Inbound webhook endpoint: ${DEFAULT_WEBHOOK_PATH.replace('{id}', connector.id)}`)
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
- console.log(`[bluebubbles] Connector stopped`)
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
- console.warn(`[bluebubbles] Unable to parse send response body: ${message}`)
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
- console.warn(`[connector] Debounced inbound flush failed: ${errorMessage(err)}`)
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
- console.warn(`[connector] Debounced inbound flush failed: ${errorMessage(err)}`)
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
- console.error(`[connector] Chatroom agent ${agent.name} error:`, errMsg)
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
- console.log(`[connector] Sent chatroom media to ${msg.platform}: ${path.basename(file.path)}`)
791
+ log.info(TAG, `Sent chatroom media to ${msg.platform}: ${path.basename(file.path)}`)
789
792
  } catch (err: unknown) {
790
- console.error(`[connector] Failed to send chatroom media ${path.basename(file.path)}:`, errorMessage(err))
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
- console.error('[connector] queued follow-up delivery failed:', errText)
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
- console.error('[connector] queued follow-up run failed:', errText)
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
- console.log(`[connector] Routing message to agent "${agent.name}" (${session.provider}/${session.model}), hasTools=${!!hasTools}`)
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
- console.log(`[connector] streamAgentChat returned ${result.fullText.length} chars total, ${fullText.length} chars final`)
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
- console.error(`[connector] streamAgentChat error:`, message)
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
- console.log(`[connector] Agent returned hidden control sentinel — suppressing outbound reply`)
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
- console.log(`[connector] Sent media to ${msg.platform}: ${path.basename(file.path)}`)
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
- console.error(`[connector] Failed to send media ${path.basename(file.path)}:`, errorMessage(err))
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
- console.warn(`[connector] Failed to check extensions for platform "${platform}":`, errorMessage(err))
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
- console.warn(`[connector] onCrash fired for "${connector.name}" (${connectorId}): ${error}`)
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
- console.log(`[connector] Started ${connector.platform} connector: ${connector.name}`)
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
- console.log(`[connector] Stopped connector: ${connectorId}`)
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
- console.log(`[connector] Auto-starting ${connector.platform} connector: ${connector.name}`)
346
+ log.info(TAG, `Auto-starting ${connector.platform} connector: ${connector.name}`)
344
347
  await startConnector(connector.id)
345
348
  } catch (err: unknown) {
346
- console.error(`[connector] Failed to auto-start ${connector.name}:`, err instanceof Error ? err.message : err)
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
- console.log(`[connector-health] Connector "${instance.connector.name}" recovered`)
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
- console.warn(`[connector-health] Connector "${instance.connector.name}" (${id}) isAlive=false — removing from running`)
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
- console.log(`[connector] sendConnectorMessage: NO_MESSAGE — suppressing outbound send`)
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
- console.log(`[connector] sendConnectorMessage: duplicate suppressed for ${connectorId}:${channelId}`)
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
- console.warn(`[connector] Outbound send failed for ${connectorId}; attempting automatic restart`, { error: errMsg })
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
- console.warn(`[discord] Thread context bootstrap failed: ${errorMessage(err)}`)
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
- console.log(`[discord] 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)}`)
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
- console.warn(`[discord] Media download failed (${attachment.name || 'file'}):`, errMsg)
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
- console.error(`[discord] Error handling message:`, err.message)
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
- console.log(`[discord] Bot logged in as ${client.user?.tag}`)
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
- console.log(`[discord] Bot disconnected`)
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
- console.error(`[discord] Shard error:`, error.message)
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
- console.log(`[email] IMAP connected to ${config.imapHost}`)
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
- console.log(`[email] Initial highwater UID: ${highwaterUid} in ${folder}`)
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
- console.error(`[email] IMAP connection failed: ${msg}`)
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
- console.error(`[email] Failed to acquire mailbox lock: ${msg}`)
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
- console.error(`[email] Error processing message UID ${msg.uid}: ${errMsg}`)
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
- console.error(`[email] Poll error: ${errMsg}`)
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
- console.log(`[email] Skipping message from ${fromAddr} — subject "${subject}" doesn't match prefix "${config.subjectPrefix}"`)
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
- console.log(`[email] Skipping empty message from ${fromAddr}`)
172
+ log.info(TAG, `Skipping empty message from ${fromAddr}`)
170
173
  return
171
174
  }
172
175
 
173
- console.log(`[email] New message from ${fromName} <${fromAddr}>: ${subject}`)
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
- console.error(`[email] Error handling message from ${fromAddr}: ${errMsg}`)
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
- console.log(`[email] Reply sent to ${to}`)
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
- console.error(`[email] Poll interval error: ${msg}`)
237
+ log.error(TAG, `Poll interval error: ${msg}`)
235
238
  })
236
239
  }, pollMs)
237
240
 
238
- console.log(`[email] Connector started — polling every ${config.pollIntervalSec || 60}s`)
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
- console.log(`[email] Connector stopped`)
265
+ log.info(TAG, `Connector stopped`)
263
266
  },
264
267
  }
265
268
  },