@swarmclawai/swarmclaw 1.2.0 → 1.2.2

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 (241) hide show
  1. package/README.md +19 -0
  2. package/package.json +5 -2
  3. package/skills/coding-agent/SKILL.md +111 -0
  4. package/skills/github/SKILL.md +140 -0
  5. package/skills/nano-banana-pro/SKILL.md +62 -0
  6. package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
  7. package/skills/nano-pdf/SKILL.md +53 -0
  8. package/skills/openai-image-gen/SKILL.md +78 -0
  9. package/skills/openai-image-gen/scripts/gen.py +328 -0
  10. package/skills/resourceful-problem-solving/SKILL.md +49 -0
  11. package/skills/skill-creator/SKILL.md +147 -0
  12. package/skills/skill-creator/scripts/init_skill.py +378 -0
  13. package/skills/skill-creator/scripts/quick_validate.py +159 -0
  14. package/skills/summarize/SKILL.md +77 -0
  15. package/src/app/api/auth/route.ts +20 -5
  16. package/src/app/api/chats/[id]/deploy/route.ts +11 -6
  17. package/src/app/api/chats/[id]/devserver/route.ts +17 -20
  18. package/src/app/api/chats/[id]/messages/route.ts +15 -11
  19. package/src/app/api/chats/[id]/route.ts +9 -10
  20. package/src/app/api/chats/[id]/stop/route.ts +5 -7
  21. package/src/app/api/chats/messages-route.test.ts +8 -6
  22. package/src/app/api/chats/route.ts +9 -10
  23. package/src/app/api/credentials/[id]/route.ts +4 -1
  24. package/src/app/api/extensions/marketplace/route.ts +5 -2
  25. package/src/app/api/ip/route.ts +2 -2
  26. package/src/app/api/memory/maintenance/route.ts +5 -2
  27. package/src/app/api/preview-server/route.ts +15 -12
  28. package/src/app/api/projects/[id]/route.ts +7 -46
  29. package/src/app/api/system/status/route.ts +11 -0
  30. package/src/app/api/upload/route.ts +4 -1
  31. package/src/cli/index.js +7 -0
  32. package/src/cli/spec.js +1 -0
  33. package/src/components/agents/agent-files-editor.tsx +44 -32
  34. package/src/components/agents/personality-builder.tsx +13 -7
  35. package/src/components/agents/trash-list.tsx +1 -1
  36. package/src/components/chat/chat-area.tsx +45 -23
  37. package/src/components/chat/message-bubble.test.ts +35 -0
  38. package/src/components/chat/message-bubble.tsx +20 -9
  39. package/src/components/chat/message-list.tsx +62 -42
  40. package/src/components/chat/swarm-status-card.tsx +10 -3
  41. package/src/components/input/chat-input.tsx +34 -14
  42. package/src/components/layout/daemon-indicator.tsx +7 -8
  43. package/src/components/layout/update-banner.tsx +8 -13
  44. package/src/components/logs/log-list.tsx +1 -1
  45. package/src/components/memory/memory-card.tsx +3 -1
  46. package/src/components/org-chart/org-chart-view.tsx +4 -0
  47. package/src/components/projects/project-list.tsx +4 -2
  48. package/src/components/projects/tabs/overview-tab.tsx +3 -2
  49. package/src/components/secrets/secret-sheet.tsx +1 -1
  50. package/src/components/secrets/secrets-list.tsx +1 -1
  51. package/src/components/shared/agent-switch-dialog.tsx +12 -6
  52. package/src/components/shared/dir-browser.tsx +22 -18
  53. package/src/components/skills/skill-sheet.tsx +2 -3
  54. package/src/components/tasks/task-list.tsx +1 -1
  55. package/src/components/tasks/task-sheet.tsx +1 -1
  56. package/src/hooks/use-openclaw-gateway.ts +46 -27
  57. package/src/instrumentation.ts +10 -7
  58. package/src/lib/chat/assistant-render-id.ts +3 -0
  59. package/src/lib/chat/chat-streaming-state.test.ts +42 -3
  60. package/src/lib/chat/chat-streaming-state.ts +20 -8
  61. package/src/lib/chat/chat.ts +18 -2
  62. package/src/lib/chat/queued-message-queue.test.ts +23 -1
  63. package/src/lib/chat/queued-message-queue.ts +11 -2
  64. package/src/lib/providers/anthropic.ts +6 -3
  65. package/src/lib/providers/claude-cli.ts +9 -3
  66. package/src/lib/providers/cli-utils.test.ts +124 -0
  67. package/src/lib/providers/cli-utils.ts +15 -0
  68. package/src/lib/providers/codex-cli.ts +9 -3
  69. package/src/lib/providers/gemini-cli.ts +6 -2
  70. package/src/lib/providers/index.ts +4 -1
  71. package/src/lib/providers/ollama.ts +5 -2
  72. package/src/lib/providers/openai.ts +8 -5
  73. package/src/lib/providers/opencode-cli.ts +6 -2
  74. package/src/lib/server/activity/activity-log.ts +21 -0
  75. package/src/lib/server/agents/agent-availability.test.ts +10 -5
  76. package/src/lib/server/agents/agent-cascade.ts +79 -59
  77. package/src/lib/server/agents/agent-registry.ts +23 -4
  78. package/src/lib/server/agents/agent-repository.ts +90 -0
  79. package/src/lib/server/agents/delegation-job-repository.ts +53 -0
  80. package/src/lib/server/agents/delegation-jobs.ts +11 -4
  81. package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
  82. package/src/lib/server/agents/guardian.ts +2 -2
  83. package/src/lib/server/agents/main-agent-loop.ts +14 -6
  84. package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
  85. package/src/lib/server/agents/subagent-runtime.ts +9 -6
  86. package/src/lib/server/agents/subagent-swarm.ts +3 -2
  87. package/src/lib/server/agents/task-session.ts +3 -4
  88. package/src/lib/server/approvals/approval-repository.ts +30 -0
  89. package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
  90. package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
  91. package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
  92. package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
  93. package/src/lib/server/chat-execution/chat-execution.ts +84 -1914
  94. package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
  95. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
  96. package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
  97. package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
  98. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
  99. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
  100. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
  101. package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
  102. package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
  103. package/src/lib/server/chat-execution/message-classifier.ts +5 -2
  104. package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
  105. package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
  106. package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
  107. package/src/lib/server/chat-execution/response-completeness.ts +5 -2
  108. package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
  109. package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
  110. package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
  111. package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
  112. package/src/lib/server/connectors/bluebubbles.ts +7 -4
  113. package/src/lib/server/connectors/connector-inbound.ts +16 -13
  114. package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
  115. package/src/lib/server/connectors/connector-outbound.ts +6 -3
  116. package/src/lib/server/connectors/connector-repository.ts +58 -0
  117. package/src/lib/server/connectors/discord.ts +10 -7
  118. package/src/lib/server/connectors/email.ts +17 -14
  119. package/src/lib/server/connectors/googlechat.ts +7 -4
  120. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
  121. package/src/lib/server/connectors/matrix.ts +6 -3
  122. package/src/lib/server/connectors/openclaw.ts +20 -17
  123. package/src/lib/server/connectors/outbox.ts +4 -1
  124. package/src/lib/server/connectors/runtime-state.test.ts +117 -0
  125. package/src/lib/server/connectors/runtime-state.ts +19 -0
  126. package/src/lib/server/connectors/session-consolidation.ts +5 -2
  127. package/src/lib/server/connectors/signal.ts +9 -6
  128. package/src/lib/server/connectors/slack.ts +13 -10
  129. package/src/lib/server/connectors/teams.ts +8 -5
  130. package/src/lib/server/connectors/telegram.ts +15 -12
  131. package/src/lib/server/connectors/whatsapp.ts +32 -29
  132. package/src/lib/server/credentials/credential-repository.ts +7 -0
  133. package/src/lib/server/embeddings.ts +4 -1
  134. package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
  135. package/src/lib/server/link-understanding.ts +4 -1
  136. package/src/lib/server/memory/memory-abstract.test.ts +59 -0
  137. package/src/lib/server/memory/memory-abstract.ts +59 -0
  138. package/src/lib/server/memory/memory-db.ts +40 -14
  139. package/src/lib/server/missions/mission-repository.ts +74 -0
  140. package/src/lib/server/missions/mission-service/actions.ts +6 -0
  141. package/src/lib/server/missions/mission-service/bindings.ts +9 -0
  142. package/src/lib/server/missions/mission-service/context.ts +4 -0
  143. package/src/lib/server/missions/mission-service/core.ts +2269 -0
  144. package/src/lib/server/missions/mission-service/queries.ts +12 -0
  145. package/src/lib/server/missions/mission-service/recovery.ts +5 -0
  146. package/src/lib/server/missions/mission-service/ticks.ts +9 -0
  147. package/src/lib/server/missions/mission-service.test.ts +9 -2
  148. package/src/lib/server/missions/mission-service.ts +6 -2263
  149. package/src/lib/server/openclaw/gateway.ts +8 -5
  150. package/src/lib/server/persistence/repository-utils.ts +154 -0
  151. package/src/lib/server/persistence/storage-context.ts +51 -0
  152. package/src/lib/server/persistence/transaction.ts +1 -0
  153. package/src/lib/server/project-utils.ts +13 -0
  154. package/src/lib/server/projects/project-repository.ts +36 -0
  155. package/src/lib/server/projects/project-service.ts +79 -0
  156. package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
  157. package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
  158. package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
  159. package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
  160. package/src/lib/server/provider-health.ts +18 -0
  161. package/src/lib/server/query-expansion.ts +4 -1
  162. package/src/lib/server/runtime/alert-dispatch.ts +8 -7
  163. package/src/lib/server/runtime/daemon-policy.ts +1 -1
  164. package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
  165. package/src/lib/server/runtime/daemon-state/health.ts +6 -0
  166. package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
  167. package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
  168. package/src/lib/server/runtime/daemon-state.test.ts +48 -0
  169. package/src/lib/server/runtime/daemon-state.ts +3 -1331
  170. package/src/lib/server/runtime/estop-repository.ts +4 -0
  171. package/src/lib/server/runtime/estop.ts +3 -1
  172. package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
  173. package/src/lib/server/runtime/heartbeat-service.ts +78 -34
  174. package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
  175. package/src/lib/server/runtime/idle-window.ts +6 -3
  176. package/src/lib/server/runtime/network.ts +11 -0
  177. package/src/lib/server/runtime/orchestrator-events.ts +2 -2
  178. package/src/lib/server/runtime/perf.ts +4 -1
  179. package/src/lib/server/runtime/process-manager.ts +7 -4
  180. package/src/lib/server/runtime/queue/claims.ts +4 -0
  181. package/src/lib/server/runtime/queue/core.ts +2079 -0
  182. package/src/lib/server/runtime/queue/execution.ts +7 -0
  183. package/src/lib/server/runtime/queue/followups.ts +4 -0
  184. package/src/lib/server/runtime/queue/queries.ts +12 -0
  185. package/src/lib/server/runtime/queue/recovery.ts +7 -0
  186. package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
  187. package/src/lib/server/runtime/queue-repository.ts +17 -0
  188. package/src/lib/server/runtime/queue.ts +5 -2058
  189. package/src/lib/server/runtime/run-ledger.ts +6 -5
  190. package/src/lib/server/runtime/run-repository.ts +73 -0
  191. package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
  192. package/src/lib/server/runtime/runtime-settings.ts +1 -1
  193. package/src/lib/server/runtime/runtime-state.ts +99 -0
  194. package/src/lib/server/runtime/scheduler.ts +13 -8
  195. package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
  196. package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
  197. package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
  198. package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
  199. package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
  200. package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
  201. package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
  202. package/src/lib/server/runtime/session-run-manager.ts +72 -1374
  203. package/src/lib/server/runtime/watch-job-repository.ts +35 -0
  204. package/src/lib/server/runtime/watch-jobs.ts +3 -1
  205. package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
  206. package/src/lib/server/sandbox/novnc-auth.ts +10 -0
  207. package/src/lib/server/schedules/schedule-repository.ts +42 -0
  208. package/src/lib/server/session-tools/context.ts +14 -0
  209. package/src/lib/server/session-tools/discovery.ts +9 -6
  210. package/src/lib/server/session-tools/index.ts +3 -1
  211. package/src/lib/server/session-tools/platform.ts +1 -1
  212. package/src/lib/server/session-tools/subagent.ts +23 -2
  213. package/src/lib/server/session-tools/wallet.ts +4 -1
  214. package/src/lib/server/sessions/session-repository.ts +85 -0
  215. package/src/lib/server/settings/settings-repository.ts +25 -0
  216. package/src/lib/server/skills/clawhub-client.ts +4 -1
  217. package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
  218. package/src/lib/server/skills/skill-discovery.test.ts +2 -2
  219. package/src/lib/server/skills/skill-discovery.ts +2 -2
  220. package/src/lib/server/skills/skill-eligibility.ts +6 -0
  221. package/src/lib/server/skills/skill-repository.ts +14 -0
  222. package/src/lib/server/solana.ts +6 -0
  223. package/src/lib/server/storage-auth.ts +5 -5
  224. package/src/lib/server/storage-normalization.ts +4 -0
  225. package/src/lib/server/storage.ts +32 -32
  226. package/src/lib/server/tasks/task-followups.ts +4 -1
  227. package/src/lib/server/tasks/task-repository.ts +54 -0
  228. package/src/lib/server/tool-loop-detection.ts +8 -3
  229. package/src/lib/server/tool-planning.ts +226 -0
  230. package/src/lib/server/tool-retry.ts +4 -3
  231. package/src/lib/server/usage/usage-repository.ts +30 -0
  232. package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
  233. package/src/lib/server/webhooks/webhook-repository.ts +10 -0
  234. package/src/lib/server/ws-hub.ts +5 -2
  235. package/src/lib/strip-internal-metadata.test.ts +78 -37
  236. package/src/lib/strip-internal-metadata.ts +20 -6
  237. package/src/stores/use-approval-store.ts +7 -1
  238. package/src/stores/use-chat-store.test.ts +54 -0
  239. package/src/stores/use-chat-store.ts +26 -6
  240. package/src/types/index.ts +6 -0
  241. /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
