@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.
- package/README.md +19 -0
- package/package.json +5 -2
- package/skills/coding-agent/SKILL.md +111 -0
- package/skills/github/SKILL.md +140 -0
- package/skills/nano-banana-pro/SKILL.md +62 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
- package/skills/nano-pdf/SKILL.md +53 -0
- package/skills/openai-image-gen/SKILL.md +78 -0
- package/skills/openai-image-gen/scripts/gen.py +328 -0
- package/skills/resourceful-problem-solving/SKILL.md +49 -0
- package/skills/skill-creator/SKILL.md +147 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/quick_validate.py +159 -0
- package/skills/summarize/SKILL.md +77 -0
- package/src/app/api/auth/route.ts +20 -5
- package/src/app/api/chats/[id]/deploy/route.ts +11 -6
- package/src/app/api/chats/[id]/devserver/route.ts +17 -20
- package/src/app/api/chats/[id]/messages/route.ts +15 -11
- package/src/app/api/chats/[id]/route.ts +9 -10
- package/src/app/api/chats/[id]/stop/route.ts +5 -7
- package/src/app/api/chats/messages-route.test.ts +8 -6
- package/src/app/api/chats/route.ts +9 -10
- package/src/app/api/credentials/[id]/route.ts +4 -1
- package/src/app/api/extensions/marketplace/route.ts +5 -2
- package/src/app/api/ip/route.ts +2 -2
- package/src/app/api/memory/maintenance/route.ts +5 -2
- package/src/app/api/preview-server/route.ts +15 -12
- package/src/app/api/projects/[id]/route.ts +7 -46
- 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/chat-area.tsx +45 -23
- package/src/components/chat/message-bubble.test.ts +35 -0
- package/src/components/chat/message-bubble.tsx +20 -9
- package/src/components/chat/message-list.tsx +62 -42
- package/src/components/chat/swarm-status-card.tsx +10 -3
- package/src/components/input/chat-input.tsx +34 -14
- 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/assistant-render-id.ts +3 -0
- package/src/lib/chat/chat-streaming-state.test.ts +42 -3
- package/src/lib/chat/chat-streaming-state.ts +20 -8
- package/src/lib/chat/chat.ts +18 -2
- package/src/lib/chat/queued-message-queue.test.ts +23 -1
- package/src/lib/chat/queued-message-queue.ts +11 -2
- package/src/lib/providers/anthropic.ts +6 -3
- package/src/lib/providers/claude-cli.ts +9 -3
- package/src/lib/providers/cli-utils.test.ts +124 -0
- 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/activity/activity-log.ts +21 -0
- package/src/lib/server/agents/agent-availability.test.ts +10 -5
- package/src/lib/server/agents/agent-cascade.ts +79 -59
- package/src/lib/server/agents/agent-registry.ts +23 -4
- package/src/lib/server/agents/agent-repository.ts +90 -0
- package/src/lib/server/agents/delegation-job-repository.ts +53 -0
- package/src/lib/server/agents/delegation-jobs.ts +11 -4
- package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
- package/src/lib/server/agents/guardian.ts +2 -2
- package/src/lib/server/agents/main-agent-loop.ts +14 -6
- package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
- package/src/lib/server/agents/subagent-runtime.ts +9 -6
- package/src/lib/server/agents/subagent-swarm.ts +3 -2
- package/src/lib/server/agents/task-session.ts +3 -4
- package/src/lib/server/approvals/approval-repository.ts +30 -0
- package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
- package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
- package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution.ts +84 -1914
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
- 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.test.ts +329 -0
- package/src/lib/server/chat-execution/message-classifier.ts +5 -2
- package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
- package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
- package/src/lib/server/chat-execution/response-completeness.ts +5 -2
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- 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/connector-repository.ts +58 -0
- 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.test.ts +117 -0
- 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/credentials/credential-repository.ts +7 -0
- package/src/lib/server/embeddings.ts +4 -1
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/link-understanding.ts +4 -1
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- 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-repository.ts +74 -0
- package/src/lib/server/missions/mission-service/actions.ts +6 -0
- package/src/lib/server/missions/mission-service/bindings.ts +9 -0
- package/src/lib/server/missions/mission-service/context.ts +4 -0
- package/src/lib/server/missions/mission-service/core.ts +2269 -0
- package/src/lib/server/missions/mission-service/queries.ts +12 -0
- package/src/lib/server/missions/mission-service/recovery.ts +5 -0
- package/src/lib/server/missions/mission-service/ticks.ts +9 -0
- package/src/lib/server/missions/mission-service.test.ts +9 -2
- package/src/lib/server/missions/mission-service.ts +6 -2263
- package/src/lib/server/openclaw/gateway.ts +8 -5
- package/src/lib/server/persistence/repository-utils.ts +154 -0
- package/src/lib/server/persistence/storage-context.ts +51 -0
- package/src/lib/server/persistence/transaction.ts +1 -0
- package/src/lib/server/project-utils.ts +13 -0
- package/src/lib/server/projects/project-repository.ts +36 -0
- package/src/lib/server/projects/project-service.ts +79 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- 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 +8 -7
- package/src/lib/server/runtime/daemon-policy.ts +1 -1
- package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
- package/src/lib/server/runtime/daemon-state/health.ts +6 -0
- package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
- package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
- package/src/lib/server/runtime/daemon-state.test.ts +48 -0
- package/src/lib/server/runtime/daemon-state.ts +3 -1331
- package/src/lib/server/runtime/estop-repository.ts +4 -0
- package/src/lib/server/runtime/estop.ts +3 -1
- package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
- package/src/lib/server/runtime/heartbeat-service.ts +78 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +6 -3
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- 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/claims.ts +4 -0
- package/src/lib/server/runtime/queue/core.ts +2079 -0
- package/src/lib/server/runtime/queue/execution.ts +7 -0
- package/src/lib/server/runtime/queue/followups.ts +4 -0
- package/src/lib/server/runtime/queue/queries.ts +12 -0
- package/src/lib/server/runtime/queue/recovery.ts +7 -0
- package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
- package/src/lib/server/runtime/queue-repository.ts +17 -0
- package/src/lib/server/runtime/queue.ts +5 -2058
- package/src/lib/server/runtime/run-ledger.ts +6 -5
- package/src/lib/server/runtime/run-repository.ts +73 -0
- package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
- package/src/lib/server/runtime/runtime-settings.ts +1 -1
- package/src/lib/server/runtime/runtime-state.ts +99 -0
- package/src/lib/server/runtime/scheduler.ts +13 -8
- package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
- package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
- package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
- package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
- package/src/lib/server/runtime/session-run-manager.ts +72 -1374
- package/src/lib/server/runtime/watch-job-repository.ts +35 -0
- package/src/lib/server/runtime/watch-jobs.ts +3 -1
- 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/schedules/schedule-repository.ts +42 -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/sessions/session-repository.ts +85 -0
- package/src/lib/server/settings/settings-repository.ts +25 -0
- 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-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-eligibility.ts +6 -0
- package/src/lib/server/skills/skill-repository.ts +14 -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 +32 -32
- package/src/lib/server/tasks/task-followups.ts +4 -1
- package/src/lib/server/tasks/task-repository.ts +54 -0
- 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/usage/usage-repository.ts +30 -0
- package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/server/ws-hub.ts +5 -2
- package/src/lib/strip-internal-metadata.test.ts +78 -37
- package/src/lib/strip-internal-metadata.ts +20 -6
- package/src/stores/use-approval-store.ts +7 -1
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +26 -6
- package/src/types/index.ts +6 -0
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { Client, GatewayIntentBits, Events, Partials, AttachmentBuilder } from 'discord.js'
|
|
2
3
|
import fs from 'fs'
|
|
3
4
|
import path from 'path'
|
|
@@ -8,6 +9,8 @@ import { deliverChunkedConnectorText } from './delivery'
|
|
|
8
9
|
import { downloadInboundMediaToUpload, inferInboundMediaType } from './media'
|
|
9
10
|
import { errorMessage } from '@/lib/shared-utils'
|
|
10
11
|
|
|
12
|
+
const TAG = 'discord'
|
|
13
|
+
|
|
11
14
|
function buildDiscordThreadTitle(params: {
|
|
12
15
|
threadName?: string
|
|
13
16
|
channelName?: string
|
|
@@ -89,7 +92,7 @@ async function hydrateDiscordThreadContext(message: any, inbound: InboundMessage
|
|
|
89
92
|
}].filter((entry) => entry.text.trim().length > 0)
|
|
90
93
|
}
|
|
91
94
|
} catch (err: unknown) {
|
|
92
|
-
|
|
95
|
+
log.warn(TAG, `Thread context bootstrap failed: ${errorMessage(err)}`)
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -128,7 +131,7 @@ const discord: PlatformConnector = {
|
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
client.on(Events.MessageCreate, async (message) => {
|
|
131
|
-
|
|
134
|
+
log.info(TAG, `Message from ${message.author.username} in ${message.channel.type === 1 ? 'DM' : '#' + ('name' in message.channel ? (message.channel as any).name : message.channelId)}: ${message.content.slice(0, 80)}`)
|
|
132
135
|
// Ignore bot messages
|
|
133
136
|
if (message.author.bot) return
|
|
134
137
|
|
|
@@ -155,7 +158,7 @@ const discord: PlatformConnector = {
|
|
|
155
158
|
}
|
|
156
159
|
} catch (err: unknown) {
|
|
157
160
|
const errMsg = errorMessage(err)
|
|
158
|
-
|
|
161
|
+
log.warn(TAG, `Media download failed (${attachment.name || 'file'}):`, errMsg)
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
media.push({
|
|
@@ -215,7 +218,7 @@ const discord: PlatformConnector = {
|
|
|
215
218
|
},
|
|
216
219
|
})
|
|
217
220
|
} catch (err: any) {
|
|
218
|
-
|
|
221
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
219
222
|
try {
|
|
220
223
|
await message.reply('Sorry, I encountered an error processing your message.')
|
|
221
224
|
} catch { /* ignore */ }
|
|
@@ -223,7 +226,7 @@ const discord: PlatformConnector = {
|
|
|
223
226
|
})
|
|
224
227
|
|
|
225
228
|
await client.login(botToken)
|
|
226
|
-
|
|
229
|
+
log.info(TAG, `Bot logged in as ${client.user?.tag}`)
|
|
227
230
|
|
|
228
231
|
const instance: ConnectorInstance = {
|
|
229
232
|
connector,
|
|
@@ -284,7 +287,7 @@ const discord: PlatformConnector = {
|
|
|
284
287
|
},
|
|
285
288
|
async stop() {
|
|
286
289
|
client.destroy()
|
|
287
|
-
|
|
290
|
+
log.info(TAG, 'Bot disconnected')
|
|
288
291
|
},
|
|
289
292
|
}
|
|
290
293
|
|
|
@@ -293,7 +296,7 @@ const discord: PlatformConnector = {
|
|
|
293
296
|
instance.onCrash?.('Discord session invalidated')
|
|
294
297
|
})
|
|
295
298
|
client.on('shardError', (error) => {
|
|
296
|
-
|
|
299
|
+
log.error(TAG, 'Shard error:', error.message)
|
|
297
300
|
})
|
|
298
301
|
|
|
299
302
|
return instance
|
|
@@ -5,6 +5,9 @@ import type { Connector } from '@/types'
|
|
|
5
5
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
6
6
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
7
7
|
import { errorMessage } from '@/lib/shared-utils'
|
|
8
|
+
import { log } from '@/lib/server/logger'
|
|
9
|
+
|
|
10
|
+
const TAG = 'email'
|
|
8
11
|
|
|
9
12
|
interface EmailConfig {
|
|
10
13
|
imapHost: string
|
|
@@ -79,7 +82,7 @@ const email: PlatformConnector = {
|
|
|
79
82
|
try {
|
|
80
83
|
await imap.connect()
|
|
81
84
|
connected = true
|
|
82
|
-
|
|
85
|
+
log.info(TAG, `IMAP connected to ${config.imapHost}`)
|
|
83
86
|
|
|
84
87
|
// Get the current highest UID as highwater mark (don't process old messages)
|
|
85
88
|
const lock = await imap.getMailboxLock(folder)
|
|
@@ -87,14 +90,14 @@ const email: PlatformConnector = {
|
|
|
87
90
|
const status = await imap.status(folder, { uidNext: true })
|
|
88
91
|
// uidNext is the next UID that will be assigned; current highest is uidNext - 1
|
|
89
92
|
highwaterUid = typeof status.uidNext === 'number' ? status.uidNext - 1 : 0
|
|
90
|
-
|
|
93
|
+
log.info(TAG, `Initial highwater UID: ${highwaterUid} in ${folder}`)
|
|
91
94
|
} finally {
|
|
92
95
|
lock.release()
|
|
93
96
|
}
|
|
94
97
|
} catch (err: unknown) {
|
|
95
98
|
connected = false
|
|
96
99
|
const msg = errorMessage(err)
|
|
97
|
-
|
|
100
|
+
log.error(TAG, `IMAP connection failed: ${msg}`)
|
|
98
101
|
throw err
|
|
99
102
|
}
|
|
100
103
|
}
|
|
@@ -107,7 +110,7 @@ const email: PlatformConnector = {
|
|
|
107
110
|
lock = await imap.getMailboxLock(folder)
|
|
108
111
|
} catch (err: unknown) {
|
|
109
112
|
const msg = errorMessage(err)
|
|
110
|
-
|
|
113
|
+
log.error(TAG, `Failed to acquire mailbox lock: ${msg}`)
|
|
111
114
|
connected = false
|
|
112
115
|
return
|
|
113
116
|
}
|
|
@@ -127,7 +130,7 @@ const email: PlatformConnector = {
|
|
|
127
130
|
await processMessage(msg)
|
|
128
131
|
} catch (err: unknown) {
|
|
129
132
|
const errMsg = errorMessage(err)
|
|
130
|
-
|
|
133
|
+
log.error(TAG, `Error processing message UID ${msg.uid}: ${errMsg}`)
|
|
131
134
|
}
|
|
132
135
|
if (msg.uid > highwaterUid) {
|
|
133
136
|
highwaterUid = msg.uid
|
|
@@ -137,7 +140,7 @@ const email: PlatformConnector = {
|
|
|
137
140
|
const errMsg = errorMessage(err)
|
|
138
141
|
// A fetch on an empty range can throw; that's normal
|
|
139
142
|
if (!errMsg.includes('Nothing to fetch')) {
|
|
140
|
-
|
|
143
|
+
log.error(TAG, `Poll error: ${errMsg}`)
|
|
141
144
|
}
|
|
142
145
|
} finally {
|
|
143
146
|
lock.release()
|
|
@@ -154,7 +157,7 @@ const email: PlatformConnector = {
|
|
|
154
157
|
|
|
155
158
|
// Filter by subject prefix if configured
|
|
156
159
|
if (config.subjectPrefix && !subject.startsWith(config.subjectPrefix)) {
|
|
157
|
-
|
|
160
|
+
log.info(TAG, `Skipping message from ${fromAddr} — subject "${subject}" doesn't match prefix "${config.subjectPrefix}"`)
|
|
158
161
|
return
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -166,11 +169,11 @@ const email: PlatformConnector = {
|
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
if (!bodyText.trim()) {
|
|
169
|
-
|
|
172
|
+
log.info(TAG, `Skipping empty message from ${fromAddr}`)
|
|
170
173
|
return
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
|
|
176
|
+
log.info(TAG, `New message from ${fromName} <${fromAddr}>: ${subject}`)
|
|
174
177
|
|
|
175
178
|
// Use the sender's email as channelId
|
|
176
179
|
const channelId = fromAddr
|
|
@@ -199,7 +202,7 @@ const email: PlatformConnector = {
|
|
|
199
202
|
await sendReply(channelId, reply.visibleText)
|
|
200
203
|
} catch (err: unknown) {
|
|
201
204
|
const errMsg = errorMessage(err)
|
|
202
|
-
|
|
205
|
+
log.error(TAG, `Error handling message from ${fromAddr}: ${errMsg}`)
|
|
203
206
|
}
|
|
204
207
|
}
|
|
205
208
|
|
|
@@ -222,7 +225,7 @@ const email: PlatformConnector = {
|
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
await smtp.sendMail(mailOptions)
|
|
225
|
-
|
|
228
|
+
log.info(TAG, `Reply sent to ${to}`)
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
// Connect and start polling
|
|
@@ -231,11 +234,11 @@ const email: PlatformConnector = {
|
|
|
231
234
|
pollTimer = setInterval(() => {
|
|
232
235
|
pollForNewMessages().catch((err: unknown) => {
|
|
233
236
|
const msg = errorMessage(err)
|
|
234
|
-
|
|
237
|
+
log.error(TAG, `Poll interval error: ${msg}`)
|
|
235
238
|
})
|
|
236
239
|
}, pollMs)
|
|
237
240
|
|
|
238
|
-
|
|
241
|
+
log.info(TAG, `Connector started — polling every ${config.pollIntervalSec || 60}s`)
|
|
239
242
|
|
|
240
243
|
return {
|
|
241
244
|
connector,
|
|
@@ -259,7 +262,7 @@ const email: PlatformConnector = {
|
|
|
259
262
|
// Connection may already be closed
|
|
260
263
|
}
|
|
261
264
|
connected = false
|
|
262
|
-
|
|
265
|
+
log.info(TAG, `Connector stopped`)
|
|
263
266
|
},
|
|
264
267
|
}
|
|
265
268
|
},
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
2
3
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
3
4
|
|
|
5
|
+
const TAG = 'googlechat'
|
|
6
|
+
|
|
4
7
|
const googlechat: PlatformConnector = {
|
|
5
8
|
async start(connector, botToken, onMessage): Promise<ConnectorInstance> {
|
|
6
9
|
const pkg = 'googleapis'
|
|
@@ -29,11 +32,11 @@ const googlechat: PlatformConnector = {
|
|
|
29
32
|
const handlerKey = `__swarmclaw_googlechat_handler_${connector.id}__`
|
|
30
33
|
let stopped = false
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
log.info(TAG, 'Bot authenticated via service account')
|
|
33
36
|
if (allowedSpaces) {
|
|
34
|
-
|
|
37
|
+
log.info(TAG, `Filtering to spaces: ${allowedSpaces.join(', ')}`)
|
|
35
38
|
}
|
|
36
|
-
|
|
39
|
+
log.info(TAG, `Inbound webhook endpoint: /api/connectors/${connector.id}/webhook`)
|
|
37
40
|
|
|
38
41
|
function cleanInboundText(raw: unknown): string {
|
|
39
42
|
const txt = typeof raw === 'string' ? raw : ''
|
|
@@ -100,7 +103,7 @@ const googlechat: PlatformConnector = {
|
|
|
100
103
|
stopped = true
|
|
101
104
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
105
|
delete (globalThis as any)[handlerKey]
|
|
103
|
-
|
|
106
|
+
log.info(TAG, 'Bot disconnected')
|
|
104
107
|
},
|
|
105
108
|
}
|
|
106
109
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import fs from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { decryptKey, loadCredentials, loadSettings } from '../storage'
|
|
@@ -5,6 +6,8 @@ import { mimeFromPath } from './media'
|
|
|
5
6
|
import type { InboundMessage, InboundMedia } from './types'
|
|
6
7
|
import { errorMessage } from '@/lib/shared-utils'
|
|
7
8
|
|
|
9
|
+
const TAG = 'audio-transcription'
|
|
10
|
+
|
|
8
11
|
const PLACEHOLDER_TEXT = new Set([
|
|
9
12
|
'',
|
|
10
13
|
'(media message)',
|
|
@@ -250,11 +253,11 @@ export async function enrichInboundMessageWithAudioTranscript(params: {
|
|
|
250
253
|
try {
|
|
251
254
|
const transcript = (await attempt.run()).replace(/\s+/g, ' ').trim()
|
|
252
255
|
if (!transcript) continue
|
|
253
|
-
|
|
256
|
+
log.info(TAG, `Inbound audio transcribed via ${attempt.provider}: ${path.basename(localPath)}`)
|
|
254
257
|
return { ...msg, text: transcript }
|
|
255
258
|
} catch (err: unknown) {
|
|
256
259
|
const reason = errorMessage(err)
|
|
257
|
-
|
|
260
|
+
log.warn(TAG, `Inbound audio transcription failed via ${attempt.provider}: ${reason}`)
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import fs from 'fs'
|
|
2
3
|
import path from 'path'
|
|
3
4
|
import { DATA_DIR } from '../data-dir'
|
|
4
5
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
5
6
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
6
7
|
|
|
8
|
+
const TAG = 'matrix'
|
|
9
|
+
|
|
7
10
|
const matrix: PlatformConnector = {
|
|
8
11
|
async start(connector, botToken, onMessage): Promise<ConnectorInstance> {
|
|
9
12
|
const pkg = 'matrix-bot-sdk'
|
|
@@ -52,7 +55,7 @@ const matrix: PlatformConnector = {
|
|
|
52
55
|
if (!reply) return
|
|
53
56
|
await client.sendText(roomId, reply.visibleText)
|
|
54
57
|
} catch (err: any) {
|
|
55
|
-
|
|
58
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
56
59
|
try {
|
|
57
60
|
await client.sendText(roomId, 'Sorry, I encountered an error processing your message.')
|
|
58
61
|
} catch { /* ignore */ }
|
|
@@ -60,7 +63,7 @@ const matrix: PlatformConnector = {
|
|
|
60
63
|
})
|
|
61
64
|
|
|
62
65
|
await client.start()
|
|
63
|
-
|
|
66
|
+
log.info(TAG, `Bot connected to ${homeserverUrl}`)
|
|
64
67
|
|
|
65
68
|
return {
|
|
66
69
|
connector,
|
|
@@ -69,7 +72,7 @@ const matrix: PlatformConnector = {
|
|
|
69
72
|
},
|
|
70
73
|
async stop() {
|
|
71
74
|
client.stop()
|
|
72
|
-
|
|
75
|
+
log.info(TAG, 'Bot disconnected')
|
|
73
76
|
},
|
|
74
77
|
}
|
|
75
78
|
},
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
type GatewayFrame,
|
|
13
13
|
type GatewayResponseFrame,
|
|
14
14
|
} from '../gateway/protocol'
|
|
15
|
+
import { log } from '@/lib/server/logger'
|
|
16
|
+
|
|
17
|
+
const TAG = 'openclaw'
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* OpenClaw gateway connector using the current WS protocol:
|
|
@@ -443,7 +446,7 @@ async function buildOutboundAttachments(options?: OutboundSendOptions): Promise<
|
|
|
443
446
|
})
|
|
444
447
|
return { attachments: [attachment], fallbackUrl: null }
|
|
445
448
|
} catch (err) {
|
|
446
|
-
|
|
449
|
+
log.warn(TAG, `Failed to inline media URL, falling back to link send: ${getErrorMessage(err)}`)
|
|
447
450
|
return { attachments: [], fallbackUrl: mediaUrl }
|
|
448
451
|
}
|
|
449
452
|
}
|
|
@@ -705,8 +708,8 @@ const openclaw: PlatformConnector = {
|
|
|
705
708
|
if (lastTickAtMs <= 0) return
|
|
706
709
|
const delta = Date.now() - lastTickAtMs
|
|
707
710
|
if (delta <= toleranceMs) return
|
|
708
|
-
|
|
709
|
-
`
|
|
711
|
+
log.error(
|
|
712
|
+
TAG, `Tick missed (${delta}ms > ${toleranceMs}ms), forcing reconnect`,
|
|
710
713
|
)
|
|
711
714
|
try { ws.close(4000, 'tick missed') } catch { /* ignore */ }
|
|
712
715
|
}, pollMs)
|
|
@@ -740,7 +743,7 @@ const openclaw: PlatformConnector = {
|
|
|
740
743
|
const previous = historyErrorLogBySession.get(sessionKey) || 0
|
|
741
744
|
if (now - previous < HISTORY_ERROR_LOG_INTERVAL_MS) return
|
|
742
745
|
historyErrorLogBySession.set(sessionKey, now)
|
|
743
|
-
|
|
746
|
+
log.warn(TAG, `chat.history poll failed for "${sessionKey}": ${message}`)
|
|
744
747
|
}
|
|
745
748
|
|
|
746
749
|
function cleanupSocket() {
|
|
@@ -764,7 +767,7 @@ const openclaw: PlatformConnector = {
|
|
|
764
767
|
if (stopped) return
|
|
765
768
|
const delay = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempt, RECONNECT_MAX_MS)
|
|
766
769
|
reconnectAttempt++
|
|
767
|
-
|
|
770
|
+
log.info(TAG, `Reconnecting in ${delay}ms (attempt ${reconnectAttempt})`)
|
|
768
771
|
clearReconnectTimer()
|
|
769
772
|
reconnectTimer = setTimeout(() => connect(), delay)
|
|
770
773
|
}
|
|
@@ -838,7 +841,7 @@ const openclaw: PlatformConnector = {
|
|
|
838
841
|
const lowerReason = (reason || '').toLowerCase()
|
|
839
842
|
if (!lowerReason.includes('device token mismatch')) return
|
|
840
843
|
if (!identity.deviceToken) return
|
|
841
|
-
|
|
844
|
+
log.warn(TAG, 'Clearing stale stored device token after mismatch')
|
|
842
845
|
persistIdentityToken(undefined)
|
|
843
846
|
}
|
|
844
847
|
|
|
@@ -918,11 +921,11 @@ const openclaw: PlatformConnector = {
|
|
|
918
921
|
}
|
|
919
922
|
if (tickWatchdogEnabled) startTickWatchdog()
|
|
920
923
|
startHistoryPoller()
|
|
921
|
-
|
|
924
|
+
log.info(TAG, `Connected + authenticated (${wsUrl})`)
|
|
922
925
|
})
|
|
923
926
|
.catch((err: unknown) => {
|
|
924
927
|
clearConnectHelloTimer()
|
|
925
|
-
|
|
928
|
+
log.error(TAG, `Connect handshake failed: ${getErrorMessage(err)}`)
|
|
926
929
|
try { ws?.close(1008, 'connect failed') } catch { /* ignore */ }
|
|
927
930
|
})
|
|
928
931
|
}
|
|
@@ -962,7 +965,7 @@ const openclaw: PlatformConnector = {
|
|
|
962
965
|
await sendChat(inbound.channelId, reply.visibleText)
|
|
963
966
|
} catch (err: unknown) {
|
|
964
967
|
const message = getErrorMessage(err)
|
|
965
|
-
|
|
968
|
+
log.error(TAG, 'Error routing inbound chat event:', message)
|
|
966
969
|
await sendChat(inbound.channelId, `[Error] ${message}`)
|
|
967
970
|
}
|
|
968
971
|
}
|
|
@@ -1025,7 +1028,7 @@ const openclaw: PlatformConnector = {
|
|
|
1025
1028
|
) {
|
|
1026
1029
|
historyPollingUnsupported = true
|
|
1027
1030
|
clearHistoryPollTimer()
|
|
1028
|
-
|
|
1031
|
+
log.warn(TAG, 'chat.history is unavailable; disabling history polling fallback')
|
|
1029
1032
|
return
|
|
1030
1033
|
}
|
|
1031
1034
|
maybeLogHistoryError(sessionKey, message)
|
|
@@ -1059,18 +1062,18 @@ const openclaw: PlatformConnector = {
|
|
|
1059
1062
|
function connect() {
|
|
1060
1063
|
if (stopped) return
|
|
1061
1064
|
cleanupSocket()
|
|
1062
|
-
|
|
1065
|
+
log.info(TAG, `Connecting to ${wsUrl}`)
|
|
1063
1066
|
ws = new WebSocket(wsUrl)
|
|
1064
1067
|
|
|
1065
1068
|
ws.onopen = () => {
|
|
1066
|
-
|
|
1069
|
+
log.info(TAG, `Socket open: ${wsUrl}`)
|
|
1067
1070
|
connectSent = false
|
|
1068
1071
|
connected = false
|
|
1069
1072
|
lastTickAtMs = 0
|
|
1070
1073
|
clearConnectHelloTimer()
|
|
1071
1074
|
connectHelloTimer = setTimeout(() => {
|
|
1072
1075
|
if (stopped || connected) return
|
|
1073
|
-
|
|
1076
|
+
log.warn(TAG, `Connect handshake timed out after ${CONNECT_HELLO_TIMEOUT_MS}ms`)
|
|
1074
1077
|
try { ws?.close(4001, 'connect timeout') } catch { /* ignore */ }
|
|
1075
1078
|
}, CONNECT_HELLO_TIMEOUT_MS)
|
|
1076
1079
|
connectHelloTimer.unref?.()
|
|
@@ -1080,7 +1083,7 @@ const openclaw: PlatformConnector = {
|
|
|
1080
1083
|
ws.onmessage = (event) => {
|
|
1081
1084
|
const frame = parseGatewayFrame(event.data)
|
|
1082
1085
|
if (!frame) {
|
|
1083
|
-
|
|
1086
|
+
log.warn(TAG, 'Ignoring malformed gateway frame')
|
|
1084
1087
|
return
|
|
1085
1088
|
}
|
|
1086
1089
|
|
|
@@ -1124,14 +1127,14 @@ const openclaw: PlatformConnector = {
|
|
|
1124
1127
|
|
|
1125
1128
|
ws.onclose = (event) => {
|
|
1126
1129
|
const reason = event.reason || 'none'
|
|
1127
|
-
|
|
1130
|
+
log.info(TAG, `Disconnected (code=${event.code}, reason=${reason})`)
|
|
1128
1131
|
clearStaleTokenIfNeeded(reason)
|
|
1129
1132
|
cleanupSocket()
|
|
1130
1133
|
if (!stopped) scheduleReconnect()
|
|
1131
1134
|
}
|
|
1132
1135
|
|
|
1133
1136
|
ws.onerror = () => {
|
|
1134
|
-
|
|
1137
|
+
log.error(TAG, 'WebSocket error')
|
|
1135
1138
|
}
|
|
1136
1139
|
}
|
|
1137
1140
|
|
|
@@ -1189,7 +1192,7 @@ const openclaw: PlatformConnector = {
|
|
|
1189
1192
|
async stop() {
|
|
1190
1193
|
stopped = true
|
|
1191
1194
|
cleanupSocket()
|
|
1192
|
-
|
|
1195
|
+
log.info(TAG, 'Connector stopped')
|
|
1193
1196
|
},
|
|
1194
1197
|
}
|
|
1195
1198
|
},
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
} from '../storage'
|
|
7
7
|
import { notify } from '../ws-hub'
|
|
8
8
|
import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
|
|
9
|
+
import { log } from '@/lib/server/logger'
|
|
10
|
+
|
|
11
|
+
const TAG = 'connector-outbox'
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
export type ConnectorOutboxStatus =
|
|
@@ -151,7 +154,7 @@ function scheduleTimer(delayMs: number): void {
|
|
|
151
154
|
outboxState.timer = null
|
|
152
155
|
outboxState.dueAt = null
|
|
153
156
|
void runConnectorOutboxNow().catch((err: unknown) => {
|
|
154
|
-
|
|
157
|
+
log.warn(TAG, `Worker tick failed: ${errorMessage(err)}`)
|
|
155
158
|
})
|
|
156
159
|
}, Math.max(0, delayMs))
|
|
157
160
|
outboxState.timer.unref?.()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it, before, after } from 'node:test'
|
|
3
|
+
|
|
4
|
+
const originalEnv = {
|
|
5
|
+
SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let mod: typeof import('@/lib/server/connectors/runtime-state')
|
|
9
|
+
|
|
10
|
+
before(async () => {
|
|
11
|
+
process.env.SWARMCLAW_BUILD_MODE = '1'
|
|
12
|
+
mod = await import('@/lib/server/connectors/runtime-state')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
after(() => {
|
|
16
|
+
if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
|
|
17
|
+
else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// getConnectorRuntimeState
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe('getConnectorRuntimeState', () => {
|
|
25
|
+
it('returns object with all expected Map keys', () => {
|
|
26
|
+
const state = mod.getConnectorRuntimeState()
|
|
27
|
+
assert.ok(state.running instanceof Map)
|
|
28
|
+
assert.ok(state.lastInboundChannelByConnector instanceof Map)
|
|
29
|
+
assert.ok(state.lastInboundTimeByConnector instanceof Map)
|
|
30
|
+
assert.ok(state.locks instanceof Map)
|
|
31
|
+
assert.ok(state.generationCounter instanceof Map)
|
|
32
|
+
assert.ok(state.scheduledFollowups instanceof Map)
|
|
33
|
+
assert.ok(state.recentInboundByKey instanceof Map)
|
|
34
|
+
assert.ok(state.pendingInboundDebounce instanceof Map)
|
|
35
|
+
assert.ok(state.scheduledFollowupByDedupe instanceof Map)
|
|
36
|
+
assert.ok(state.reconnectStates instanceof Map)
|
|
37
|
+
assert.ok(state.recentOutbound instanceof Map)
|
|
38
|
+
assert.ok(state.routeMessageHandlerRef)
|
|
39
|
+
assert.equal(typeof state.routeMessageHandlerRef.current, 'function')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('returns the same instance on repeated calls (singleton)', () => {
|
|
43
|
+
const a = mod.getConnectorRuntimeState()
|
|
44
|
+
const b = mod.getConnectorRuntimeState()
|
|
45
|
+
assert.equal(a, b)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// pruneConnectorTrackingState
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
describe('pruneConnectorTrackingState', () => {
|
|
54
|
+
it('removes stale entries and retains live ones', () => {
|
|
55
|
+
const state = mod.getConnectorRuntimeState()
|
|
56
|
+
|
|
57
|
+
// Seed tracking maps with stale + live IDs
|
|
58
|
+
state.lastInboundChannelByConnector.set('live-1', 'channel-a')
|
|
59
|
+
state.lastInboundChannelByConnector.set('stale-1', 'channel-b')
|
|
60
|
+
state.lastInboundTimeByConnector.set('live-1', Date.now())
|
|
61
|
+
state.lastInboundTimeByConnector.set('stale-2', Date.now())
|
|
62
|
+
state.generationCounter.set('live-1', 5)
|
|
63
|
+
state.generationCounter.set('stale-1', 3)
|
|
64
|
+
|
|
65
|
+
const liveIds = new Set(['live-1'])
|
|
66
|
+
const removed = mod.pruneConnectorTrackingState(liveIds)
|
|
67
|
+
|
|
68
|
+
// Verify stale entries are gone
|
|
69
|
+
assert.equal(state.lastInboundChannelByConnector.has('stale-1'), false)
|
|
70
|
+
assert.equal(state.lastInboundTimeByConnector.has('stale-2'), false)
|
|
71
|
+
assert.equal(state.generationCounter.has('stale-1'), false)
|
|
72
|
+
|
|
73
|
+
// Verify live entries are retained
|
|
74
|
+
assert.equal(state.lastInboundChannelByConnector.get('live-1'), 'channel-a')
|
|
75
|
+
assert.ok(state.lastInboundTimeByConnector.has('live-1'))
|
|
76
|
+
assert.equal(state.generationCounter.get('live-1'), 5)
|
|
77
|
+
|
|
78
|
+
// Removed count: stale-1 from channel + stale-2 from time + stale-1 from gen = 3
|
|
79
|
+
assert.equal(removed, 3)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('removes all entries when liveIds is empty', () => {
|
|
83
|
+
const state = mod.getConnectorRuntimeState()
|
|
84
|
+
|
|
85
|
+
state.lastInboundChannelByConnector.set('a', 'ch')
|
|
86
|
+
state.lastInboundTimeByConnector.set('a', 1)
|
|
87
|
+
state.generationCounter.set('a', 1)
|
|
88
|
+
|
|
89
|
+
const removed = mod.pruneConnectorTrackingState(new Set())
|
|
90
|
+
|
|
91
|
+
assert.equal(state.lastInboundChannelByConnector.size, 0)
|
|
92
|
+
assert.equal(state.lastInboundTimeByConnector.size, 0)
|
|
93
|
+
assert.equal(state.generationCounter.size, 0)
|
|
94
|
+
assert.ok(removed >= 3)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('returns 0 when all IDs are live', () => {
|
|
98
|
+
const state = mod.getConnectorRuntimeState()
|
|
99
|
+
|
|
100
|
+
// Clear first
|
|
101
|
+
state.lastInboundChannelByConnector.clear()
|
|
102
|
+
state.lastInboundTimeByConnector.clear()
|
|
103
|
+
state.generationCounter.clear()
|
|
104
|
+
|
|
105
|
+
state.lastInboundChannelByConnector.set('x', 'ch')
|
|
106
|
+
state.lastInboundTimeByConnector.set('x', 1)
|
|
107
|
+
state.generationCounter.set('x', 1)
|
|
108
|
+
|
|
109
|
+
const removed = mod.pruneConnectorTrackingState(new Set(['x']))
|
|
110
|
+
assert.equal(removed, 0)
|
|
111
|
+
|
|
112
|
+
// Entries still present
|
|
113
|
+
assert.equal(state.lastInboundChannelByConnector.has('x'), true)
|
|
114
|
+
assert.equal(state.lastInboundTimeByConnector.has('x'), true)
|
|
115
|
+
assert.equal(state.generationCounter.has('x'), true)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -66,4 +66,23 @@ export const connectorRuntimeState = getConnectorRuntimeState()
|
|
|
66
66
|
|
|
67
67
|
export const runningConnectors = connectorRuntimeState.running
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Remove tracking entries for connectors that no longer exist in storage.
|
|
71
|
+
* Called periodically by the daemon health sweep.
|
|
72
|
+
*/
|
|
73
|
+
export function pruneConnectorTrackingState(liveConnectorIds: Set<string>): number {
|
|
74
|
+
const st = getConnectorRuntimeState()
|
|
75
|
+
let removed = 0
|
|
76
|
+
for (const id of st.lastInboundChannelByConnector.keys()) {
|
|
77
|
+
if (!liveConnectorIds.has(id)) { st.lastInboundChannelByConnector.delete(id); removed++ }
|
|
78
|
+
}
|
|
79
|
+
for (const id of st.lastInboundTimeByConnector.keys()) {
|
|
80
|
+
if (!liveConnectorIds.has(id)) { st.lastInboundTimeByConnector.delete(id); removed++ }
|
|
81
|
+
}
|
|
82
|
+
for (const id of st.generationCounter.keys()) {
|
|
83
|
+
if (!liveConnectorIds.has(id)) { st.generationCounter.delete(id); removed++ }
|
|
84
|
+
}
|
|
85
|
+
return removed
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
export type ConnectorThreadSession = Session
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* One-time migration: backfill allKnownPeerIds on existing connector sessions.
|
|
3
3
|
* Populates the field from existing senderId, senderIdAlt, channelId, channelIdAlt, peerKey.
|
|
4
4
|
*/
|
|
5
|
+
import { log } from '@/lib/server/logger'
|
|
5
6
|
import { loadSessions, loadSettings, saveSettings, upsertStoredItem } from '../storage'
|
|
6
7
|
import type { Session } from '@/types'
|
|
7
8
|
import { isDirectConnectorSession } from './session-kind'
|
|
8
9
|
|
|
10
|
+
const TAG = 'session-consolidation'
|
|
11
|
+
|
|
9
12
|
const ALL_KNOWN_PEER_IDS_MIGRATION_FLAG = '_migration_allKnownPeerIds'
|
|
10
13
|
const THREAD_CONNECTOR_MIRROR_CLEANUP_FLAG = '_migration_pruneThreadConnectorMirrors'
|
|
11
14
|
|
|
@@ -43,7 +46,7 @@ export function backfillAllKnownPeerIds(): { migrated: number; skipped: boolean
|
|
|
43
46
|
updated[ALL_KNOWN_PEER_IDS_MIGRATION_FLAG] = true
|
|
44
47
|
saveSettings(updated)
|
|
45
48
|
if (migrated > 0) {
|
|
46
|
-
|
|
49
|
+
log.info(TAG, `Backfilled allKnownPeerIds on ${migrated} sessions`)
|
|
47
50
|
}
|
|
48
51
|
return { migrated, skipped: false }
|
|
49
52
|
}
|
|
@@ -81,7 +84,7 @@ export function pruneThreadConnectorMirrors(): { cleanedSessions: number; remove
|
|
|
81
84
|
updated[THREAD_CONNECTOR_MIRROR_CLEANUP_FLAG] = true
|
|
82
85
|
saveSettings(updated)
|
|
83
86
|
if (removedMessages > 0) {
|
|
84
|
-
|
|
87
|
+
log.info(TAG, `Pruned ${removedMessages} mirrored connector message(s) from ${cleanedSessions} main session(s)`)
|
|
85
88
|
}
|
|
86
89
|
return { cleanedSessions, removedMessages, skipped: false }
|
|
87
90
|
}
|