@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
|
@@ -3,9 +3,10 @@ import { HumanMessage, AIMessage } from '@langchain/core/messages'
|
|
|
3
3
|
import { createReactAgent } from '@langchain/langgraph/prebuilt'
|
|
4
4
|
import { MemorySaver } from '@langchain/langgraph'
|
|
5
5
|
import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/runtime/heartbeat-defaults'
|
|
6
|
+
import { getAgent } from '@/lib/server/agents/agent-repository'
|
|
6
7
|
import { buildSessionTools } from '@/lib/server/session-tools'
|
|
7
8
|
import { buildChatModel } from '@/lib/server/build-llm'
|
|
8
|
-
import { loadSettings
|
|
9
|
+
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
9
10
|
import { getExtensionManager } from '@/lib/server/extensions'
|
|
10
11
|
import {
|
|
11
12
|
collectCapabilityAgentContext,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
buildCredentialAwarenessSection,
|
|
29
30
|
} from '@/lib/server/chat-execution/prompt-sections'
|
|
30
31
|
|
|
32
|
+
import { log } from '@/lib/server/logger'
|
|
31
33
|
import { logExecution } from '@/lib/server/execution-log'
|
|
32
34
|
import { buildCurrentDateTimePromptContext } from '@/lib/server/prompt-runtime-context'
|
|
33
35
|
import { expandExtensionIds } from '@/lib/server/tool-aliases'
|
|
@@ -40,6 +42,7 @@ import { routeTaskIntent } from '@/lib/server/capability-router'
|
|
|
40
42
|
import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
|
|
41
43
|
import { resolveSessionToolPolicy } from '@/lib/server/tool-capability-policy'
|
|
42
44
|
import { ToolLoopTracker } from '@/lib/server/tool-loop-detection'
|
|
45
|
+
import { isHeartbeatSource } from '@/lib/server/runtime/heartbeat-source'
|
|
43
46
|
import { isCurrentThreadRecallRequest } from '@/lib/server/memory/memory-policy'
|
|
44
47
|
import {
|
|
45
48
|
resolveEffectiveSessionMemoryScopeMode,
|
|
@@ -99,6 +102,8 @@ import {
|
|
|
99
102
|
type MessageClassification,
|
|
100
103
|
} from '@/lib/server/chat-execution/message-classifier'
|
|
101
104
|
|
|
105
|
+
const TAG = 'stream-agent-chat'
|
|
106
|
+
|
|
102
107
|
// LangGraph unhandledRejection handler has been moved to src/instrumentation.ts
|
|
103
108
|
// to avoid re-registration on every HMR reload.
|
|
104
109
|
|
|
@@ -175,6 +180,8 @@ interface StreamAgentChatOpts {
|
|
|
175
180
|
fallbackCredentialIds?: string[]
|
|
176
181
|
signal?: AbortSignal
|
|
177
182
|
promptMode?: PromptMode
|
|
183
|
+
/** Run source (e.g. 'heartbeat', 'chat', 'scheduler') — used for heartbeat-specific tuning. */
|
|
184
|
+
source?: string
|
|
178
185
|
}
|
|
179
186
|
|
|
180
187
|
export interface StreamAgentChatResult {
|
|
@@ -207,6 +214,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
207
214
|
async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
|
|
208
215
|
const startTs = Date.now()
|
|
209
216
|
const { session, message, imagePath, imageUrl, attachedFiles, apiKey, systemPrompt, extraSystemContext, write, history, fallbackCredentialIds, signal } = opts
|
|
217
|
+
const isHeartbeat = isHeartbeatSource(opts.source)
|
|
210
218
|
const promptMode: PromptMode = opts.promptMode ?? resolvePromptMode(session)
|
|
211
219
|
const isMinimalPrompt = promptMode === 'minimal'
|
|
212
220
|
const isConnectorSession = isDirectConnectorSession(session)
|
|
@@ -221,13 +229,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
221
229
|
// fallbackCredentialIds is intentionally accepted for compatibility with caller signatures.
|
|
222
230
|
void fallbackCredentialIds
|
|
223
231
|
|
|
232
|
+
const sessionAgent = session.agentId ? getAgent(session.agentId) : null
|
|
233
|
+
|
|
224
234
|
// Resolve agent's thinking level for provider-native params
|
|
225
235
|
let agentThinkingLevel: 'minimal' | 'low' | 'medium' | 'high' | undefined
|
|
226
236
|
if (session.thinkingLevel) {
|
|
227
237
|
agentThinkingLevel = session.thinkingLevel
|
|
228
|
-
} else if (
|
|
229
|
-
|
|
230
|
-
agentThinkingLevel = agentsForThinking[session.agentId]?.thinkingLevel
|
|
238
|
+
} else if (sessionAgent) {
|
|
239
|
+
agentThinkingLevel = sessionAgent.thinkingLevel
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
const llm = buildChatModel({
|
|
@@ -313,9 +322,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
313
322
|
let agentResponseStyle: 'concise' | 'normal' | 'detailed' | null = null
|
|
314
323
|
let agentResponseMaxChars: number | null = null
|
|
315
324
|
const activeProjectContext = resolveActiveProjectContext(session)
|
|
316
|
-
if (
|
|
317
|
-
const
|
|
318
|
-
const agent = agents[session.agentId]
|
|
325
|
+
if (sessionAgent) {
|
|
326
|
+
const agent = sessionAgent
|
|
319
327
|
isCoordinatorAgent = agent?.role === 'coordinator'
|
|
320
328
|
agentDelegationEnabled = agent?.delegationEnabled === true
|
|
321
329
|
agentDelegationTargetMode = agent?.delegationTargetMode === 'selected' ? 'selected' : 'all'
|
|
@@ -366,7 +374,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
366
374
|
const extensionContextParts = await collectCapabilityAgentContext(session, sessionExtensions, message, history)
|
|
367
375
|
promptParts.push(...extensionContextParts)
|
|
368
376
|
} catch (err: unknown) {
|
|
369
|
-
|
|
377
|
+
log.error(TAG, 'Capability context injection failed:', err instanceof Error ? err.message : String(err))
|
|
370
378
|
}
|
|
371
379
|
|
|
372
380
|
// Project context — full mode only
|
|
@@ -408,13 +416,15 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
408
416
|
|
|
409
417
|
// Proactive memory recall — full mode only
|
|
410
418
|
{
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
const memoryBlock = await buildProactiveMemorySection(
|
|
414
|
-
session, agentForMemory, message, activeProjectContext.projectRoot,
|
|
419
|
+
const memoryResult = await buildProactiveMemorySection(
|
|
420
|
+
session, sessionAgent, message, activeProjectContext.projectRoot,
|
|
415
421
|
isMinimalPrompt, currentThreadRecallRequest,
|
|
416
422
|
)
|
|
417
|
-
if (
|
|
423
|
+
if (memoryResult.section) promptParts.push(memoryResult.section)
|
|
424
|
+
// Persist injection dedup counts so repeated memories are suppressed
|
|
425
|
+
if (Object.keys(memoryResult.injectedIds).length > 0) {
|
|
426
|
+
session.injectedMemoryIds = memoryResult.injectedIds
|
|
427
|
+
}
|
|
418
428
|
}
|
|
419
429
|
|
|
420
430
|
// Goal anchor — keeps the agent focused when context is long.
|
|
@@ -429,9 +439,26 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
429
439
|
const budget = isMinimalPrompt ? MINIMAL_PROMPT_BUDGET : DEFAULT_PROMPT_BUDGET
|
|
430
440
|
const budgetResult = applyPromptBudget(promptParts, budget)
|
|
431
441
|
if (budgetResult.truncated) {
|
|
432
|
-
|
|
442
|
+
log.warn(TAG, `Prompt truncated: ${budgetResult.originalChars} chars → ${budget.maxTotalChars} chars (mode=${promptMode})`)
|
|
433
443
|
} else if (isOverWarningThreshold(budgetResult.originalChars, budget)) {
|
|
434
|
-
|
|
444
|
+
log.warn(TAG, `Prompt near budget: ${budgetResult.originalChars}/${budget.maxTotalChars} chars (mode=${promptMode})`)
|
|
445
|
+
}
|
|
446
|
+
// Emit prompt section telemetry (no-op when perf disabled)
|
|
447
|
+
if (perf.isEnabled()) {
|
|
448
|
+
const sectionSizes: Record<string, number> = {}
|
|
449
|
+
for (let i = 0; i < rawPromptParts.length; i++) {
|
|
450
|
+
const part = rawPromptParts[i]
|
|
451
|
+
const headerMatch = part.match(/^##?\s+(.+?)[\n\r]/)
|
|
452
|
+
const label = headerMatch ? headerMatch[1].slice(0, 30) : `section_${i}`
|
|
453
|
+
sectionSizes[label] = (sectionSizes[label] || 0) + part.length
|
|
454
|
+
}
|
|
455
|
+
perf.start('prompt', 'budget', {
|
|
456
|
+
sessionId: session.id,
|
|
457
|
+
totalChars: budgetResult.originalChars,
|
|
458
|
+
budgetMax: budget.maxTotalChars,
|
|
459
|
+
truncated: budgetResult.truncated,
|
|
460
|
+
sections: sectionSizes,
|
|
461
|
+
})()
|
|
435
462
|
}
|
|
436
463
|
let prompt = budgetResult.prompt
|
|
437
464
|
|
|
@@ -443,6 +470,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
443
470
|
...(typeof settings.toolLoopFrequencyWarn === 'number' && { toolFrequencyWarn: settings.toolLoopFrequencyWarn }),
|
|
444
471
|
...(typeof settings.toolLoopFrequencyCritical === 'number' && { toolFrequencyCritical: settings.toolLoopFrequencyCritical }),
|
|
445
472
|
...(typeof settings.toolLoopCircuitBreaker === 'number' && { circuitBreaker: settings.toolLoopCircuitBreaker }),
|
|
473
|
+
// Heartbeat runs are brief status checks — tighten thresholds significantly
|
|
474
|
+
...(isHeartbeat && { toolFrequencyWarn: 8, toolFrequencyCritical: 15 }),
|
|
446
475
|
})
|
|
447
476
|
const emittedPreToolWarnings = new Set<string>()
|
|
448
477
|
const recursionLimit = getAgentLoopRecursionLimit(runtime)
|
|
@@ -453,14 +482,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
453
482
|
|
|
454
483
|
async function buildContentForFile(filePath: string): Promise<LangChainContentPart | string | null> {
|
|
455
484
|
if (!fs.existsSync(filePath)) {
|
|
456
|
-
|
|
485
|
+
log.info(TAG, `FILE NOT FOUND: ${filePath}`)
|
|
457
486
|
return null
|
|
458
487
|
}
|
|
459
488
|
const name = filePath.split('/').pop() || 'file'
|
|
460
489
|
if (IMAGE_EXTS.test(filePath)) {
|
|
461
490
|
const buf = fs.readFileSync(filePath)
|
|
462
491
|
if (buf.length === 0) {
|
|
463
|
-
|
|
492
|
+
log.warn(TAG, `Image file is empty: ${filePath}`)
|
|
464
493
|
return `[Attached image: ${name} — file is empty]`
|
|
465
494
|
}
|
|
466
495
|
const data = buf.toString('base64')
|
|
@@ -587,8 +616,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
587
616
|
summarize,
|
|
588
617
|
})
|
|
589
618
|
effectiveHistory = result.messages
|
|
590
|
-
|
|
591
|
-
`
|
|
619
|
+
log.info(TAG,
|
|
620
|
+
`Auto-compacted ${session.id}: ${recentHistory.length} → ${effectiveHistory.length} msgs` +
|
|
592
621
|
` (prompt history ${promptHistoryTokens} tokens)` +
|
|
593
622
|
(result.summaryAdded ? ' (LLM summary)' : ' (sliding window fallback)'),
|
|
594
623
|
)
|
|
@@ -783,7 +812,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
783
812
|
// Init turn state and limits
|
|
784
813
|
// -------------------------------------------------------------------------
|
|
785
814
|
const state = new ChatTurnState()
|
|
786
|
-
const limits = new ContinuationLimits(isConnectorSession)
|
|
815
|
+
const limits = new ContinuationLimits(isConnectorSession, isHeartbeat)
|
|
787
816
|
const routingDecision = routeTaskIntent(message, sessionExtensions, null)
|
|
788
817
|
const explicitRequiredToolNames = getExplicitRequiredToolNames(message, sessionExtensions)
|
|
789
818
|
|
|
@@ -896,14 +925,18 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
896
925
|
&& !abortController.signal.aborted)
|
|
897
926
|
|| (isTransientProviderError && !abortController.signal.aborted)
|
|
898
927
|
|
|
899
|
-
const
|
|
900
|
-
console[logLevel](`[stream-agent-chat] Error in streamEvents iteration=${iteration}`, {
|
|
928
|
+
const logPayload = {
|
|
901
929
|
errName, errMsg, errStack,
|
|
902
930
|
statusCode, retryAfterMs: extractedRetryAfterMs,
|
|
903
931
|
isRecursionError, isContextOverflow, isTransientAbort,
|
|
904
932
|
hasToolCalls: state.hasToolCalls, fullTextLen: state.fullText.length,
|
|
905
933
|
parentAborted: abortController.signal.aborted,
|
|
906
|
-
}
|
|
934
|
+
}
|
|
935
|
+
if (abortController.signal.aborted) {
|
|
936
|
+
log.warn(TAG, `Error in streamEvents iteration=${iteration}`, logPayload)
|
|
937
|
+
} else {
|
|
938
|
+
log.error(TAG, `Error in streamEvents iteration=${iteration}`, logPayload)
|
|
939
|
+
}
|
|
907
940
|
|
|
908
941
|
if (timers.requiredToolKickoffTimedOut && limits.canContinue('required_tool') && !abortController.signal.aborted) {
|
|
909
942
|
const hadPartialOutput = state.fullText.length > iterationStartState.fullText.length || state.streamedToolEvents.length > iterationStartState.toolEventCount
|
|
@@ -1069,9 +1102,9 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
1069
1102
|
|
|
1070
1103
|
if (!shouldContinue) break
|
|
1071
1104
|
|
|
1072
|
-
//
|
|
1105
|
+
// Partial reset on loop_recovery: clear frequency counts but preserve circuit breaker + repeat history
|
|
1073
1106
|
if (shouldContinue === 'loop_recovery') {
|
|
1074
|
-
loopTracker.
|
|
1107
|
+
loopTracker.resetFrequencyCounts()
|
|
1075
1108
|
}
|
|
1076
1109
|
|
|
1077
1110
|
const continuationAssistantText = shouldContinue === 'memory_write_followthrough'
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* can recall cross-context conversations (chatroom ↔ direct chat).
|
|
6
6
|
*/
|
|
7
7
|
import type { Agent } from '@/types'
|
|
8
|
+
import { log } from '@/lib/server/logger'
|
|
9
|
+
|
|
10
|
+
const TAG = 'chatroom-memory-bridge'
|
|
8
11
|
|
|
9
12
|
/** Truncate text to a max length, collapsing whitespace */
|
|
10
13
|
function truncate(text: string, max: number): string {
|
|
@@ -58,7 +61,7 @@ export async function persistChatroomInteractionMemory(params: {
|
|
|
58
61
|
})
|
|
59
62
|
} catch (err: unknown) {
|
|
60
63
|
// Non-critical — log and continue
|
|
61
|
-
|
|
64
|
+
log.warn(TAG, 'Failed to persist interaction memory:', err instanceof Error ? err.message : String(err))
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -126,9 +129,9 @@ export async function summarizeAndConsolidateChatroomMemories(params: {
|
|
|
126
129
|
})
|
|
127
130
|
} catch (llmErr: unknown) {
|
|
128
131
|
// LLM summarization is best-effort
|
|
129
|
-
|
|
132
|
+
log.warn(TAG, 'LLM summarization failed:', llmErr instanceof Error ? llmErr.message : String(llmErr))
|
|
130
133
|
}
|
|
131
134
|
} catch (err: unknown) {
|
|
132
|
-
|
|
135
|
+
log.warn(TAG, 'Failed to consolidate memories:', err instanceof Error ? err.message : String(err))
|
|
133
136
|
}
|
|
134
137
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Chatroom } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
loadChatroom as loadStoredChatroom,
|
|
5
|
+
loadChatrooms as loadStoredChatrooms,
|
|
6
|
+
saveChatrooms as saveStoredChatrooms,
|
|
7
|
+
upsertChatroom as upsertStoredChatroom,
|
|
8
|
+
} from '@/lib/server/storage'
|
|
9
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
10
|
+
|
|
11
|
+
export const chatroomRepository = createRecordRepository<Chatroom>(
|
|
12
|
+
'chatrooms',
|
|
13
|
+
{
|
|
14
|
+
get(id) {
|
|
15
|
+
return loadStoredChatroom(id) as Chatroom | null
|
|
16
|
+
},
|
|
17
|
+
list() {
|
|
18
|
+
return loadStoredChatrooms() as Record<string, Chatroom>
|
|
19
|
+
},
|
|
20
|
+
upsert(id, value) {
|
|
21
|
+
upsertStoredChatroom(id, value as Chatroom)
|
|
22
|
+
},
|
|
23
|
+
replace(data) {
|
|
24
|
+
saveStoredChatrooms(data)
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
export const loadChatrooms = () => chatroomRepository.list()
|
|
30
|
+
export const loadChatroom = (id: string) => chatroomRepository.get(id)
|
|
31
|
+
export const saveChatrooms = (items: Record<string, Chatroom | Record<string, unknown>>) => chatroomRepository.replace(items as Record<string, Chatroom>)
|
|
32
|
+
export const upsertChatroom = (id: string, value: Chatroom | Record<string, unknown>) => chatroomRepository.upsert(id, value as Chatroom)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import crypto from 'node:crypto'
|
|
2
3
|
import type { PlatformConnector, ConnectorInstance, InboundMessage, InboundMedia } from './types'
|
|
3
4
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
4
5
|
|
|
6
|
+
const TAG = 'bluebubbles'
|
|
7
|
+
|
|
5
8
|
const DEFAULT_TIMEOUT_MS = 10_000
|
|
6
9
|
const DEFAULT_WEBHOOK_PATH = '/api/connectors/{id}/webhook'
|
|
7
10
|
|
|
@@ -283,8 +286,8 @@ const bluebubbles: PlatformConnector = {
|
|
|
283
286
|
throw new Error(`BlueBubbles ping failed (${pingRes.status})`)
|
|
284
287
|
}
|
|
285
288
|
|
|
286
|
-
|
|
287
|
-
|
|
289
|
+
log.info(TAG, `Connected to ${serverUrl}`)
|
|
290
|
+
log.info(TAG, `Inbound webhook endpoint: ${DEFAULT_WEBHOOK_PATH.replace('{id}', connector.id)}`)
|
|
288
291
|
|
|
289
292
|
return {
|
|
290
293
|
connector,
|
|
@@ -302,7 +305,7 @@ const bluebubbles: PlatformConnector = {
|
|
|
302
305
|
stopped = true
|
|
303
306
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
304
307
|
delete (globalThis as any)[handlerKey]
|
|
305
|
-
|
|
308
|
+
log.info(TAG, 'Connector stopped')
|
|
306
309
|
},
|
|
307
310
|
}
|
|
308
311
|
},
|
|
@@ -351,7 +354,7 @@ async function sendBlueBubblesText(params: {
|
|
|
351
354
|
// BlueBubbles may return empty body on success in some setups.
|
|
352
355
|
const message = getErrorMessage(err)
|
|
353
356
|
if (!message.toLowerCase().includes('json')) {
|
|
354
|
-
|
|
357
|
+
log.warn(TAG, `Unable to parse send response body: ${message}`)
|
|
355
358
|
}
|
|
356
359
|
return {}
|
|
357
360
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { genId } from '@/lib/id'
|
|
2
3
|
import {
|
|
3
4
|
loadConnectors,
|
|
@@ -95,6 +96,8 @@ import {
|
|
|
95
96
|
} from '@/lib/server/runtime/session-run-manager'
|
|
96
97
|
import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution'
|
|
97
98
|
|
|
99
|
+
const TAG = 'connector-inbound'
|
|
100
|
+
|
|
98
101
|
type ConnectorSession = Session
|
|
99
102
|
type CurrentChannelConnectorDelivery = {
|
|
100
103
|
mode: 'text' | 'voice_note'
|
|
@@ -205,14 +208,14 @@ async function routeOrDebounceInbound(connector: Connector, msg: InboundMessage)
|
|
|
205
208
|
clearTimeout(pending.timer)
|
|
206
209
|
pending.timer = setTimeout(() => {
|
|
207
210
|
void flushDebouncedInbound(debounceKey).catch((err: unknown) => {
|
|
208
|
-
|
|
211
|
+
log.warn(TAG, `Debounced inbound flush failed: ${errorMessage(err)}`)
|
|
209
212
|
})
|
|
210
213
|
}, policy.inboundDebounceMs)
|
|
211
214
|
pending.timer.unref?.()
|
|
212
215
|
} else {
|
|
213
216
|
const timer = setTimeout(() => {
|
|
214
217
|
void flushDebouncedInbound(debounceKey).catch((err: unknown) => {
|
|
215
|
-
|
|
218
|
+
log.warn(TAG, `Debounced inbound flush failed: ${errorMessage(err)}`)
|
|
216
219
|
})
|
|
217
220
|
}, policy.inboundDebounceMs)
|
|
218
221
|
timer.unref?.()
|
|
@@ -756,7 +759,7 @@ async function routeMessageToChatroom(connector: Connector, msg: InboundMessage)
|
|
|
756
759
|
} catch (err: unknown) {
|
|
757
760
|
const errMsg = errorMessage(err)
|
|
758
761
|
markProviderFailure(agent.provider, errMsg)
|
|
759
|
-
|
|
762
|
+
log.error(TAG, `Chatroom agent ${agent.name} error:`, errMsg)
|
|
760
763
|
}
|
|
761
764
|
}
|
|
762
765
|
|
|
@@ -785,9 +788,9 @@ async function routeMessageToChatroom(connector: Connector, msg: InboundMessage)
|
|
|
785
788
|
replyToMessageId: replyOptions.replyToMessageId,
|
|
786
789
|
threadId: replyOptions.threadId,
|
|
787
790
|
})
|
|
788
|
-
|
|
791
|
+
log.info(TAG, `Sent chatroom media to ${msg.platform}: ${path.basename(file.path)}`)
|
|
789
792
|
} catch (err: unknown) {
|
|
790
|
-
|
|
793
|
+
log.error(TAG, `Failed to send chatroom media ${path.basename(file.path)}:`, errorMessage(err))
|
|
791
794
|
}
|
|
792
795
|
}
|
|
793
796
|
}
|
|
@@ -1105,7 +1108,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1105
1108
|
})
|
|
1106
1109
|
} catch (err: unknown) {
|
|
1107
1110
|
const errText = errorMessage(err)
|
|
1108
|
-
|
|
1111
|
+
log.error(TAG, 'queued follow-up delivery failed:', errText)
|
|
1109
1112
|
try {
|
|
1110
1113
|
const { sendConnectorMessage } = await import('./connector-outbound')
|
|
1111
1114
|
await sendConnectorMessage({
|
|
@@ -1120,7 +1123,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1120
1123
|
}
|
|
1121
1124
|
}).catch(async (err: unknown) => {
|
|
1122
1125
|
const errText = errorMessage(err)
|
|
1123
|
-
|
|
1126
|
+
log.error(TAG, 'queued follow-up run failed:', errText)
|
|
1124
1127
|
try {
|
|
1125
1128
|
const { sendConnectorMessage } = await import('./connector-outbound')
|
|
1126
1129
|
await sendConnectorMessage({
|
|
@@ -1175,7 +1178,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1175
1178
|
if (transcript) currentChannelDeliveryRef.current?.transcripts.push(transcript)
|
|
1176
1179
|
}
|
|
1177
1180
|
const hasTools = getEnabledCapabilityIds(session).length > 0 && session.provider !== 'claude-cli'
|
|
1178
|
-
|
|
1181
|
+
log.info(TAG, `Routing message to agent "${agent.name}" (${session.provider}/${session.model}), hasTools=${!!hasTools}`)
|
|
1179
1182
|
|
|
1180
1183
|
if (hasTools) {
|
|
1181
1184
|
try {
|
|
@@ -1269,10 +1272,10 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1269
1272
|
// Use finalResponse for connectors — strips intermediate planning/tool-use text
|
|
1270
1273
|
fullText = result.finalResponse || result.fullText
|
|
1271
1274
|
mediaExtractionText = [result.fullText || '', ...toolMediaOutputs].filter(Boolean).join('\n\n')
|
|
1272
|
-
|
|
1275
|
+
log.info(TAG, `streamAgentChat returned ${result.fullText.length} chars total, ${fullText.length} chars final`)
|
|
1273
1276
|
} catch (err: unknown) {
|
|
1274
1277
|
const message = errorMessage(err)
|
|
1275
|
-
|
|
1278
|
+
log.error(TAG, 'streamAgentChat error:', message)
|
|
1276
1279
|
return `[Error] ${message}`
|
|
1277
1280
|
}
|
|
1278
1281
|
} else {
|
|
@@ -1324,7 +1327,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1324
1327
|
} else {
|
|
1325
1328
|
await maybeSendStatusReaction(connector, msg, 'silent')
|
|
1326
1329
|
}
|
|
1327
|
-
|
|
1330
|
+
log.info(TAG, 'Agent returned hidden control sentinel — suppressing outbound reply')
|
|
1328
1331
|
logExecution(session.id, 'decision', 'Agent suppressed outbound (NO_MESSAGE)', {
|
|
1329
1332
|
agentId: agent.id,
|
|
1330
1333
|
detail: { platform: msg.platform, channelId: msg.channelId },
|
|
@@ -1375,7 +1378,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1375
1378
|
replyToMessageId: replyOptions.replyToMessageId,
|
|
1376
1379
|
threadId: replyOptions.threadId,
|
|
1377
1380
|
})
|
|
1378
|
-
|
|
1381
|
+
log.info(TAG, `Sent media to ${msg.platform}: ${path.basename(file.path)}`)
|
|
1379
1382
|
logExecution(session.id, 'outbound', 'Connector media sent', {
|
|
1380
1383
|
agentId: agent.id,
|
|
1381
1384
|
detail: {
|
|
@@ -1386,7 +1389,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1386
1389
|
},
|
|
1387
1390
|
})
|
|
1388
1391
|
} catch (err: unknown) {
|
|
1389
|
-
|
|
1392
|
+
log.error(TAG, `Failed to send media ${path.basename(file.path)}:`, errorMessage(err))
|
|
1390
1393
|
logExecution(session.id, 'error', 'Connector media send failed', {
|
|
1391
1394
|
agentId: agent.id,
|
|
1392
1395
|
detail: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import { genId } from '@/lib/id'
|
|
2
3
|
import {
|
|
3
4
|
loadConnectors, saveConnectors,
|
|
@@ -19,6 +20,8 @@ import {
|
|
|
19
20
|
} from './reconnect-state'
|
|
20
21
|
import { connectorRuntimeState, runningConnectors } from './runtime-state'
|
|
21
22
|
|
|
23
|
+
const TAG = 'connector-lifecycle'
|
|
24
|
+
|
|
22
25
|
const running = runningConnectors
|
|
23
26
|
const {
|
|
24
27
|
lastInboundChannelByConnector,
|
|
@@ -91,7 +94,7 @@ export async function getPlatform(platform: string) {
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
} catch (err: unknown) {
|
|
94
|
-
|
|
97
|
+
log.warn(TAG, `Failed to check extensions for platform "${platform}":`, errorMessage(err))
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
throw new Error(`Unknown platform: ${platform}`)
|
|
@@ -184,7 +187,7 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
|
|
|
184
187
|
const typedInstance = instance as ConnectorInstance
|
|
185
188
|
if (!typedInstance.onCrash) {
|
|
186
189
|
typedInstance.onCrash = (error: string) => {
|
|
187
|
-
|
|
190
|
+
log.warn(TAG, `onCrash fired for "${connector.name}" (${connectorId}): ${error}`)
|
|
188
191
|
running.delete(connectorId)
|
|
189
192
|
recordHealthEvent(connectorId, 'disconnected', `Crash callback: ${error}`)
|
|
190
193
|
|
|
@@ -217,7 +220,7 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
|
|
|
217
220
|
clearReconnectState(connectorId)
|
|
218
221
|
notify('connectors')
|
|
219
222
|
|
|
220
|
-
|
|
223
|
+
log.info(TAG, `Started ${connector.platform} connector: ${connector.name}`)
|
|
221
224
|
logActivity({ entityType: 'connector', entityId: connectorId, action: 'started', actor: 'system', summary: `Connector "${connector.name}" (${connector.platform}) started` })
|
|
222
225
|
recordHealthEvent(connectorId, 'started', `${connector.platform} connector "${connector.name}" started`)
|
|
223
226
|
} catch (err: unknown) {
|
|
@@ -277,7 +280,7 @@ export async function stopConnector(
|
|
|
277
280
|
notify('connectors')
|
|
278
281
|
}
|
|
279
282
|
|
|
280
|
-
|
|
283
|
+
log.info(TAG, `Stopped connector: ${connectorId}`)
|
|
281
284
|
logActivity({ entityType: 'connector', entityId: connectorId, action: 'stopped', actor: 'system', summary: `Connector stopped` })
|
|
282
285
|
recordHealthEvent(connectorId, 'stopped', `Connector stopped`)
|
|
283
286
|
}
|
|
@@ -340,10 +343,10 @@ export async function autoStartConnectors(): Promise<void> {
|
|
|
340
343
|
for (const connector of Object.values(connectors) as Connector[]) {
|
|
341
344
|
if (connector.isEnabled && !running.has(connector.id)) {
|
|
342
345
|
try {
|
|
343
|
-
|
|
346
|
+
log.info(TAG, `Auto-starting ${connector.platform} connector: ${connector.name}`)
|
|
344
347
|
await startConnector(connector.id)
|
|
345
348
|
} catch (err: unknown) {
|
|
346
|
-
|
|
349
|
+
log.error(TAG, `Failed to auto-start ${connector.name}:`, err instanceof Error ? err.message : err)
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
}
|
|
@@ -434,14 +437,14 @@ export async function checkConnectorHealth(): Promise<void> {
|
|
|
434
437
|
if (instance.isAlive()) {
|
|
435
438
|
// Connector is healthy — clear any reconnect state
|
|
436
439
|
if (connectorReconnectStateStore.has(id)) {
|
|
437
|
-
|
|
440
|
+
log.info(TAG, `Connector "${instance.connector.name}" recovered`)
|
|
438
441
|
clearReconnectState(id)
|
|
439
442
|
}
|
|
440
443
|
continue
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
// Connector is dead but still in the running Map
|
|
444
|
-
|
|
447
|
+
log.warn(TAG, `Connector "${instance.connector.name}" (${id}) isAlive=false — removing from running`)
|
|
445
448
|
recordHealthEvent(id, 'disconnected', `Connector "${instance.connector.name}" detected as dead (isAlive=false)`)
|
|
446
449
|
notifyOrchestrators(`Connector ${instance.connector.name || id} status: disconnected`, `connector-status:${id}`)
|
|
447
450
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
1
2
|
import {
|
|
2
3
|
loadConnectors,
|
|
3
4
|
loadSession, upsertSession,
|
|
@@ -19,6 +20,8 @@ import { enqueueConnectorOutbox } from './outbox'
|
|
|
19
20
|
import { connectorRuntimeState, runningConnectors } from './runtime-state'
|
|
20
21
|
import { recordHealthEvent, startConnector } from './connector-lifecycle'
|
|
21
22
|
|
|
23
|
+
const TAG = 'connector-outbound'
|
|
24
|
+
|
|
22
25
|
const running = runningConnectors
|
|
23
26
|
const { recentOutbound } = connectorRuntimeState
|
|
24
27
|
const OUTBOUND_DEDUP_TTL_MS = 30_000
|
|
@@ -161,7 +164,7 @@ export async function sendConnectorMessage(params: {
|
|
|
161
164
|
|
|
162
165
|
// Apply NO_MESSAGE filter at the delivery layer so all outbound paths respect it
|
|
163
166
|
if ((suppressHiddenText || isNoMessage(sanitizedText)) && !params.imageUrl && !params.fileUrl && !params.mediaPath) {
|
|
164
|
-
|
|
167
|
+
log.info(TAG, 'sendConnectorMessage: NO_MESSAGE — suppressing outbound send')
|
|
165
168
|
return { connectorId, platform: connector.platform, channelId: params.channelId, suppressed: true }
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -178,7 +181,7 @@ export async function sendConnectorMessage(params: {
|
|
|
178
181
|
text: sanitizedText,
|
|
179
182
|
dedupeKey: params.dedupeKey,
|
|
180
183
|
})) {
|
|
181
|
-
|
|
184
|
+
log.info(TAG, `sendConnectorMessage: duplicate suppressed for ${connectorId}:${channelId}`)
|
|
182
185
|
return { connectorId, platform: connector.platform, channelId, suppressed: true }
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -226,7 +229,7 @@ export async function sendConnectorMessage(params: {
|
|
|
226
229
|
} catch (err: unknown) {
|
|
227
230
|
if (!isRecoverableConnectorSendError(err)) throw err
|
|
228
231
|
const errMsg = errorMessage(err)
|
|
229
|
-
|
|
232
|
+
log.warn(TAG, `Outbound send failed for ${connectorId}; attempting automatic restart`, { error: errMsg })
|
|
230
233
|
recordHealthEvent(connectorId, 'disconnected', `Outbound send failed: ${errMsg}`)
|
|
231
234
|
await startConnector(connectorId)
|
|
232
235
|
result = await sendThroughCurrentInstance()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Connector } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteStoredItem,
|
|
5
|
+
loadConnectorHealth as loadStoredConnectorHealth,
|
|
6
|
+
loadConnectors as loadStoredConnectors,
|
|
7
|
+
loadStoredItem,
|
|
8
|
+
saveConnectors as saveStoredConnectors,
|
|
9
|
+
upsertConnectorHealthEvent as upsertStoredConnectorHealthEvent,
|
|
10
|
+
upsertStoredItem,
|
|
11
|
+
} from '@/lib/server/storage'
|
|
12
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
13
|
+
|
|
14
|
+
export const connectorRepository = createRecordRepository<Connector>(
|
|
15
|
+
'connectors',
|
|
16
|
+
{
|
|
17
|
+
get(id) {
|
|
18
|
+
return loadStoredItem('connectors', id) as Connector | null
|
|
19
|
+
},
|
|
20
|
+
list() {
|
|
21
|
+
return loadStoredConnectors() as Record<string, Connector>
|
|
22
|
+
},
|
|
23
|
+
upsert(id, value) {
|
|
24
|
+
upsertStoredItem('connectors', id, value)
|
|
25
|
+
},
|
|
26
|
+
replace(data) {
|
|
27
|
+
saveStoredConnectors(data)
|
|
28
|
+
},
|
|
29
|
+
patch(id, updater) {
|
|
30
|
+
const current = loadStoredItem('connectors', id) as Connector | null
|
|
31
|
+
const next = updater(current)
|
|
32
|
+
if (next === null) {
|
|
33
|
+
deleteStoredItem('connectors', id)
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
upsertStoredItem('connectors', id, next)
|
|
37
|
+
return next
|
|
38
|
+
},
|
|
39
|
+
delete(id) {
|
|
40
|
+
deleteStoredItem('connectors', id)
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export const loadConnectors = () => connectorRepository.list()
|
|
46
|
+
export const loadConnector = (id: string) => connectorRepository.get(id)
|
|
47
|
+
export const saveConnectors = (items: Record<string, Connector | Record<string, unknown>>) => connectorRepository.replace(items as Record<string, Connector>)
|
|
48
|
+
export const upsertConnector = (id: string, value: Connector | Record<string, unknown>) => connectorRepository.upsert(id, value as Connector)
|
|
49
|
+
export const patchConnector = (id: string, updater: (current: Connector | null) => Connector | null) => connectorRepository.patch(id, updater)
|
|
50
|
+
export const deleteConnector = (id: string) => connectorRepository.delete(id)
|
|
51
|
+
|
|
52
|
+
export function loadConnectorHealth() {
|
|
53
|
+
return loadStoredConnectorHealth()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function upsertConnectorHealthEvent(id: string, value: Record<string, unknown>) {
|
|
57
|
+
upsertStoredConnectorHealthEvent(id, value)
|
|
58
|
+
}
|