@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,9 +1,12 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { spawn, execSync } from 'child_process'
|
|
2
3
|
import type { ChildProcess } from 'child_process'
|
|
3
4
|
import type { Connector } from '@/types'
|
|
4
5
|
import type { PlatformConnector, ConnectorInstance, InboundMessage, ConnectorIngressResult } from './types'
|
|
5
6
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
6
7
|
|
|
8
|
+
const TAG = 'signal'
|
|
9
|
+
|
|
7
10
|
const signal: PlatformConnector = {
|
|
8
11
|
async start(connector, _botToken, onMessage): Promise<ConnectorInstance> {
|
|
9
12
|
const phoneNumber = connector.config.phoneNumber
|
|
@@ -43,16 +46,16 @@ const signal: PlatformConnector = {
|
|
|
43
46
|
|
|
44
47
|
daemonProc.stderr?.on('data', (chunk: Buffer) => {
|
|
45
48
|
const msg = chunk.toString().trim()
|
|
46
|
-
if (msg)
|
|
49
|
+
if (msg) log.error(TAG, `stderr: ${msg}`)
|
|
47
50
|
})
|
|
48
51
|
|
|
49
52
|
daemonProc.on('exit', (code) => {
|
|
50
53
|
if (!stopped) {
|
|
51
|
-
|
|
54
|
+
log.error(TAG, `daemon exited unexpectedly with code ${code}`)
|
|
52
55
|
}
|
|
53
56
|
})
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
log.info(TAG, `Daemon started in stdio mode for ${phoneNumber}`)
|
|
56
59
|
} else if (mode === 'http') {
|
|
57
60
|
// Poll the signal-cli REST API for incoming messages
|
|
58
61
|
const pollInterval = 2000
|
|
@@ -74,7 +77,7 @@ const signal: PlatformConnector = {
|
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
pollTimer = setInterval(poll, pollInterval)
|
|
77
|
-
|
|
80
|
+
log.info(TAG, `Polling ${httpUrl} for ${phoneNumber} every ${pollInterval}ms`)
|
|
78
81
|
} else {
|
|
79
82
|
throw new Error(`Unknown signalCliMode: ${mode}. Use 'stdio' or 'http'.`)
|
|
80
83
|
}
|
|
@@ -119,7 +122,7 @@ const signal: PlatformConnector = {
|
|
|
119
122
|
daemonProc.kill('SIGTERM')
|
|
120
123
|
daemonProc = null
|
|
121
124
|
}
|
|
122
|
-
|
|
125
|
+
log.info(TAG, `Connector stopped for ${phoneNumber}`)
|
|
123
126
|
},
|
|
124
127
|
}
|
|
125
128
|
},
|
|
@@ -177,7 +180,7 @@ export async function handleSignalEvent(
|
|
|
177
180
|
)
|
|
178
181
|
}
|
|
179
182
|
} catch (err: any) {
|
|
180
|
-
|
|
183
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
186
|
|
|
@@ -7,6 +7,9 @@ import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
|
7
7
|
import { deliverChunkedConnectorText } from './delivery'
|
|
8
8
|
import { downloadInboundMediaToUpload, inferInboundMediaType, mimeFromPath, isImageMime } from './media'
|
|
9
9
|
import { dedup, errorMessage } from '@/lib/shared-utils'
|
|
10
|
+
import { log } from '@/lib/server/logger'
|
|
11
|
+
|
|
12
|
+
const TAG = 'slack'
|
|
10
13
|
|
|
11
14
|
function normalizeSlackEmoji(input: string): string {
|
|
12
15
|
const raw = input.trim().replace(/^:|:$/g, '')
|
|
@@ -99,7 +102,7 @@ async function hydrateSlackThreadContext(params: {
|
|
|
99
102
|
params.inbound.threadPersonaLabel = params.inbound.threadTitle
|
|
100
103
|
params.inbound.threadHistory = history.length ? history : undefined
|
|
101
104
|
} catch (err: unknown) {
|
|
102
|
-
|
|
105
|
+
log.warn(TAG, `Thread context bootstrap failed: ${errorMessage(err)}`)
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -141,7 +144,7 @@ const slack: PlatformConnector = {
|
|
|
141
144
|
throw new Error('Auth test returned empty — the bot token may be revoked or the app uninstalled')
|
|
142
145
|
}
|
|
143
146
|
botUserId = auth.user_id as string
|
|
144
|
-
|
|
147
|
+
log.info(TAG, `Authenticated as @${auth.user} in workspace "${auth.team}"`)
|
|
145
148
|
} catch (err: any) {
|
|
146
149
|
const hint = err.code === 'slack_webapi_platform_error'
|
|
147
150
|
? '. Check that your Bot Token (xoxb-...) is correct and the app is installed to the workspace.'
|
|
@@ -159,7 +162,7 @@ const slack: PlatformConnector = {
|
|
|
159
162
|
|
|
160
163
|
// Catch global errors so they don't become unhandled rejections
|
|
161
164
|
app.error(async (error) => {
|
|
162
|
-
|
|
165
|
+
log.error(TAG, 'App error:', error)
|
|
163
166
|
})
|
|
164
167
|
|
|
165
168
|
// Optional: restrict to specific channels
|
|
@@ -177,7 +180,7 @@ const slack: PlatformConnector = {
|
|
|
177
180
|
const channelId = msg.channel
|
|
178
181
|
if (allowedChannels && !allowedChannels.includes(channelId)) return
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
log.info(TAG, `Message in ${channelId} from ${msg.user}: ${(msg.text || '').slice(0, 80)}`)
|
|
181
184
|
|
|
182
185
|
// Get user info for display name
|
|
183
186
|
let senderName = msg.user || 'unknown'
|
|
@@ -213,7 +216,7 @@ const slack: PlatformConnector = {
|
|
|
213
216
|
continue
|
|
214
217
|
}
|
|
215
218
|
} catch (err: any) {
|
|
216
|
-
|
|
219
|
+
log.warn(TAG, `Media download failed (${f?.name || 'file'}):`, err?.message || String(err))
|
|
217
220
|
}
|
|
218
221
|
}
|
|
219
222
|
media.push({
|
|
@@ -262,7 +265,7 @@ const slack: PlatformConnector = {
|
|
|
262
265
|
},
|
|
263
266
|
})
|
|
264
267
|
} catch (err: any) {
|
|
265
|
-
|
|
268
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
266
269
|
try {
|
|
267
270
|
await say('Sorry, I encountered an error processing your message.')
|
|
268
271
|
} catch { /* ignore */ }
|
|
@@ -320,12 +323,12 @@ const slack: PlatformConnector = {
|
|
|
320
323
|
},
|
|
321
324
|
})
|
|
322
325
|
} catch (err: any) {
|
|
323
|
-
|
|
326
|
+
log.error(TAG, 'Error handling mention:', err.message)
|
|
324
327
|
}
|
|
325
328
|
})
|
|
326
329
|
|
|
327
330
|
await app.start()
|
|
328
|
-
|
|
331
|
+
log.info(TAG, 'Bot connected (socket mode)')
|
|
329
332
|
|
|
330
333
|
let appStopped = false
|
|
331
334
|
|
|
@@ -423,14 +426,14 @@ const slack: PlatformConnector = {
|
|
|
423
426
|
async stop() {
|
|
424
427
|
appStopped = true
|
|
425
428
|
await app.stop()
|
|
426
|
-
|
|
429
|
+
log.info(TAG, 'Bot disconnected')
|
|
427
430
|
},
|
|
428
431
|
}
|
|
429
432
|
|
|
430
433
|
// Bolt emits 'error' on unrecoverable failures (auth revoked, socket closed permanently)
|
|
431
434
|
app.error(async (error) => {
|
|
432
435
|
const errMsg = error.original?.message || error.message || String(error)
|
|
433
|
-
|
|
436
|
+
log.error(TAG, 'App error:', errMsg)
|
|
434
437
|
if (appStopped) return
|
|
435
438
|
appStopped = true
|
|
436
439
|
instance.onCrash?.(`Slack error: ${errMsg}`)
|
|
@@ -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 = 'teams'
|
|
6
|
+
|
|
4
7
|
const teams: PlatformConnector = {
|
|
5
8
|
async start(connector, botToken, onMessage): Promise<ConnectorInstance> {
|
|
6
9
|
const pkg = 'botbuilder'
|
|
@@ -15,7 +18,7 @@ const teams: PlatformConnector = {
|
|
|
15
18
|
})
|
|
16
19
|
|
|
17
20
|
adapter.onTurnError = async (_context: unknown, error: Error) => {
|
|
18
|
-
|
|
21
|
+
log.error(TAG, 'Turn error:', error.message)
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
// Store conversation references for proactive messaging
|
|
@@ -49,7 +52,7 @@ const teams: PlatformConnector = {
|
|
|
49
52
|
if (!reply) return
|
|
50
53
|
await context.sendActivity(reply.visibleText)
|
|
51
54
|
} catch (err: any) {
|
|
52
|
-
|
|
55
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
53
56
|
try {
|
|
54
57
|
await context.sendActivity('Sorry, I encountered an error processing your message.')
|
|
55
58
|
} catch { /* ignore */ }
|
|
@@ -61,8 +64,8 @@ const teams: PlatformConnector = {
|
|
|
61
64
|
const handlerKey = `__swarmclaw_teams_handler_${connector.id}__`
|
|
62
65
|
;(globalThis as any)[handlerKey] = processActivity
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
log.info(TAG, `Bot registered (appId: ${appId})`)
|
|
68
|
+
log.info(TAG, `Configure your bot's messaging endpoint to POST to /api/connectors/${connector.id}/webhook`)
|
|
66
69
|
|
|
67
70
|
return {
|
|
68
71
|
connector,
|
|
@@ -85,7 +88,7 @@ const teams: PlatformConnector = {
|
|
|
85
88
|
stopped = true
|
|
86
89
|
delete (globalThis as any)[handlerKey]
|
|
87
90
|
conversationReferences.clear()
|
|
88
|
-
|
|
91
|
+
log.info(TAG, 'Bot disconnected')
|
|
89
92
|
},
|
|
90
93
|
}
|
|
91
94
|
},
|
|
@@ -7,6 +7,9 @@ import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
|
7
7
|
import { deliverChunkedConnectorText } from './delivery'
|
|
8
8
|
import { downloadInboundMediaToUpload, inferInboundMediaType, mimeFromPath, isImageMime, isAudioMime } from './media'
|
|
9
9
|
import { errorMessage } from '@/lib/shared-utils'
|
|
10
|
+
import { log } from '@/lib/server/logger'
|
|
11
|
+
|
|
12
|
+
const TAG = 'telegram'
|
|
10
13
|
|
|
11
14
|
const telegram: PlatformConnector = {
|
|
12
15
|
async start(connector, botToken, onMessage): Promise<ConnectorInstance> {
|
|
@@ -20,23 +23,23 @@ const telegram: PlatformConnector = {
|
|
|
20
23
|
|
|
21
24
|
// Log all errors
|
|
22
25
|
bot.catch((err) => {
|
|
23
|
-
|
|
26
|
+
log.error(TAG, 'Bot error:', err.message || err)
|
|
24
27
|
})
|
|
25
28
|
|
|
26
29
|
// Delete any existing webhook so long polling works
|
|
27
30
|
await bot.api.deleteWebhook().catch((err) => {
|
|
28
|
-
|
|
31
|
+
log.error(TAG, 'Failed to delete webhook:', err.message)
|
|
29
32
|
})
|
|
30
33
|
|
|
31
34
|
// Log all incoming updates for debugging
|
|
32
35
|
bot.use(async (ctx, next) => {
|
|
33
|
-
|
|
36
|
+
log.info(TAG, `Update received: chat=${ctx.chat?.id}, from=${ctx.from?.first_name}, hasText=${!!ctx.message?.text}`)
|
|
34
37
|
await next()
|
|
35
38
|
})
|
|
36
39
|
|
|
37
40
|
// Handle /start command (required for new conversations)
|
|
38
41
|
bot.command('start', async (ctx) => {
|
|
39
|
-
|
|
42
|
+
log.info(TAG, `/start from ${ctx.from?.first_name} (chat=${ctx.chat.id})`)
|
|
40
43
|
await ctx.reply('Hello! I\'m ready to chat. Send me a message.')
|
|
41
44
|
})
|
|
42
45
|
|
|
@@ -51,15 +54,15 @@ const telegram: PlatformConnector = {
|
|
|
51
54
|
// that appear as short bracketed strings like [rr], [e], [read] etc.
|
|
52
55
|
const hasMedia = raw.photo || raw.video || raw.audio || raw.voice || raw.document || raw.animation
|
|
53
56
|
if (!hasMedia && /^\[.{1,5}\]$/.test(text.trim())) {
|
|
54
|
-
|
|
57
|
+
log.info(TAG, `Ignoring system event from ${ctx.from.first_name}: ${text}`)
|
|
55
58
|
return
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
log.info(TAG, `Message from ${ctx.from.first_name} (chat=${chatId}): ${String(text).slice(0, 80)}`)
|
|
59
62
|
|
|
60
63
|
// Filter by allowed chats if configured
|
|
61
64
|
if (allowedChats && !allowedChats.includes(chatId)) {
|
|
62
|
-
|
|
65
|
+
log.info(TAG, `Skipping — chat ${chatId} not in allowed list: ${allowedChats.join(',')}`)
|
|
63
66
|
return
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -125,7 +128,7 @@ const telegram: PlatformConnector = {
|
|
|
125
128
|
})
|
|
126
129
|
if (stored) media.push(stored)
|
|
127
130
|
} catch (err: any) {
|
|
128
|
-
|
|
131
|
+
log.warn(TAG, `Failed to fetch media ${m.fileId}:`, err?.message || String(err))
|
|
129
132
|
media.push({
|
|
130
133
|
type: m.type,
|
|
131
134
|
fileName: m.fileName,
|
|
@@ -175,7 +178,7 @@ const telegram: PlatformConnector = {
|
|
|
175
178
|
},
|
|
176
179
|
})
|
|
177
180
|
} catch (err: any) {
|
|
178
|
-
|
|
181
|
+
log.error(TAG, 'Error handling message:', err.message)
|
|
179
182
|
try {
|
|
180
183
|
await ctx.reply('Sorry, I encountered an error processing your message.')
|
|
181
184
|
} catch { /* ignore */ }
|
|
@@ -268,7 +271,7 @@ const telegram: PlatformConnector = {
|
|
|
268
271
|
async stop() {
|
|
269
272
|
botRunning = false
|
|
270
273
|
await bot.stop()
|
|
271
|
-
|
|
274
|
+
log.info(TAG, 'Bot stopped')
|
|
272
275
|
},
|
|
273
276
|
}
|
|
274
277
|
|
|
@@ -277,12 +280,12 @@ const telegram: PlatformConnector = {
|
|
|
277
280
|
allowed_updates: ['message', 'edited_message'],
|
|
278
281
|
onStart: (botInfo) => {
|
|
279
282
|
botUsername = botInfo.username || ''
|
|
280
|
-
|
|
283
|
+
log.info(TAG, `Bot started as @${botInfo.username} — polling for updates`)
|
|
281
284
|
},
|
|
282
285
|
}).catch((err: unknown) => {
|
|
283
286
|
botRunning = false
|
|
284
287
|
const errMsg = errorMessage(err)
|
|
285
|
-
|
|
288
|
+
log.error(TAG, 'Polling stopped with error:', errMsg)
|
|
286
289
|
instance.onCrash?.(`Polling stopped: ${errMsg}`)
|
|
287
290
|
})
|
|
288
291
|
|
|
@@ -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
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { generateAbstract } from './memory-abstract'
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Short content guard
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
describe('generateAbstract', () => {
|
|
11
|
+
it('returns null for content <= 200 chars', async () => {
|
|
12
|
+
const short = 'A'.repeat(200)
|
|
13
|
+
assert.equal(await generateAbstract(short, 'title'), null)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns null for empty content', async () => {
|
|
17
|
+
assert.equal(await generateAbstract('', 'title'), null)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('returns null for content exactly 200 chars', async () => {
|
|
21
|
+
assert.equal(await generateAbstract('x'.repeat(200)), null)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Fallback abstract
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
it('returns fallback (truncated prefix) when LLM import fails', async () => {
|
|
29
|
+
// generateAbstract uses dynamic import('@/lib/server/build-llm')
|
|
30
|
+
// In a test environment without the full server, the import will fail
|
|
31
|
+
// and the catch block returns fallbackAbstract
|
|
32
|
+
const longContent = 'B'.repeat(250)
|
|
33
|
+
const result = await generateAbstract(longContent, 'title')
|
|
34
|
+
// Fallback: first 150 chars + '...'
|
|
35
|
+
assert.ok(result !== null)
|
|
36
|
+
assert.equal(result, 'B'.repeat(150) + '...')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('returns content as-is when <= 150 chars and LLM fails', async () => {
|
|
40
|
+
// This case won't trigger because content <= 200 returns null.
|
|
41
|
+
// The fallback only runs for content > 200 chars.
|
|
42
|
+
// For content > 200 but fallback returns first 150 + '...'
|
|
43
|
+
const content = 'C'.repeat(201)
|
|
44
|
+
const result = await generateAbstract(content, 'title')
|
|
45
|
+
assert.ok(result !== null)
|
|
46
|
+
assert.equal(result, 'C'.repeat(150) + '...')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('fallback does not add ellipsis for content <= 150 chars', async () => {
|
|
50
|
+
// This is a unit test of the fallback logic itself:
|
|
51
|
+
// Since generateAbstract returns null for content <= 200,
|
|
52
|
+
// the fallback truncation (150 + '...') only applies to content > 200.
|
|
53
|
+
// But the fallbackAbstract function itself handles <= 150 without ellipsis.
|
|
54
|
+
// We test this indirectly — content of 201 chars gets truncated to 150 + '...'
|
|
55
|
+
const content = 'D'.repeat(201)
|
|
56
|
+
const result = await generateAbstract(content)
|
|
57
|
+
assert.equal(result, 'D'.repeat(150) + '...')
|
|
58
|
+
})
|
|
59
|
+
})
|