@swarmclawai/swarmclaw 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/package.json +4 -1
- package/src/app/api/chats/[id]/deploy/route.ts +11 -6
- package/src/app/api/chats/[id]/devserver/route.ts +5 -2
- package/src/app/api/chats/[id]/messages/route.ts +7 -1
- package/src/app/api/credentials/[id]/route.ts +4 -1
- package/src/app/api/extensions/marketplace/route.ts +5 -2
- package/src/app/api/memory/maintenance/route.ts +5 -2
- package/src/app/api/preview-server/route.ts +14 -11
- package/src/app/api/system/status/route.ts +11 -0
- package/src/app/api/upload/route.ts +4 -1
- package/src/cli/index.js +7 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-files-editor.tsx +44 -32
- package/src/components/agents/personality-builder.tsx +13 -7
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +1 -0
- package/src/components/chat/message-list.tsx +25 -39
- package/src/components/chat/swarm-status-card.tsx +10 -3
- package/src/components/layout/daemon-indicator.tsx +7 -8
- package/src/components/layout/update-banner.tsx +8 -13
- package/src/components/logs/log-list.tsx +1 -1
- package/src/components/memory/memory-card.tsx +3 -1
- package/src/components/org-chart/org-chart-view.tsx +4 -0
- package/src/components/projects/project-list.tsx +4 -2
- package/src/components/projects/tabs/overview-tab.tsx +3 -2
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +12 -6
- package/src/components/shared/dir-browser.tsx +22 -18
- package/src/components/skills/skill-sheet.tsx +2 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +1 -1
- package/src/hooks/use-openclaw-gateway.ts +46 -27
- package/src/instrumentation.ts +10 -7
- package/src/lib/chat/chat.ts +18 -2
- package/src/lib/providers/anthropic.ts +6 -3
- package/src/lib/providers/claude-cli.ts +9 -3
- package/src/lib/providers/cli-utils.ts +15 -0
- package/src/lib/providers/codex-cli.ts +9 -3
- package/src/lib/providers/gemini-cli.ts +6 -2
- package/src/lib/providers/index.ts +4 -1
- package/src/lib/providers/ollama.ts +5 -2
- package/src/lib/providers/openai.ts +8 -5
- package/src/lib/providers/opencode-cli.ts +6 -2
- package/src/lib/server/agents/agent-registry.ts +20 -3
- package/src/lib/server/agents/main-agent-loop.ts +4 -3
- package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
- package/src/lib/server/chat-execution/chat-execution.ts +14 -2
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
- package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
- package/src/lib/server/chat-execution/message-classifier.ts +5 -2
- package/src/lib/server/chat-execution/post-stream-finalization.ts +4 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +52 -9
- package/src/lib/server/chat-execution/response-completeness.ts +5 -2
- package/src/lib/server/chat-execution/stream-agent-chat.ts +42 -12
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
- package/src/lib/server/connectors/bluebubbles.ts +7 -4
- package/src/lib/server/connectors/connector-inbound.ts +16 -13
- package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
- package/src/lib/server/connectors/connector-outbound.ts +6 -3
- package/src/lib/server/connectors/discord.ts +10 -7
- package/src/lib/server/connectors/email.ts +17 -14
- package/src/lib/server/connectors/googlechat.ts +7 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
- package/src/lib/server/connectors/matrix.ts +6 -3
- package/src/lib/server/connectors/openclaw.ts +20 -17
- package/src/lib/server/connectors/outbox.ts +4 -1
- package/src/lib/server/connectors/runtime-state.ts +19 -0
- package/src/lib/server/connectors/session-consolidation.ts +5 -2
- package/src/lib/server/connectors/signal.ts +9 -6
- package/src/lib/server/connectors/slack.ts +13 -10
- package/src/lib/server/connectors/teams.ts +8 -5
- package/src/lib/server/connectors/telegram.ts +15 -12
- package/src/lib/server/connectors/whatsapp.ts +32 -29
- package/src/lib/server/embeddings.ts +4 -1
- package/src/lib/server/link-understanding.ts +4 -1
- package/src/lib/server/memory/memory-abstract.ts +59 -0
- package/src/lib/server/memory/memory-db.ts +40 -14
- package/src/lib/server/missions/mission-service.ts +6 -3
- package/src/lib/server/openclaw/gateway.ts +8 -5
- package/src/lib/server/project-utils.ts +13 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
- package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
- package/src/lib/server/provider-health.ts +18 -0
- package/src/lib/server/query-expansion.ts +4 -1
- package/src/lib/server/runtime/alert-dispatch.ts +7 -6
- package/src/lib/server/runtime/daemon-state.ts +189 -50
- package/src/lib/server/runtime/heartbeat-service.ts +23 -0
- package/src/lib/server/runtime/idle-window.ts +4 -1
- package/src/lib/server/runtime/perf.ts +4 -1
- package/src/lib/server/runtime/process-manager.ts +7 -4
- package/src/lib/server/runtime/queue.ts +31 -28
- package/src/lib/server/runtime/scheduler.ts +9 -6
- package/src/lib/server/runtime/session-run-manager.ts +3 -0
- package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
- package/src/lib/server/sandbox/novnc-auth.ts +10 -0
- package/src/lib/server/session-tools/context.ts +14 -0
- package/src/lib/server/session-tools/discovery.ts +9 -6
- package/src/lib/server/session-tools/index.ts +3 -1
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/subagent.ts +23 -2
- package/src/lib/server/session-tools/wallet.ts +4 -1
- package/src/lib/server/skills/clawhub-client.ts +4 -1
- package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
- package/src/lib/server/skills/skill-eligibility.ts +6 -0
- package/src/lib/server/solana.ts +6 -0
- package/src/lib/server/storage-auth.ts +5 -5
- package/src/lib/server/storage-normalization.ts +4 -0
- package/src/lib/server/storage.ts +19 -8
- package/src/lib/server/tasks/task-followups.ts +4 -1
- package/src/lib/server/tool-loop-detection.ts +8 -3
- package/src/lib/server/tool-planning.ts +226 -0
- package/src/lib/server/tool-retry.ts +4 -3
- package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
- package/src/lib/server/ws-hub.ts +5 -2
- package/src/lib/strip-internal-metadata.test.ts +44 -4
- package/src/lib/strip-internal-metadata.ts +20 -6
- package/src/stores/use-approval-store.ts +7 -1
- package/src/stores/use-chat-store.ts +5 -1
- package/src/types/index.ts +6 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
570
|
+
log.info(TAG, `Connection update gen=${gen}: connection=${connection}, hasQR=${!!qr}`)
|
|
568
571
|
|
|
569
572
|
if (qr) {
|
|
570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
607
|
+
log.info(TAG, `Reconnecting in 3s...`)
|
|
605
608
|
scheduleReconnect(3000)
|
|
606
609
|
} else {
|
|
607
|
-
|
|
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
|
-
|
|
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
|
-
|
|
622
|
+
log.info(TAG, `messages.upsert gen=${gen}: type=${type}, count=${messages.length}`)
|
|
620
623
|
|
|
621
624
|
if (gen !== socketGen) {
|
|
622
|
-
|
|
625
|
+
log.info(TAG, `Ignoring stale socket event (gen=${gen}, current=${socketGen})`)
|
|
623
626
|
return
|
|
624
627
|
}
|
|
625
628
|
if (type !== 'notify') {
|
|
626
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
682
|
+
log.info(TAG, `JID filter: candidates=${collectWhatsAppAddressCandidates(msg).join(',')}, allowedJids=${allowedJids.join(',')}, matched=${matched}`)
|
|
680
683
|
if (!matched) {
|
|
681
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
764
|
+
log.warn(TAG, 'Delivery recording failed (response already sent):', errorMessage(recordErr))
|
|
762
765
|
}
|
|
763
766
|
} catch (err: unknown) {
|
|
764
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|
|
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
|
-
|
|
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
|
-
|
|
1150
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|