@@ -3,9 +3,10 @@ import { HumanMessage, AIMessage } from '@langchain/core/messages'
3
3
  import { createReactAgent } from '@langchain/langgraph/prebuilt'
4
4
  import { MemorySaver } from '@langchain/langgraph'
5
5
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/runtime/heartbeat-defaults'
6
+ import { getAgent } from '@/lib/server/agents/agent-repository'
6
7
  import { buildSessionTools } from '@/lib/server/session-tools'
7
8
  import { buildChatModel } from '@/lib/server/build-llm'
8
- import { loadSettings, loadAgents } from '@/lib/server/storage'
9
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
9
10
  import { getExtensionManager } from '@/lib/server/extensions'
10
11
  import {
11
12
  collectCapabilityAgentContext,
@@ -28,6 +29,7 @@ import {
28
29
  buildCredentialAwarenessSection,
29
30
  } from '@/lib/server/chat-execution/prompt-sections'
30
31
 
32
+ import { log } from '@/lib/server/logger'
31
33
  import { logExecution } from '@/lib/server/execution-log'
32
34
  import { buildCurrentDateTimePromptContext } from '@/lib/server/prompt-runtime-context'
33
35
  import { expandExtensionIds } from '@/lib/server/tool-aliases'
@@ -40,6 +42,7 @@ import { routeTaskIntent } from '@/lib/server/capability-router'
40
42
  import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
41
43
  import { resolveSessionToolPolicy } from '@/lib/server/tool-capability-policy'
42
44
  import { ToolLoopTracker } from '@/lib/server/tool-loop-detection'
45
+ import { isHeartbeatSource } from '@/lib/server/runtime/heartbeat-source'
43
46
  import { isCurrentThreadRecallRequest } from '@/lib/server/memory/memory-policy'
44
47
  import {
45
48
  resolveEffectiveSessionMemoryScopeMode,
@@ -99,6 +102,8 @@ import {
99
102
  type MessageClassification,
100
103
  } from '@/lib/server/chat-execution/message-classifier'
101
104
 
105
+ const TAG = 'stream-agent-chat'
106
+
102
107
  // LangGraph unhandledRejection handler has been moved to src/instrumentation.ts
103
108
  // to avoid re-registration on every HMR reload.
104
109
 
@@ -175,6 +180,8 @@ interface StreamAgentChatOpts {
175
180
  fallbackCredentialIds?: string[]
176
181
  signal?: AbortSignal
177
182
  promptMode?: PromptMode
183
+ /** Run source (e.g. 'heartbeat', 'chat', 'scheduler') — used for heartbeat-specific tuning. */
184
+ source?: string
178
185
  }
179
186
 
180
187
  export interface StreamAgentChatResult {
@@ -207,6 +214,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
207
214
  async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
208
215
  const startTs = Date.now()
209
216
  const { session, message, imagePath, imageUrl, attachedFiles, apiKey, systemPrompt, extraSystemContext, write, history, fallbackCredentialIds, signal } = opts
217
+ const isHeartbeat = isHeartbeatSource(opts.source)
210
218
  const promptMode: PromptMode = opts.promptMode ?? resolvePromptMode(session)
211
219
  const isMinimalPrompt = promptMode === 'minimal'
212
220
  const isConnectorSession = isDirectConnectorSession(session)
@@ -221,13 +229,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
221
229
  // fallbackCredentialIds is intentionally accepted for compatibility with caller signatures.
222
230
  void fallbackCredentialIds
223
231
 
232
+ const sessionAgent = session.agentId ? getAgent(session.agentId) : null
233
+
224
234
  // Resolve agent's thinking level for provider-native params
225
235
  let agentThinkingLevel: 'minimal' | 'low' | 'medium' | 'high' | undefined
226
236
  if (session.thinkingLevel) {
227
237
  agentThinkingLevel = session.thinkingLevel
228
- } else if (session.agentId) {
229
- const agentsForThinking = loadAgents()
230
- agentThinkingLevel = agentsForThinking[session.agentId]?.thinkingLevel
238
+ } else if (sessionAgent) {
239
+ agentThinkingLevel = sessionAgent.thinkingLevel
231
240
  }
232
241
 
233
242
  const llm = buildChatModel({
@@ -313,9 +322,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
313
322
  let agentResponseStyle: 'concise' | 'normal' | 'detailed' | null = null
314
323
  let agentResponseMaxChars: number | null = null
315
324
  const activeProjectContext = resolveActiveProjectContext(session)
316
- if (session.agentId) {
317
- const agents = loadAgents()
318
- const agent = agents[session.agentId]
325
+ if (sessionAgent) {
326
+ const agent = sessionAgent
319
327
  isCoordinatorAgent = agent?.role === 'coordinator'
320
328
  agentDelegationEnabled = agent?.delegationEnabled === true
321
329
  agentDelegationTargetMode = agent?.delegationTargetMode === 'selected' ? 'selected' : 'all'
@@ -366,7 +374,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
366
374
  const extensionContextParts = await collectCapabilityAgentContext(session, sessionExtensions, message, history)
367
375
  promptParts.push(...extensionContextParts)
368
376
  } catch (err: unknown) {
369
- console.error('[stream-agent-chat] Capability context injection failed:', err instanceof Error ? err.message : String(err))
377
+ log.error(TAG, 'Capability context injection failed:', err instanceof Error ? err.message : String(err))
370
378
  }
371
379
 
372
380
  // Project context — full mode only
@@ -408,13 +416,15 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
408
416
 
409
417
  // Proactive memory recall — full mode only
410
418
  {
411
- const agents = loadAgents()
412
- const agentForMemory = session.agentId ? agents[session.agentId] : null
413
- const memoryBlock = await buildProactiveMemorySection(
414
- session, agentForMemory, message, activeProjectContext.projectRoot,
419
+ const memoryResult = await buildProactiveMemorySection(
420
+ session, sessionAgent, message, activeProjectContext.projectRoot,
415
421
  isMinimalPrompt, currentThreadRecallRequest,
416
422
  )
417
- if (memoryBlock) promptParts.push(memoryBlock)
423
+ if (memoryResult.section) promptParts.push(memoryResult.section)
424
+ // Persist injection dedup counts so repeated memories are suppressed
425
+ if (Object.keys(memoryResult.injectedIds).length > 0) {
426
+ session.injectedMemoryIds = memoryResult.injectedIds
427
+ }
418
428
  }
419
429
 
420
430
  // Goal anchor — keeps the agent focused when context is long.
@@ -429,9 +439,26 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
429
439
  const budget = isMinimalPrompt ? MINIMAL_PROMPT_BUDGET : DEFAULT_PROMPT_BUDGET
430
440
  const budgetResult = applyPromptBudget(promptParts, budget)
431
441
  if (budgetResult.truncated) {
432
- console.warn(`[stream-agent-chat] Prompt truncated: ${budgetResult.originalChars} chars → ${budget.maxTotalChars} chars (mode=${promptMode})`)
442
+ log.warn(TAG, `Prompt truncated: ${budgetResult.originalChars} chars → ${budget.maxTotalChars} chars (mode=${promptMode})`)
433
443
  } else if (isOverWarningThreshold(budgetResult.originalChars, budget)) {
434
- console.warn(`[stream-agent-chat] Prompt near budget: ${budgetResult.originalChars}/${budget.maxTotalChars} chars (mode=${promptMode})`)
444
+ log.warn(TAG, `Prompt near budget: ${budgetResult.originalChars}/${budget.maxTotalChars} chars (mode=${promptMode})`)
445
+ }
446
+ // Emit prompt section telemetry (no-op when perf disabled)
447
+ if (perf.isEnabled()) {
448
+ const sectionSizes: Record<string, number> = {}
449
+ for (let i = 0; i < rawPromptParts.length; i++) {
450
+ const part = rawPromptParts[i]
451
+ const headerMatch = part.match(/^##?\s+(.+?)[\n\r]/)
452
+ const label = headerMatch ? headerMatch[1].slice(0, 30) : `section_${i}`
453
+ sectionSizes[label] = (sectionSizes[label] || 0) + part.length
454
+ }
455
+ perf.start('prompt', 'budget', {
456
+ sessionId: session.id,
457
+ totalChars: budgetResult.originalChars,
458
+ budgetMax: budget.maxTotalChars,
459
+ truncated: budgetResult.truncated,
460
+ sections: sectionSizes,
461
+ })()
435
462
  }
436
463
  let prompt = budgetResult.prompt
437
464
 
@@ -443,6 +470,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
443
470
  ...(typeof settings.toolLoopFrequencyWarn === 'number' && { toolFrequencyWarn: settings.toolLoopFrequencyWarn }),
444
471
  ...(typeof settings.toolLoopFrequencyCritical === 'number' && { toolFrequencyCritical: settings.toolLoopFrequencyCritical }),
445
472
  ...(typeof settings.toolLoopCircuitBreaker === 'number' && { circuitBreaker: settings.toolLoopCircuitBreaker }),
473
+ // Heartbeat runs are brief status checks — tighten thresholds significantly
474
+ ...(isHeartbeat && { toolFrequencyWarn: 8, toolFrequencyCritical: 15 }),
446
475
  })
447
476
  const emittedPreToolWarnings = new Set<string>()
448
477
  const recursionLimit = getAgentLoopRecursionLimit(runtime)
@@ -453,14 +482,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
453
482
 
454
483
  async function buildContentForFile(filePath: string): Promise<LangChainContentPart | string | null> {
455
484
  if (!fs.existsSync(filePath)) {
456
- console.log(`[stream-agent-chat] FILE NOT FOUND: ${filePath}`)
485
+ log.info(TAG, `FILE NOT FOUND: ${filePath}`)
457
486
  return null
458
487
  }
459
488
  const name = filePath.split('/').pop() || 'file'
460
489
  if (IMAGE_EXTS.test(filePath)) {
461
490
  const buf = fs.readFileSync(filePath)
462
491
  if (buf.length === 0) {
463
- console.warn(`[stream-agent-chat] Image file is empty: ${filePath}`)
492
+ log.warn(TAG, `Image file is empty: ${filePath}`)
464
493
  return `[Attached image: ${name} — file is empty]`
465
494
  }
466
495
  const data = buf.toString('base64')
@@ -587,8 +616,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
587
616
  summarize,
588
617
  })
589
618
  effectiveHistory = result.messages
590
- console.log(
591
- `[stream-agent-chat] Auto-compacted ${session.id}: ${recentHistory.length} → ${effectiveHistory.length} msgs` +
619
+ log.info(TAG,
620
+ `Auto-compacted ${session.id}: ${recentHistory.length} → ${effectiveHistory.length} msgs` +
592
621
  ` (prompt history ${promptHistoryTokens} tokens)` +
593
622
  (result.summaryAdded ? ' (LLM summary)' : ' (sliding window fallback)'),
594
623
  )
@@ -783,7 +812,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
783
812
  // Init turn state and limits
784
813
  // -------------------------------------------------------------------------
785
814
  const state = new ChatTurnState()
786
- const limits = new ContinuationLimits(isConnectorSession)
815
+ const limits = new ContinuationLimits(isConnectorSession, isHeartbeat)
787
816
  const routingDecision = routeTaskIntent(message, sessionExtensions, null)
788
817
  const explicitRequiredToolNames = getExplicitRequiredToolNames(message, sessionExtensions)
789
818
 
@@ -896,14 +925,18 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
896
925
  && !abortController.signal.aborted)
897
926
  || (isTransientProviderError && !abortController.signal.aborted)
898
927
 
899
- const logLevel = abortController.signal.aborted ? 'warn' : 'error'
900
- console[logLevel](`[stream-agent-chat] Error in streamEvents iteration=${iteration}`, {
928
+ const logPayload = {
901
929
  errName, errMsg, errStack,
902
930
  statusCode, retryAfterMs: extractedRetryAfterMs,
903
931
  isRecursionError, isContextOverflow, isTransientAbort,
904
932
  hasToolCalls: state.hasToolCalls, fullTextLen: state.fullText.length,
905
933
  parentAborted: abortController.signal.aborted,
906
- })
934
+ }
935
+ if (abortController.signal.aborted) {
936
+ log.warn(TAG, `Error in streamEvents iteration=${iteration}`, logPayload)
937
+ } else {
938
+ log.error(TAG, `Error in streamEvents iteration=${iteration}`, logPayload)
939
+ }
907
940
 
908
941
  if (timers.requiredToolKickoffTimedOut && limits.canContinue('required_tool') && !abortController.signal.aborted) {
909
942
  const hadPartialOutput = state.fullText.length > iterationStartState.fullText.length || state.streamedToolEvents.length > iterationStartState.toolEventCount
@@ -1069,9 +1102,9 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
1069
1102
 
1070
1103
  if (!shouldContinue) break
1071
1104
 
1072
- // Reset tool loop tracker on loop_recovery so the agent gets a fresh frequency budget
1105
+ // Partial reset on loop_recovery: clear frequency counts but preserve circuit breaker + repeat history
1073
1106
  if (shouldContinue === 'loop_recovery') {
1074
- loopTracker.reset()
1107
+ loopTracker.resetFrequencyCounts()
1075
1108
  }
1076
1109
 
1077
1110
  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
  }
@@ -0,0 +1,32 @@
1
+ import type { Chatroom } from '@/types'
2
+
3
+ import {
4
+ loadChatroom as loadStoredChatroom,
5
+ loadChatrooms as loadStoredChatrooms,
6
+ saveChatrooms as saveStoredChatrooms,
7
+ upsertChatroom as upsertStoredChatroom,
8
+ } from '@/lib/server/storage'
9
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
10
+
11
+ export const chatroomRepository = createRecordRepository<Chatroom>(
12
+ 'chatrooms',
13
+ {
14
+ get(id) {
15
+ return loadStoredChatroom(id) as Chatroom | null
16
+ },
17
+ list() {
18
+ return loadStoredChatrooms() as Record<string, Chatroom>
19
+ },
20
+ upsert(id, value) {
21
+ upsertStoredChatroom(id, value as Chatroom)
22
+ },
23
+ replace(data) {
24
+ saveStoredChatrooms(data)
25
+ },
26
+ },
27
+ )
28
+
29
+ export const loadChatrooms = () => chatroomRepository.list()
30
+ export const loadChatroom = (id: string) => chatroomRepository.get(id)
31
+ export const saveChatrooms = (items: Record<string, Chatroom | Record<string, unknown>>) => chatroomRepository.replace(items as Record<string, Chatroom>)
32
+ export const upsertChatroom = (id: string, value: Chatroom | Record<string, unknown>) => chatroomRepository.upsert(id, value as Chatroom)
@@ -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()
@@ -0,0 +1,58 @@
1
+ import type { Connector } from '@/types'
2
+
3
+ import {
4
+ deleteStoredItem,
5
+ loadConnectorHealth as loadStoredConnectorHealth,
6
+ loadConnectors as loadStoredConnectors,
7
+ loadStoredItem,
8
+ saveConnectors as saveStoredConnectors,
9
+ upsertConnectorHealthEvent as upsertStoredConnectorHealthEvent,
10
+ upsertStoredItem,
11
+ } from '@/lib/server/storage'
12
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
13
+
14
+ export const connectorRepository = createRecordRepository<Connector>(
15
+ 'connectors',
16
+ {
17
+ get(id) {
18
+ return loadStoredItem('connectors', id) as Connector | null
19
+ },
20
+ list() {
21
+ return loadStoredConnectors() as Record<string, Connector>
22
+ },
23
+ upsert(id, value) {
24
+ upsertStoredItem('connectors', id, value)
25
+ },
26
+ replace(data) {
27
+ saveStoredConnectors(data)
28
+ },
29
+ patch(id, updater) {
30
+ const current = loadStoredItem('connectors', id) as Connector | null
31
+ const next = updater(current)
32
+ if (next === null) {
33
+ deleteStoredItem('connectors', id)
34
+ return null
35
+ }
36
+ upsertStoredItem('connectors', id, next)
37
+ return next
38
+ },
39
+ delete(id) {
40
+ deleteStoredItem('connectors', id)
41
+ },
42
+ },
43
+ )
44
+
45
+ export const loadConnectors = () => connectorRepository.list()
46
+ export const loadConnector = (id: string) => connectorRepository.get(id)
47
+ export const saveConnectors = (items: Record<string, Connector | Record<string, unknown>>) => connectorRepository.replace(items as Record<string, Connector>)
48
+ export const upsertConnector = (id: string, value: Connector | Record<string, unknown>) => connectorRepository.upsert(id, value as Connector)
49
+ export const patchConnector = (id: string, updater: (current: Connector | null) => Connector | null) => connectorRepository.patch(id, updater)
50
+ export const deleteConnector = (id: string) => connectorRepository.delete(id)
51
+
52
+ export function loadConnectorHealth() {
53
+ return loadStoredConnectorHealth()
54
+ }
55
+
56
+ export function upsertConnectorHealthEvent(id: string, value: Record<string, unknown>) {
57
+ upsertStoredConnectorHealthEvent(id, value)
58
+ }