@swarmclawai/swarmclaw 1.2.1 → 1.2.3
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 +16 -85
- package/bin/server-cmd.js +64 -1
- package/package.json +2 -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]/devserver/route.ts +13 -19
- package/src/app/api/chats/[id]/messages/route.ts +13 -15
- 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/ip/route.ts +2 -2
- package/src/app/api/preview-server/route.ts +1 -1
- package/src/app/api/projects/[id]/route.ts +7 -46
- package/src/cli/server-cmd.test.js +74 -0
- 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 +19 -9
- package/src/components/chat/message-list.tsx +37 -3
- package/src/components/input/chat-input.tsx +34 -14
- package/src/components/openclaw/openclaw-deploy-panel.tsx +4 -0
- package/src/instrumentation.ts +1 -1
- 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/queued-message-queue.test.ts +23 -1
- package/src/lib/chat/queued-message-queue.ts +11 -2
- package/src/lib/providers/cli-utils.test.ts +124 -0
- 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 +3 -1
- 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 +10 -3
- 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/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 -1926
- 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/message-classifier.test.ts +329 -0
- package/src/lib/server/chat-execution/post-stream-finalization.ts +1 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +5 -6
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +16 -13
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- package/src/lib/server/connectors/connector-repository.ts +58 -0
- package/src/lib/server/connectors/runtime-state.test.ts +117 -0
- package/src/lib/server/credentials/credential-repository.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- 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 -2266
- package/src/lib/server/openclaw/deploy.test.ts +42 -3
- package/src/lib/server/openclaw/deploy.ts +26 -12
- 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/projects/project-repository.ts +36 -0
- package/src/lib/server/projects/project-service.ts +79 -0
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- package/src/lib/server/runtime/alert-dispatch.ts +1 -1
- 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 -1470
- 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 +55 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +2 -2
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- 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 -2061
- 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 +4 -2
- 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 -1377
- 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/schedules/schedule-repository.ts +42 -0
- 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/skill-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-repository.ts +14 -0
- package/src/lib/server/storage.ts +13 -24
- package/src/lib/server/tasks/task-repository.ts +54 -0
- package/src/lib/server/usage/usage-repository.ts +30 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/strip-internal-metadata.test.ts +42 -41
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +21 -5
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
|
@@ -73,13 +73,24 @@ function buildExtensionCapabilityLines(enabledExtensions: string[], opts?: { del
|
|
|
73
73
|
return lines
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
const DISPLAY_TOOL_ALIASES: Record<string, string[]> = {
|
|
77
|
+
files: ['send_file'],
|
|
78
|
+
shell: ['sandbox_exec', 'sandbox_list_runtimes'],
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
function buildExactToolNameList(enabledExtensions: string[]): string[] {
|
|
77
82
|
const planning = getEnabledToolPlanningView(enabledExtensions)
|
|
83
|
+
const displayAliases = dedup(
|
|
84
|
+
enabledExtensions
|
|
85
|
+
.map((toolId) => canonicalizeExtensionId(toolId))
|
|
86
|
+
.flatMap((toolId) => DISPLAY_TOOL_ALIASES[toolId] || []),
|
|
87
|
+
)
|
|
78
88
|
const extensionToolNames = getExtensionManager()
|
|
79
89
|
.getTools(enabledExtensions)
|
|
80
90
|
.map(({ tool }) => tool.name)
|
|
81
91
|
const combined = [
|
|
82
92
|
...planning.displayToolIds,
|
|
93
|
+
...displayAliases,
|
|
83
94
|
...planning.entries.map((entry) => entry.toolName),
|
|
84
95
|
...extensionToolNames,
|
|
85
96
|
]
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import type { Session, Agent } from '@/types'
|
|
10
10
|
import type { ActiveProjectContext } from '@/lib/server/project-context'
|
|
11
11
|
import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
|
|
12
|
-
import {
|
|
12
|
+
import { getAgent, listAgents } from '@/lib/server/agents/agent-repository'
|
|
13
|
+
import { loadSkills } from '@/lib/server/skills/skill-repository'
|
|
13
14
|
import { buildRuntimeSkillPromptBlocks, resolveRuntimeSkills } from '@/lib/server/skills/runtime-skill-resolver'
|
|
14
15
|
import { resolveTeam } from '@/lib/server/agents/team-resolution'
|
|
15
16
|
|
|
@@ -127,8 +128,7 @@ export async function buildAgentAwarenessSection(
|
|
|
127
128
|
// Load agent to get delegation settings so the awareness block respects them
|
|
128
129
|
let delegationOpts: { delegationTargetMode?: 'all' | 'selected'; delegationTargetAgentIds?: string[] } | undefined
|
|
129
130
|
try {
|
|
130
|
-
const
|
|
131
|
-
const agent = agents[session.agentId]
|
|
131
|
+
const agent = getAgent(session.agentId)
|
|
132
132
|
if (agent?.delegationTargetMode === 'selected') {
|
|
133
133
|
delegationOpts = {
|
|
134
134
|
delegationTargetMode: 'selected',
|
|
@@ -334,7 +334,7 @@ export function buildCoordinatorSection(
|
|
|
334
334
|
): string | null {
|
|
335
335
|
if (!agent || agent.role !== 'coordinator') return null
|
|
336
336
|
|
|
337
|
-
const allAgents =
|
|
337
|
+
const allAgents = listAgents()
|
|
338
338
|
const selfId = agent.id
|
|
339
339
|
|
|
340
340
|
// Resolve which agents this coordinator can delegate to
|
|
@@ -511,8 +511,7 @@ export function buildCliDelegationContext(opts: {
|
|
|
511
511
|
// Team roster summary
|
|
512
512
|
if (opts.agent?.id) {
|
|
513
513
|
try {
|
|
514
|
-
const
|
|
515
|
-
const team = resolveTeam(opts.agent.id, agents)
|
|
514
|
+
const team = resolveTeam(opts.agent.id, listAgents())
|
|
516
515
|
if (team.mode === 'team') {
|
|
517
516
|
const teammates = [
|
|
518
517
|
...(team.coordinator ? [`${team.coordinator.name} (coordinator)`] : []),
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listAgentIncidents } from '@/lib/server/autonomy/supervisor-incident-repository'
|
|
2
|
+
import { listAgents } from '@/lib/server/agents/agent-repository'
|
|
3
|
+
import { loadChatrooms } from '@/lib/server/chatrooms/chatroom-repository'
|
|
4
|
+
import { loadConnectors } from '@/lib/server/connectors/connector-repository'
|
|
5
|
+
import { loadMission } from '@/lib/server/missions/mission-repository'
|
|
6
|
+
import { loadSchedules } from '@/lib/server/schedules/schedule-repository'
|
|
7
|
+
import { loadTasks } from '@/lib/server/tasks/task-repository'
|
|
8
|
+
import { loadUsage } from '@/lib/server/usage/usage-repository'
|
|
2
9
|
import { listPersistedRuns } from '@/lib/server/runtime/run-ledger'
|
|
3
10
|
import type { BoardTask, Mission, Schedule, SupervisorIncident, SessionRunRecord } from '@/types'
|
|
4
11
|
|
|
@@ -258,7 +265,7 @@ export function buildPlatformStatusSummary(): string {
|
|
|
258
265
|
const oneDayAgo = now - 86_400_000
|
|
259
266
|
|
|
260
267
|
// Agents
|
|
261
|
-
const agents = Object.values(
|
|
268
|
+
const agents = Object.values(listAgents())
|
|
262
269
|
const activeAgents = agents.filter((a) => a.lastUsedAt && a.lastUsedAt > oneHourAgo)
|
|
263
270
|
|
|
264
271
|
// Tasks
|
|
@@ -276,7 +283,7 @@ export function buildPlatformStatusSummary(): string {
|
|
|
276
283
|
const connectors = Object.values(loadConnectors())
|
|
277
284
|
const connectorLines: string[] = []
|
|
278
285
|
for (const c of connectors) {
|
|
279
|
-
const status = c.status === 'running'
|
|
286
|
+
const status = c.status === 'running' ? '✓' : `✗ (${c.status})`
|
|
280
287
|
connectorLines.push(`${c.platform || c.id} ${status}`)
|
|
281
288
|
}
|
|
282
289
|
|
|
@@ -285,8 +292,7 @@ export function buildPlatformStatusSummary(): string {
|
|
|
285
292
|
const activeChatrooms = chatrooms.filter((c) => !c.archivedAt && !c.temporary)
|
|
286
293
|
|
|
287
294
|
// Incidents
|
|
288
|
-
const
|
|
289
|
-
const recentIncidents = allIncidents.filter((i) => i.createdAt > oneDayAgo)
|
|
295
|
+
const recentIncidents = listAgentIncidents().filter((i) => i.createdAt > oneDayAgo)
|
|
290
296
|
const warnings = recentIncidents.filter((i) => i.severity === 'medium').length
|
|
291
297
|
const errors = recentIncidents.filter((i) => i.severity === 'high').length
|
|
292
298
|
|
|
@@ -348,8 +354,7 @@ export function buildSituationalAwarenessBlock(input: SituationalAwarenessInput)
|
|
|
348
354
|
|
|
349
355
|
const failedRuns = listPersistedRuns({ sessionId, status: 'failed', limit: 10 })
|
|
350
356
|
|
|
351
|
-
const
|
|
352
|
-
const incidents = Object.values(allIncidents).filter((i) => i.agentId === agentId)
|
|
357
|
+
const incidents = listAgentIncidents(agentId)
|
|
353
358
|
|
|
354
359
|
const mission = missionId ? loadMission(missionId) : null
|
|
355
360
|
|
|
@@ -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,
|
|
@@ -228,13 +229,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
228
229
|
// fallbackCredentialIds is intentionally accepted for compatibility with caller signatures.
|
|
229
230
|
void fallbackCredentialIds
|
|
230
231
|
|
|
232
|
+
const sessionAgent = session.agentId ? getAgent(session.agentId) : null
|
|
233
|
+
|
|
231
234
|
// Resolve agent's thinking level for provider-native params
|
|
232
235
|
let agentThinkingLevel: 'minimal' | 'low' | 'medium' | 'high' | undefined
|
|
233
236
|
if (session.thinkingLevel) {
|
|
234
237
|
agentThinkingLevel = session.thinkingLevel
|
|
235
|
-
} else if (
|
|
236
|
-
|
|
237
|
-
agentThinkingLevel = agentsForThinking[session.agentId]?.thinkingLevel
|
|
238
|
+
} else if (sessionAgent) {
|
|
239
|
+
agentThinkingLevel = sessionAgent.thinkingLevel
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
const llm = buildChatModel({
|
|
@@ -320,9 +322,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
320
322
|
let agentResponseStyle: 'concise' | 'normal' | 'detailed' | null = null
|
|
321
323
|
let agentResponseMaxChars: number | null = null
|
|
322
324
|
const activeProjectContext = resolveActiveProjectContext(session)
|
|
323
|
-
if (
|
|
324
|
-
const
|
|
325
|
-
const agent = agents[session.agentId]
|
|
325
|
+
if (sessionAgent) {
|
|
326
|
+
const agent = sessionAgent
|
|
326
327
|
isCoordinatorAgent = agent?.role === 'coordinator'
|
|
327
328
|
agentDelegationEnabled = agent?.delegationEnabled === true
|
|
328
329
|
agentDelegationTargetMode = agent?.delegationTargetMode === 'selected' ? 'selected' : 'all'
|
|
@@ -415,10 +416,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
415
416
|
|
|
416
417
|
// Proactive memory recall — full mode only
|
|
417
418
|
{
|
|
418
|
-
const agents = loadAgents()
|
|
419
|
-
const agentForMemory = session.agentId ? agents[session.agentId] : null
|
|
420
419
|
const memoryResult = await buildProactiveMemorySection(
|
|
421
|
-
session,
|
|
420
|
+
session, sessionAgent, message, activeProjectContext.projectRoot,
|
|
422
421
|
isMinimalPrompt, currentThreadRecallRequest,
|
|
423
422
|
)
|
|
424
423
|
if (memoryResult.section) promptParts.push(memoryResult.section)
|
|
@@ -926,14 +925,18 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
926
925
|
&& !abortController.signal.aborted)
|
|
927
926
|
|| (isTransientProviderError && !abortController.signal.aborted)
|
|
928
927
|
|
|
929
|
-
const
|
|
930
|
-
console[logLevel](`[stream-agent-chat] Error in streamEvents iteration=${iteration}`, {
|
|
928
|
+
const logPayload = {
|
|
931
929
|
errName, errMsg, errStack,
|
|
932
930
|
statusCode, retryAfterMs: extractedRetryAfterMs,
|
|
933
931
|
isRecursionError, isContextOverflow, isTransientAbort,
|
|
934
932
|
hasToolCalls: state.hasToolCalls, fullTextLen: state.fullText.length,
|
|
935
933
|
parentAborted: abortController.signal.aborted,
|
|
936
|
-
}
|
|
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
|
+
}
|
|
937
940
|
|
|
938
941
|
if (timers.requiredToolKickoffTimedOut && limits.canContinue('required_tool') && !abortController.signal.aborted) {
|
|
939
942
|
const hadPartialOutput = state.fullText.length > iterationStartState.fullText.length || state.streamedToolEvents.length > iterationStartState.toolEventCount
|
|
@@ -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)
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
})
|
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Mission, MissionEvent } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteMission as deleteStoredMission,
|
|
5
|
+
loadMission as loadStoredMission,
|
|
6
|
+
loadMissionEvent as loadStoredMissionEvent,
|
|
7
|
+
loadMissionEvents as loadStoredMissionEvents,
|
|
8
|
+
loadMissions as loadStoredMissions,
|
|
9
|
+
patchMission as patchStoredMission,
|
|
10
|
+
saveMissionEvents as saveStoredMissionEvents,
|
|
11
|
+
saveMissions as saveStoredMissions,
|
|
12
|
+
upsertMission as upsertStoredMission,
|
|
13
|
+
upsertMissionEvent as upsertStoredMissionEvent,
|
|
14
|
+
upsertMissionEvents as upsertStoredMissionEvents,
|
|
15
|
+
} from '@/lib/server/storage'
|
|
16
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
17
|
+
|
|
18
|
+
export const missionRepository = createRecordRepository<Mission>(
|
|
19
|
+
'missions',
|
|
20
|
+
{
|
|
21
|
+
get(id) {
|
|
22
|
+
return loadStoredMission(id) as Mission | null
|
|
23
|
+
},
|
|
24
|
+
list() {
|
|
25
|
+
return loadStoredMissions() as Record<string, Mission>
|
|
26
|
+
},
|
|
27
|
+
upsert(id, value) {
|
|
28
|
+
upsertStoredMission(id, value as Mission)
|
|
29
|
+
},
|
|
30
|
+
replace(data) {
|
|
31
|
+
saveStoredMissions(data as Record<string, Mission>)
|
|
32
|
+
},
|
|
33
|
+
patch(id, updater) {
|
|
34
|
+
return patchStoredMission(id, updater as (current: Mission | null) => Mission | null) as Mission | null
|
|
35
|
+
},
|
|
36
|
+
delete(id) {
|
|
37
|
+
deleteStoredMission(id)
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
export const missionEventRepository = createRecordRepository<MissionEvent>(
|
|
43
|
+
'mission-events',
|
|
44
|
+
{
|
|
45
|
+
get(id) {
|
|
46
|
+
return loadStoredMissionEvent(id) as MissionEvent | null
|
|
47
|
+
},
|
|
48
|
+
list() {
|
|
49
|
+
return loadStoredMissionEvents() as Record<string, MissionEvent>
|
|
50
|
+
},
|
|
51
|
+
upsert(id, value) {
|
|
52
|
+
upsertStoredMissionEvent(id, value as MissionEvent)
|
|
53
|
+
},
|
|
54
|
+
upsertMany(entries) {
|
|
55
|
+
upsertStoredMissionEvents(entries as Array<[string, MissionEvent]>)
|
|
56
|
+
},
|
|
57
|
+
replace(data) {
|
|
58
|
+
saveStoredMissionEvents(data as Record<string, MissionEvent>)
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
export const loadMissions = () => missionRepository.list()
|
|
64
|
+
export const loadMission = (id: string) => missionRepository.get(id)
|
|
65
|
+
export const saveMissions = (items: Record<string, Mission | Record<string, unknown>>) => missionRepository.replace(items as Record<string, Mission>)
|
|
66
|
+
export const upsertMission = (id: string, value: Mission | Record<string, unknown>) => missionRepository.upsert(id, value as Mission)
|
|
67
|
+
export const patchMission = (id: string, updater: (current: Mission | null) => Mission | null) => missionRepository.patch(id, updater)
|
|
68
|
+
export const deleteMission = (id: string) => missionRepository.delete(id)
|
|
69
|
+
|
|
70
|
+
export const loadMissionEvents = () => missionEventRepository.list()
|
|
71
|
+
export const loadMissionEvent = (id: string) => missionEventRepository.get(id)
|
|
72
|
+
export const saveMissionEvents = (items: Record<string, MissionEvent | Record<string, unknown>>) => missionEventRepository.replace(items as Record<string, MissionEvent>)
|
|
73
|
+
export const upsertMissionEvent = (id: string, value: MissionEvent | Record<string, unknown>) => missionEventRepository.upsert(id, value as MissionEvent)
|
|
74
|
+
export const upsertMissionEvents = (entries: Array<[string, MissionEvent | Record<string, unknown>]>) => missionEventRepository.upsertMany(entries as Array<[string, MissionEvent]>)
|