@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
@@ -1,5 +1,5 @@
1
1
  import makeWASocket, {
2
- useMultiFileAuthState,
2
+ useMultiFileAuthState as createMultiFileAuthState,
3
3
  DisconnectReason,
4
4
  fetchLatestBaileysVersion,
5
5
  normalizeMessageContent,
@@ -22,6 +22,9 @@ import { getWhatsAppApprovedSenderIds } from './pairing'
22
22
 
23
23
  import { DATA_DIR } from '../data-dir'
24
24
  import { loadConnectors, loadSettings } from '../storage'
25
+ import { log } from '@/lib/server/logger'
26
+
27
+ const TAG = 'whatsapp'
25
28
 
26
29
  const AUTH_DIR = path.join(DATA_DIR, 'whatsapp-auth')
27
30
  const INBOUND_DEDUPE_TTL_MS = 2 * 60 * 1000
@@ -210,7 +213,7 @@ function transcodeToWhatsAppVoiceNote(params: {
210
213
  })
211
214
  if ((result.status ?? 1) !== 0 || !fs.existsSync(outputPath)) {
212
215
  const stderr = (result.stderr || '').trim()
213
- console.warn(`[whatsapp] Failed to transcode voice note to opus/ogg${stderr ? `: ${stderr}` : ''}`)
216
+ log.warn(TAG, `Failed to transcode voice note to opus/ogg${stderr ? `: ${stderr}` : ''}`)
214
217
  return null
215
218
  }
216
219
  return {
@@ -396,7 +399,7 @@ export function clearAuthDir(connectorId: string): void {
396
399
  const authDir = path.join(AUTH_DIR, connectorId)
397
400
  if (fs.existsSync(authDir)) {
398
401
  fs.rmSync(authDir, { recursive: true, force: true })
399
- console.log(`[whatsapp] Cleared auth state for connector ${connectorId}`)
402
+ log.info(TAG, `Cleared auth state for connector ${connectorId}`)
400
403
  }
401
404
  }
402
405
 
@@ -406,7 +409,7 @@ const whatsapp: PlatformConnector = {
406
409
  const authDir = path.join(AUTH_DIR, connector.id)
407
410
  if (!fs.existsSync(authDir)) fs.mkdirSync(authDir, { recursive: true })
408
411
 
409
- const { state, saveCreds } = await useMultiFileAuthState(authDir)
412
+ const { state, saveCreds } = await createMultiFileAuthState(authDir)
410
413
  const { version } = await fetchLatestBaileysVersion()
411
414
 
412
415
  let sock: ReturnType<typeof makeWASocket> | null = null
@@ -463,7 +466,7 @@ const whatsapp: PlatformConnector = {
463
466
  sent = await sock.sendMessage(channelId, { image: buf, caption, mimetype: mime })
464
467
  } catch (err: unknown) {
465
468
  const errMsg = errorMessage(err)
466
- console.warn(`[whatsapp] Image send failed (${errMsg}); retrying as document: ${fName}`)
469
+ log.warn(TAG, `Image send failed (${errMsg}); retrying as document: ${fName}`)
467
470
  sent = await sock.sendMessage(channelId, { document: buf, fileName: fName, mimetype: mime, caption })
468
471
  }
469
472
  } else if (isAudioMime(mime)) {
@@ -523,7 +526,7 @@ const whatsapp: PlatformConnector = {
523
526
  clearReconnectTimer()
524
527
  try { sock?.end(undefined) } catch { /* ignore */ }
525
528
  sock = null
526
- console.log(`[whatsapp] Stopped connector: ${connector.name}`)
529
+ log.info(TAG, `Stopped connector: ${connector.name}`)
527
530
  },
528
531
  }
529
532
 
@@ -545,7 +548,7 @@ const whatsapp: PlatformConnector = {
545
548
 
546
549
  const gen = ++socketGen // Capture generation for stale detection
547
550
  connectionState = 'connecting'
548
- console.log(`[whatsapp] Starting socket gen=${gen} for ${connector.name} (hasCreds=${instance.hasCredentials})`)
551
+ log.info(TAG, `Starting socket gen=${gen} for ${connector.name} (hasCreds=${instance.hasCredentials})`)
549
552
 
550
553
  sock = makeWASocket({
551
554
  version,
@@ -564,10 +567,10 @@ const whatsapp: PlatformConnector = {
564
567
 
565
568
  const { connection, lastDisconnect, qr } = update
566
569
  if (typeof connection === 'string' && connection) connectionState = connection
567
- console.log(`[whatsapp] Connection update gen=${gen}: connection=${connection}, hasQR=${!!qr}`)
570
+ log.info(TAG, `Connection update gen=${gen}: connection=${connection}, hasQR=${!!qr}`)
568
571
 
569
572
  if (qr) {
570
- console.log(`[whatsapp] QR code generated for ${connector.name}`)
573
+ log.info(TAG, `QR code generated for ${connector.name}`)
571
574
  try {
572
575
  instance.qrDataUrl = await QRCode.toDataURL(qr, {
573
576
  width: 280,
@@ -575,17 +578,17 @@ const whatsapp: PlatformConnector = {
575
578
  color: { dark: '#000000', light: '#ffffff' },
576
579
  })
577
580
  } catch (err) {
578
- console.error('[whatsapp] Failed to generate QR data URL:', err)
581
+ log.error(TAG, 'Failed to generate QR data URL:', err)
579
582
  }
580
583
  }
581
584
  if (connection === 'close') {
582
585
  instance.qrDataUrl = null
583
586
  const reason = (lastDisconnect?.error as any)?.output?.statusCode
584
- console.log(`[whatsapp] Connection closed: reason=${reason} stopped=${stopped}`)
587
+ log.info(TAG, `Connection closed: reason=${reason} stopped=${stopped}`)
585
588
 
586
589
  if (reason === DisconnectReason.loggedOut) {
587
590
  // Session invalidated — clear auth and restart to get fresh QR
588
- console.log(`[whatsapp] Logged out — clearing auth and restarting for fresh QR`)
591
+ log.info(TAG, `Logged out — clearing auth and restarting for fresh QR`)
589
592
  instance.authenticated = false
590
593
  instance.hasCredentials = false
591
594
  clearAuthDir(connector.id)
@@ -597,38 +600,38 @@ const whatsapp: PlatformConnector = {
597
600
  } else if (reason === 440) {
598
601
  // Conflict — another session replaced this one. Do NOT reconnect
599
602
  // (reconnecting would create a ping-pong loop with the other session)
600
- console.log(`[whatsapp] Session conflict (replaced by another connection) — stopping`)
603
+ log.info(TAG, `Session conflict (replaced by another connection) — stopping`)
601
604
  instance.authenticated = false
602
605
  instance.onCrash?.('Session conflict — replaced by another connection')
603
606
  } else if (!stopped) {
604
- console.log(`[whatsapp] Reconnecting in 3s...`)
607
+ log.info(TAG, `Reconnecting in 3s...`)
605
608
  scheduleReconnect(3000)
606
609
  } else {
607
- console.log(`[whatsapp] Disconnected permanently`)
610
+ log.info(TAG, `Disconnected permanently`)
608
611
  }
609
612
  } else if (connection === 'open') {
610
613
  instance.authenticated = true
611
614
  instance.hasCredentials = true
612
615
  instance.qrDataUrl = null
613
- console.log(`[whatsapp] Connected as ${sock?.user?.id}`)
616
+ log.info(TAG, `Connected as ${sock?.user?.id}`)
614
617
  }
615
618
  })
616
619
 
617
620
  sock.ev.on('messages.upsert', async (upsert) => {
618
621
  const { messages, type } = upsert
619
- console.log(`[whatsapp] messages.upsert gen=${gen}: type=${type}, count=${messages.length}`)
622
+ log.info(TAG, `messages.upsert gen=${gen}: type=${type}, count=${messages.length}`)
620
623
 
621
624
  if (gen !== socketGen) {
622
- console.log(`[whatsapp] Ignoring stale socket event (gen=${gen}, current=${socketGen})`)
625
+ log.info(TAG, `Ignoring stale socket event (gen=${gen}, current=${socketGen})`)
623
626
  return
624
627
  }
625
628
  if (type !== 'notify') {
626
- console.log(`[whatsapp] Ignoring non-notify upsert type: ${type}`)
629
+ log.info(TAG, `Ignoring non-notify upsert type: ${type}`)
627
630
  return
628
631
  }
629
632
 
630
633
  for (const msg of messages) {
631
- console.log(`[whatsapp] Processing message: fromMe=${msg.key.fromMe}, jid=${msg.key.remoteJid}, hasConversation=${!!msg.message?.conversation}, hasExtended=${!!msg.message?.extendedTextMessage}`)
634
+ log.info(TAG, `Processing message: fromMe=${msg.key.fromMe}, jid=${msg.key.remoteJid}, hasConversation=${!!msg.message?.conversation}, hasExtended=${!!msg.message?.extendedTextMessage}`)
632
635
 
633
636
  if (msg.key.remoteJid === 'status@broadcast') continue
634
637
 
@@ -637,7 +640,7 @@ const whatsapp: PlatformConnector = {
637
640
  const now = Date.now()
638
641
  const seenAt = seenInboundMessageIds.get(msgId)
639
642
  if (typeof seenAt === 'number' && now - seenAt <= INBOUND_DEDUPE_TTL_MS) {
640
- console.log(`[whatsapp] Skipping duplicate inbound message id: ${msgId}`)
643
+ log.info(TAG, `Skipping duplicate inbound message id: ${msgId}`)
641
644
  continue
642
645
  }
643
646
  seenInboundMessageIds.set(msgId, now)
@@ -650,7 +653,7 @@ const whatsapp: PlatformConnector = {
650
653
 
651
654
  // Skip messages sent by the bot itself (tracked by ID to prevent infinite loops)
652
655
  if (msg.key.id && sentMessageIds.has(msg.key.id)) {
653
- console.log(`[whatsapp] Skipping own bot reply: ${msg.key.id}`)
656
+ log.info(TAG, `Skipping own bot reply: ${msg.key.id}`)
654
657
  sentMessageIds.delete(msg.key.id) // Clean up
655
658
  continue
656
659
  }
@@ -662,7 +665,7 @@ const whatsapp: PlatformConnector = {
662
665
  const myPhoneNum = sock?.user?.id?.split(':')[0] || ''
663
666
  const myLid = sock?.user?.lid?.split(':')[0] || ''
664
667
  const isSelfChat = (remoteNum === myPhoneNum) || (remoteHost === 'lid' && (myLid ? remoteNum === myLid : true))
665
- console.log(`[whatsapp] Self-chat check: remote=${remoteNum}@${remoteHost}, myPhone=${myPhoneNum}, myLid=${myLid}, isSelf=${isSelfChat}`)
668
+ log.info(TAG, `Self-chat check: remote=${remoteNum}@${remoteHost}, myPhone=${myPhoneNum}, myLid=${myLid}, isSelf=${isSelfChat}`)
666
669
  if (msg.key.fromMe && !isSelfChat) continue
667
670
 
668
671
  const jid = msg.key.remoteJid || ''
@@ -676,9 +679,9 @@ const whatsapp: PlatformConnector = {
676
679
  // Self-chat always passes the filter (it's the bot's own account)
677
680
  if (allowedJids?.length && !isSelfChat) {
678
681
  const matched = isWhatsAppInboundAllowed({ allowedJids, msg, isSelfChat })
679
- console.log(`[whatsapp] JID filter: candidates=${collectWhatsAppAddressCandidates(msg).join(',')}, allowedJids=${allowedJids.join(',')}, matched=${matched}`)
682
+ log.info(TAG, `JID filter: candidates=${collectWhatsAppAddressCandidates(msg).join(',')}, allowedJids=${allowedJids.join(',')}, matched=${matched}`)
680
683
  if (!matched) {
681
- console.log(`[whatsapp] Skipping message from non-allowed JID: ${jid}`)
684
+ log.info(TAG, `Skipping message from non-allowed JID: ${jid}`)
682
685
  continue
683
686
  }
684
687
  }
@@ -712,7 +715,7 @@ const whatsapp: PlatformConnector = {
712
715
  })
713
716
  media.push(saved)
714
717
  } catch (err: any) {
715
- console.error(`[whatsapp] Failed to decode media: ${err?.message || String(err)}`)
718
+ log.error(TAG, `Failed to decode media: ${err?.message || String(err)}`)
716
719
  media.push({
717
720
  type: mediaCandidate.kind,
718
721
  fileName: mediaCandidate.payload?.fileName || undefined,
@@ -736,7 +739,7 @@ const whatsapp: PlatformConnector = {
736
739
  }) || undefined
737
740
  }
738
741
 
739
- console.log(`[whatsapp] Message from ${inbound.senderName} (${jid}): ${inbound.text.slice(0, 80)}`)
742
+ log.info(TAG, `Message from ${inbound.senderName} (${jid}): ${inbound.text.slice(0, 80)}`)
740
743
 
741
744
  try {
742
745
  const reply = await resolveConnectorIngressReply(onMessage, inbound)
@@ -758,10 +761,10 @@ const whatsapp: PlatformConnector = {
758
761
  state: 'sent',
759
762
  })
760
763
  } catch (recordErr: unknown) {
761
- console.warn(`[whatsapp] Delivery recording failed (response already sent):`, errorMessage(recordErr))
764
+ log.warn(TAG, 'Delivery recording failed (response already sent):', errorMessage(recordErr))
762
765
  }
763
766
  } catch (err: unknown) {
764
- console.error(`[whatsapp] Error handling message:`, errorMessage(err))
767
+ log.error(TAG, 'Error handling message:', errorMessage(err))
765
768
  try {
766
769
  await sock!.sendMessage(jid, { text: 'Sorry, I encountered an error processing your message.' })
767
770
  } catch { /* ignore */ }
@@ -1,5 +1,8 @@
1
1
  import { loadSettings, loadCredentials, decryptKey } from './storage'
2
2
  import { hmrSingleton } from '@/lib/shared-utils'
3
+ import { log } from '@/lib/server/logger'
4
+
5
+ const TAG = 'embeddings'
3
6
 
4
7
  interface PipelineState {
5
8
  instance: unknown
@@ -62,7 +65,7 @@ export async function getEmbedding(text: string): Promise<number[] | null> {
62
65
  return await ollamaEmbed(text, model, settings.embeddingEndpoint)
63
66
  }
64
67
  } catch (err: unknown) {
65
- console.error(`[embeddings] Error computing embedding:`, err instanceof Error ? err.message : String(err))
68
+ log.error(TAG, 'Error computing embedding:', err instanceof Error ? err.message : String(err))
66
69
  }
67
70
 
68
71
  return null
@@ -1,6 +1,9 @@
1
1
  import * as cheerio from 'cheerio'
2
2
  import { dedup } from '@/lib/shared-utils'
3
3
  import { truncate } from './session-tools/context'
4
+ import { log } from '@/lib/server/logger'
5
+
6
+ const TAG = 'link-understanding'
4
7
 
5
8
  const BARE_LINK_RE = /https?:\/\/\S+/gi
6
9
 
@@ -48,7 +51,7 @@ export async function runLinkUnderstanding(message: string): Promise<string[]> {
48
51
  }
49
52
  } catch (err) {
50
53
  // Fail silently for link understanding — don't block the main run
51
- console.error(`Link understanding failed for ${url}:`, err)
54
+ log.error(TAG, `Link understanding failed for ${url}:`, err)
52
55
  }
53
56
  }
54
57
 
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Generates concise abstracts (~100 tokens) for memory entries.
3
+ * Inspired by OpenViking's L0/L1/L2 tiered context representations.
4
+ *
5
+ * Used in proactive recall to inject summaries instead of truncated raw content,
6
+ * reducing token waste and preserving semantic meaning.
7
+ */
8
+ import { HumanMessage } from '@langchain/core/messages'
9
+
10
+ const ABSTRACT_TIMEOUT_MS = 15_000
11
+
12
+ /**
13
+ * Generate a short abstract (~100 tokens) summarizing memory content.
14
+ * Falls back to a truncated prefix if LLM generation fails or is unavailable.
15
+ */
16
+ export async function generateAbstract(content: string, title?: string): Promise<string | null> {
17
+ if (!content || content.length <= 200) return null
18
+
19
+ try {
20
+ const { buildLLM } = await import('@/lib/server/build-llm')
21
+ const { llm } = await buildLLM()
22
+
23
+ const prompt = [
24
+ 'Summarize the following memory entry in 1-2 concise sentences (max ~100 tokens).',
25
+ 'Preserve the key facts, decisions, or conclusions. Do not add commentary.',
26
+ title ? `Title: ${title}` : '',
27
+ `Content: ${content.slice(0, 2000)}`,
28
+ ].filter(Boolean).join('\n')
29
+
30
+ const response = await Promise.race([
31
+ llm.invoke([new HumanMessage(prompt)]),
32
+ new Promise<never>((_, reject) =>
33
+ setTimeout(() => reject(new Error('abstract-timeout')), ABSTRACT_TIMEOUT_MS),
34
+ ),
35
+ ])
36
+
37
+ const text = extractText(response.content)
38
+ return text || fallbackAbstract(content)
39
+ } catch {
40
+ return fallbackAbstract(content)
41
+ }
42
+ }
43
+
44
+ function fallbackAbstract(content: string): string {
45
+ return content.slice(0, 150) + (content.length > 150 ? '...' : '')
46
+ }
47
+
48
+ function extractText(content: unknown): string {
49
+ if (typeof content === 'string') return content.trim()
50
+ if (Array.isArray(content)) {
51
+ for (const part of content) {
52
+ if (typeof part === 'string') return part.trim()
53
+ if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
54
+ return part.text.trim()
55
+ }
56
+ }
57
+ }
58
+ return ''
59
+ }
@@ -5,6 +5,7 @@ import { createHash } from 'crypto'
5
5
  import { genId } from '@/lib/id'
6
6
  import type { MemoryEntry, FileReference, MemoryImage, MemoryReference } from '@/types'
7
7
  import { getEmbedding, cosineSimilarity, serializeEmbedding, deserializeEmbedding } from '@/lib/server/embeddings'
8
+ import { hmrSingleton } from '@/lib/shared-utils'
8
9
  import { applyMMR } from '@/lib/server/mmr'
9
10
  import { calculateTemporalDecayMultiplier, isDecayExempt } from '@/lib/server/memory/temporal-decay'
10
11
  import { loadSettings } from '@/lib/server/storage'
@@ -17,9 +18,13 @@ import {
17
18
  } from '@/lib/server/memory/memory-graph'
18
19
  import { isWorkingMemoryCategory } from '@/lib/server/memory/memory-tiers'
19
20
 
21
+ import { generateAbstract } from '@/lib/server/memory/memory-abstract'
20
22
  import { DATA_DIR, MEMORY_IMAGES_DIR, WORKSPACE_DIR } from '@/lib/server/data-dir'
21
23
  import { safeJsonParse } from '@/lib/server/json-utils'
22
24
  import { tryResolvePathWithinBaseDir } from '@/lib/server/path-utils'
25
+ import { log } from '@/lib/server/logger'
26
+
27
+ const TAG = 'memory-db'
23
28
 
24
29
  const DB_PATH = path.join(DATA_DIR, 'memory.db')
25
30
  const IMAGES_DIR = MEMORY_IMAGES_DIR
@@ -207,19 +212,28 @@ function shouldSkipSearchQuery(input: string): boolean {
207
212
  }
208
213
 
209
214
  // Simple cache for query embeddings to avoid blocking
210
- const embeddingCache = new Map<string, number[]>()
215
+ const EMBEDDING_CACHE_MAX = 100
216
+ const EMBEDDING_CACHE_EVICT_TO = 80
217
+ const embeddingCache = hmrSingleton('__swarmclaw_memory_embedding_cache__', () => new Map<string, number[]>())
218
+
219
+ function evictEmbeddingCache(): void {
220
+ if (embeddingCache.size <= EMBEDDING_CACHE_MAX) return
221
+ const excess = embeddingCache.size - EMBEDDING_CACHE_EVICT_TO
222
+ const iter = embeddingCache.keys()
223
+ for (let i = 0; i < excess; i++) {
224
+ const k = iter.next().value
225
+ if (k !== undefined) embeddingCache.delete(k)
226
+ }
227
+ }
211
228
 
212
229
  function getEmbeddingSync(query: string): number[] | null {
213
230
  const cached = embeddingCache.get(query)
214
231
  if (cached) return cached
232
+ // Evict before async call to bound growth regardless of resolution
233
+ evictEmbeddingCache()
215
234
  // Kick off async computation for next time
216
235
  getEmbedding(query).then((emb) => {
217
236
  if (emb) embeddingCache.set(query, emb)
218
- // Evict old entries
219
- if (embeddingCache.size > 100) {
220
- const firstKey = embeddingCache.keys().next().value
221
- if (firstKey) embeddingCache.delete(firstKey)
222
- }
223
237
  }).catch(() => { /* ok */ })
224
238
  return null
225
239
  }
@@ -524,6 +538,7 @@ function initDb() {
524
538
  'lastAccessedAt INTEGER DEFAULT 0',
525
539
  'contentHash TEXT',
526
540
  'reinforcementCount INTEGER DEFAULT 0',
541
+ 'abstract TEXT',
527
542
  ]) {
528
543
  try { db.exec(`ALTER TABLE memories ADD COLUMN ${col}`) } catch { /* already exists */ }
529
544
  }
@@ -612,7 +627,7 @@ function initDb() {
612
627
  migrated++
613
628
  }
614
629
  if (migrated > 0) {
615
- console.log(`[memory-db] Migrated ${migrated} legacy memory row(s) to graph schema`)
630
+ log.info(TAG, `Migrated ${migrated} legacy memory row(s) to graph schema`)
616
631
  }
617
632
  })
618
633
  migrateLegacyRows()
@@ -632,7 +647,7 @@ function initDb() {
632
647
  })
633
648
  tx()
634
649
  }
635
- console.log(`[memory-db] Backfilled contentHash for ${backfillRows.length} memory row(s)`)
650
+ log.info(TAG, `Backfilled contentHash for ${backfillRows.length} memory row(s)`)
636
651
  }
637
652
 
638
653
  // Fresh installs now start with an empty memory graph.
@@ -642,9 +657,9 @@ function initDb() {
642
657
  insert: db.prepare(`
643
658
  INSERT INTO memories (
644
659
  id, agentId, sessionId, category, title, content, metadata, embedding,
645
- "references", filePaths, image, imagePath, linkedMemoryIds, pinned, sharedWith, contentHash, createdAt, updatedAt
660
+ "references", filePaths, image, imagePath, linkedMemoryIds, pinned, sharedWith, contentHash, abstract, createdAt, updatedAt
646
661
  )
647
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
662
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
648
663
  `),
649
664
  update: db.prepare(`
650
665
  UPDATE memories
@@ -748,6 +763,7 @@ function initDb() {
748
763
  lastAccessedAt: typeof row.lastAccessedAt === 'number' ? row.lastAccessedAt : 0,
749
764
  contentHash: typeof row.contentHash === 'string' ? row.contentHash : undefined,
750
765
  reinforcementCount: typeof row.reinforcementCount === 'number' ? row.reinforcementCount : 0,
766
+ abstract: typeof row.abstract === 'string' ? row.abstract : null,
751
767
  createdAt: typeof row.createdAt === 'number' ? row.createdAt : Date.now(),
752
768
  updatedAt: typeof row.updatedAt === 'number' ? row.updatedAt : Date.now(),
753
769
  }
@@ -819,6 +835,7 @@ function initDb() {
819
835
  pinned,
820
836
  sharedWith,
821
837
  contentHash,
838
+ null, // abstract computed async
822
839
  now, now,
823
840
  )
824
841
  // Compute embedding in background (fire-and-forget)
@@ -829,7 +846,16 @@ function initDb() {
829
846
  serializeEmbedding(emb), id,
830
847
  )
831
848
  }
832
- }).catch((err: unknown) => { console.warn(`[memory-db] Embedding generation failed for memory ${id}:`, err instanceof Error ? err.message : String(err)) })
849
+ }).catch((err: unknown) => { log.warn(TAG, `Embedding generation failed for memory ${id}:`, err instanceof Error ? err.message : String(err)) })
850
+
851
+ // Generate abstract for long content in background (fire-and-forget)
852
+ if (content.length > 200) {
853
+ generateAbstract(content, title).then((abstract) => {
854
+ if (abstract) {
855
+ db.prepare(`UPDATE memories SET abstract = ? WHERE id = ?`).run(abstract, id)
856
+ }
857
+ }).catch(() => { /* non-critical */ })
858
+ }
833
859
 
834
860
  // Keep memory links bidirectional by default.
835
861
  if (linkedMemoryIds.length) this.link(id, linkedMemoryIds, true)
@@ -1086,7 +1112,7 @@ function initDb() {
1086
1112
  })
1087
1113
  }
1088
1114
  } catch (err: unknown) {
1089
- console.warn('[memory-db] Vector search failed, falling back to FTS:', err instanceof Error ? err.message : String(err))
1115
+ log.warn(TAG, 'Vector search failed, falling back to FTS:', err instanceof Error ? err.message : String(err))
1090
1116
  }
1091
1117
 
1092
1118
  // Merge: deduplicate by id
@@ -1146,8 +1172,8 @@ function initDb() {
1146
1172
 
1147
1173
  const elapsed = Date.now() - startedAt
1148
1174
  if (elapsed > 1200) {
1149
- console.warn(
1150
- `[memory-db] Slow search ${elapsed}ms (scope=${scopeMode}, rerank=${rerankMode}, rawLen=${String(query || '').length}, fts="${ftsQuery.slice(0, 180)}")`,
1175
+ log.warn(TAG,
1176
+ `Slow search ${elapsed}ms (scope=${scopeMode}, rerank=${rerankMode}, rawLen=${String(query || '').length}, fts="${ftsQuery.slice(0, 180)}")`,
1151
1177
  )
1152
1178
  }
1153
1179
  return out
@@ -1,3 +1,4 @@
1
+ import { log } from '@/lib/server/logger'
1
2
  import { genId } from '@/lib/id'
2
3
  import type {
3
4
  ApprovalRequest,
@@ -50,6 +51,8 @@ import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
50
51
  import { getSessionQueueSnapshot, listRuns } from '@/lib/server/runtime/session-run-manager'
51
52
  import { notify } from '@/lib/server/ws-hub'
52
53
 
54
+ const TAG = 'mission-service'
55
+
53
56
  function now(): number {
54
57
  return Date.now()
55
58
  }
@@ -1146,7 +1149,7 @@ export function requestMissionTick(
1146
1149
  })
1147
1150
  queueMicrotask(() => {
1148
1151
  void runMissionTick(missionId, trigger).catch((err: unknown) => {
1149
- console.warn(`[missions] mission tick failed for ${missionId}: ${errorMessage(err)}`)
1152
+ log.warn(TAG, `mission tick failed for ${missionId}: ${errorMessage(err)}`)
1150
1153
  })
1151
1154
  })
1152
1155
  return mission
@@ -1570,7 +1573,7 @@ export async function resolveMissionForTurn(params: {
1570
1573
  session: params.session,
1571
1574
  }, params.generateText ? { generateText: params.generateText } : undefined)
1572
1575
  } catch (err: unknown) {
1573
- console.warn(`[missions] resolveMissionForTurn failed for ${params.session.id}: ${errorMessage(err)}`)
1576
+ log.warn(TAG, `resolveMissionForTurn failed for ${params.session.id}: ${errorMessage(err)}`)
1574
1577
  return null
1575
1578
  }
1576
1579
 
@@ -1649,7 +1652,7 @@ export async function applyMissionOutcomeForTurn(params: {
1649
1652
  linkedTaskSummaries: taskSummaries,
1650
1653
  }, params.generateText ? { generateText: params.generateText } : undefined)
1651
1654
  } catch (err: unknown) {
1652
- console.warn(`[missions] applyMissionOutcomeForTurn failed for ${params.session.id}: ${errorMessage(err)}`)
1655
+ log.warn(TAG, `applyMissionOutcomeForTurn failed for ${params.session.id}: ${errorMessage(err)}`)
1653
1656
  return mission
1654
1657
  }
1655
1658
  if (!decision) return mission
@@ -8,6 +8,9 @@ import { getGatewayProfile, getGatewayProfiles, resolvePrimaryAgentRoute } from
8
8
  import { isAgentDisabled } from '@/lib/server/agents/agent-availability'
9
9
  import { errorMessage, hmrSingleton, jitteredBackoff } from '@/lib/shared-utils'
10
10
  import type { Agent } from '@/types'
11
+ import { log } from '@/lib/server/logger'
12
+
13
+ const TAG = 'openclaw-gateway'
11
14
 
12
15
  // --- Types ---
13
16
 
@@ -165,7 +168,7 @@ export class OpenClawGateway {
165
168
  try {
166
169
  const result = await wsConnect(this.wsUrl, this.token, true, 15_000)
167
170
  if (!result.ok || !result.ws) {
168
- console.error('[openclaw-gateway] Connect failed:', result.message)
171
+ log.error(TAG, 'Connect failed:', result.message)
169
172
  this.scheduleReconnect()
170
173
  return false
171
174
  }
@@ -173,7 +176,7 @@ export class OpenClawGateway {
173
176
  this.ws = result.ws
174
177
  this._connected = true
175
178
  this.consecutiveFailures = 0
176
- console.log('[openclaw-gateway] Connected to gateway')
179
+ log.info(TAG, 'Connected to gateway')
177
180
 
178
181
  this.ws.on('message', (data) => {
179
182
  try {
@@ -195,7 +198,7 @@ export class OpenClawGateway {
195
198
 
196
199
  return true
197
200
  } catch (err: unknown) {
198
- console.error('[openclaw-gateway] Connect error:', errorMessage(err))
201
+ log.error(TAG, 'Connect error:', errorMessage(err))
199
202
  this.scheduleReconnect()
200
203
  return false
201
204
  }
@@ -213,7 +216,7 @@ export class OpenClawGateway {
213
216
  this.ws = null
214
217
  }
215
218
  this._connected = false
216
- console.log('[openclaw-gateway] Disconnected')
219
+ log.info(TAG, 'Disconnected')
217
220
  }
218
221
 
219
222
  private scheduleReconnect() {
@@ -228,7 +231,7 @@ export class OpenClawGateway {
228
231
  this.doConnect().catch(() => {})
229
232
  }, delay)
230
233
  if (this.consecutiveFailures === 1 || this.consecutiveFailures % 5 === 0) {
231
- console.log(`[openclaw-gateway] ${this.consecutiveFailures} consecutive failure${this.consecutiveFailures > 1 ? 's' : ''}, next retry in ${Math.round(delay / 1000)}s`)
234
+ log.info(TAG, `${this.consecutiveFailures} consecutive failure${this.consecutiveFailures > 1 ? 's' : ''}, next retry in ${Math.round(delay / 1000)}s`)
232
235
  }
233
236
  }
234
237
 
@@ -93,6 +93,8 @@ export interface ProjectResourceSummary {
93
93
  openTaskCount: number
94
94
  queuedTaskCount: number
95
95
  runningTaskCount: number
96
+ failedTaskCount: number
97
+ staleTaskCount: number
96
98
  activeScheduleCount: number
97
99
  secretCount: number
98
100
  skillCount: number
@@ -118,6 +120,15 @@ export function summarizeProjectResources(projectId: string): ProjectResourceSum
118
120
  const openTasks = tasks
119
121
  .filter((task) => ['backlog', 'queued', 'running'].includes(String(task.status || '').toLowerCase()))
120
122
  .sort(byUpdatedDesc)
123
+ const failedTasks = tasks.filter((task) => String(task.status || '').toLowerCase() === 'failed')
124
+ const now = Date.now()
125
+ const staleDays = 3 * 24 * 60 * 60 * 1000
126
+ const staleTasks = tasks.filter((task) => {
127
+ const status = String(task.status || '').toLowerCase()
128
+ if (['completed', 'cancelled', 'archived'].includes(status)) return false
129
+ const lastUpdate = Number(task.updatedAt || task.createdAt || 0)
130
+ return lastUpdate > 0 && (now - lastUpdate) > staleDays
131
+ })
121
132
  const activeSchedules = schedules
122
133
  .filter((schedule) => String(schedule.status || '').toLowerCase() === 'active')
123
134
  .sort(byUpdatedDesc)
@@ -129,6 +140,8 @@ export function summarizeProjectResources(projectId: string): ProjectResourceSum
129
140
  openTaskCount: openTasks.length,
130
141
  queuedTaskCount: openTasks.filter((task) => task.status === 'queued').length,
131
142
  runningTaskCount: openTasks.filter((task) => task.status === 'running').length,
143
+ failedTaskCount: failedTasks.length,
144
+ staleTaskCount: staleTasks.length,
132
145
  activeScheduleCount: activeSchedules.length,
133
146
  secretCount: secrets.length,
134
147
  skillCount: skills.length,
@@ -4,7 +4,10 @@
4
4
  */
5
5
  import { HumanMessage } from '@langchain/core/messages'
6
6
  import { z } from 'zod'
7
+ import { log } from '@/lib/server/logger'
7
8
  import { genId } from '@/lib/id'
9
+
10
+ const TAG = 'protocol-agent-turn'
8
11
  import type {
9
12
  Agent,
10
13
  Chatroom,
@@ -230,7 +233,7 @@ export async function defaultExecuteAgentTurn(params: {
230
233
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
231
234
  if (attempt > 0) {
232
235
  const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1)
233
- console.warn(`[protocols] retrying agent turn for ${params.agentId} (attempt ${attempt + 1}/${MAX_RETRIES + 1}, waiting ${delay}ms)`)
236
+ log.warn(TAG, `retrying agent turn for ${params.agentId} (attempt ${attempt + 1}/${MAX_RETRIES + 1}, waiting ${delay}ms)`)
234
237
  await new Promise((resolve) => setTimeout(resolve, delay))
235
238
  }
236
239
  try {
@@ -271,7 +274,7 @@ export async function defaultExecuteAgentTurn(params: {
271
274
  const msg = errorMessage(err)
272
275
  const isRetryable = /\b(401|429|5\d{2}|timeout|ECONNR|ETIMEDOUT|ENOTFOUND|socket hang up|fetch failed)\b/i.test(msg)
273
276
  if (!isRetryable || attempt >= MAX_RETRIES) throw err
274
- console.warn(`[protocols] transient LLM error for agent ${params.agentId}: ${msg}`)
277
+ log.warn(TAG, `transient LLM error for agent ${params.agentId}: ${msg}`)
275
278
  }
276
279
  }
277
280
  throw lastError
@@ -2,7 +2,10 @@
2
2
  * Protocol run lifecycle: create/run/action, scheduling/recovery, launch helpers.
3
3
  * Groups G10 + G18 + G19 from protocol-service.ts
4
4
  */
5
+ import { log } from '@/lib/server/logger'
5
6
  import { genId } from '@/lib/id'
7
+
8
+ const TAG = 'protocol-run-lifecycle'
6
9
  import type {
7
10
  ProtocolRun,
8
11
  ProtocolRunConfig,
@@ -58,7 +61,7 @@ export function requestProtocolRunExecution(runId: string, deps?: ProtocolRunDep
58
61
  setTimeout(() => {
59
62
  void runProtocolRun(normalizedId, deps)
60
63
  .catch((err: unknown) => {
61
- console.warn(`[protocols] execution failed for ${normalizedId}: ${errorMessage(err)}`)
64
+ log.warn(TAG, `execution failed for ${normalizedId}: ${errorMessage(err)}`)
62
65
  })
63
66
  .finally(() => {
64
67
  protocolExecutionState.pendingRunIds.delete(normalizedId)
@@ -300,7 +303,7 @@ export function createProtocolRun(input: CreateProtocolRunInput, deps?: Protocol
300
303
  export async function runProtocolRun(runId: string, deps?: ProtocolRunDeps): Promise<ProtocolRun | null> {
301
304
  const release = acquireProtocolLease(runId)
302
305
  if (!release) {
303
- console.warn(`[protocols] could not acquire lease for run ${runId}, another execution may be active`)
306
+ log.warn(TAG, `could not acquire lease for run ${runId}, another execution may be active`)
304
307
  return loadProtocolRunById(runId)
305
308
  }
306
309
  try {