@swarmclawai/swarmclaw 1.2.3 → 1.2.5
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 +20 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/models/route.test.ts +60 -0
- package/src/app/api/providers/[id]/models/route.ts +33 -1
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/[id]/route.ts +30 -1
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +15 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +51 -25
- package/src/components/agents/inspector-panel.tsx +15 -4
- package/src/components/auth/setup-wizard/index.tsx +27 -18
- package/src/components/auth/setup-wizard/shared.tsx +2 -2
- package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
- package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
- package/src/components/auth/setup-wizard/types.ts +6 -4
- package/src/components/auth/setup-wizard/utils.test.ts +38 -8
- package/src/components/auth/setup-wizard/utils.ts +14 -8
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +150 -77
- package/src/components/providers/provider-sheet.tsx +102 -77
- package/src/components/shared/model-combobox.tsx +5 -4
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/agent-provider-options.test.ts +152 -0
- package/src/lib/agent-provider-options.ts +84 -0
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +78 -0
- package/src/lib/providers/index.ts +13 -10
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +6 -6
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +42 -215
- package/src/lib/server/storage-normalization.ts +98 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +288 -22
- package/src/views/settings/section-providers.tsx +2 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs'
|
|
9
9
|
import os from 'node:os'
|
|
10
10
|
import path from 'node:path'
|
|
11
|
-
import type { MessageToolEvent } from '@/types'
|
|
11
|
+
import type { Message, MessageToolEvent } from '@/types'
|
|
12
12
|
import { extractSuggestions } from '@/lib/server/suggestions'
|
|
13
13
|
import { isSuccessfulMemoryMutationToolEvent } from '@/lib/server/chat-execution/memory-mutation-tools'
|
|
14
14
|
import type { MessageClassification } from '@/lib/server/chat-execution/message-classifier'
|
|
@@ -108,7 +108,7 @@ function looksLikeIncompleteDeliverableResponse(text: string): boolean {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
function hasRecentDeliverableContext(
|
|
111
|
-
history:
|
|
111
|
+
history: Message[] | undefined,
|
|
112
112
|
userMessage: string,
|
|
113
113
|
): boolean {
|
|
114
114
|
if (!Array.isArray(history) || history.length === 0) return false
|
|
@@ -116,7 +116,7 @@ function hasRecentDeliverableContext(
|
|
|
116
116
|
if (!trimmed || trimmed.length > 160) return false
|
|
117
117
|
return history
|
|
118
118
|
.slice(-8)
|
|
119
|
-
.some((entry) => entry.role === 'user' &&
|
|
119
|
+
.some((entry) => entry.role === 'user' && entry.semantics?.isDeliverableTask === true)
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const ARTIFACT_PATH_EXT_RE = /\.(?:md|txt|html?|json|csv|ya?ml|xml|pdf|png|jpe?g|webp|gif|svg|zip|ts|tsx|js|jsx|mjs|cjs|py|sql|sh)$/i
|
|
@@ -245,7 +245,7 @@ export function shouldForceExternalExecutionFollowthrough(params: {
|
|
|
245
245
|
toolEvents: MessageToolEvent[]
|
|
246
246
|
classification?: MessageClassification | null
|
|
247
247
|
}): boolean {
|
|
248
|
-
const isTransactional = params.classification
|
|
248
|
+
const isTransactional = params.classification?.walletIntent === 'transactional'
|
|
249
249
|
if (!isTransactional) return false
|
|
250
250
|
if (!params.hasToolCalls || params.toolEvents.length < 4) return false
|
|
251
251
|
if (hasStateChangingWalletEvidence(params.toolEvents)) return false
|
|
@@ -266,7 +266,7 @@ export function shouldForceExternalExecutionKickoffFollowthrough(params: {
|
|
|
266
266
|
toolEvents: MessageToolEvent[]
|
|
267
267
|
classification?: MessageClassification | null
|
|
268
268
|
}): boolean {
|
|
269
|
-
const isTransactional = params.classification
|
|
269
|
+
const isTransactional = params.classification?.walletIntent === 'transactional'
|
|
270
270
|
if (!isTransactional) return false
|
|
271
271
|
if (params.hasToolCalls || params.toolEvents.length > 0) return false
|
|
272
272
|
|
|
@@ -289,11 +289,11 @@ export function shouldForceDeliverableFollowthrough(params: {
|
|
|
289
289
|
hasToolCalls: boolean
|
|
290
290
|
toolEvents: MessageToolEvent[]
|
|
291
291
|
cwd?: string
|
|
292
|
-
history?:
|
|
292
|
+
history?: Message[]
|
|
293
293
|
classification?: MessageClassification | null
|
|
294
294
|
}): boolean {
|
|
295
295
|
const recentDeliverableContext = hasRecentDeliverableContext(params.history, params.userMessage)
|
|
296
|
-
const isDeliverable = params.classification
|
|
296
|
+
const isDeliverable = params.classification?.isDeliverableTask === true
|
|
297
297
|
const deliverableIntent = isDeliverable || recentDeliverableContext
|
|
298
298
|
const requestedArtifacts = getRequestedArtifactStatus({
|
|
299
299
|
userMessage: params.userMessage,
|
|
@@ -744,6 +744,9 @@ export function buildContinuationPrompt(params: {
|
|
|
744
744
|
cwd?: string
|
|
745
745
|
frequencyLimitedToolName?: string
|
|
746
746
|
sessionExtensions?: string[]
|
|
747
|
+
isCoordinatorAgent?: boolean
|
|
748
|
+
recommendedDelegateName?: string | null
|
|
749
|
+
delegationRationale?: string | null
|
|
747
750
|
}): string | null {
|
|
748
751
|
switch (params.type) {
|
|
749
752
|
case 'memory_write_followthrough':
|
|
@@ -819,12 +822,24 @@ export function buildContinuationPrompt(params: {
|
|
|
819
822
|
].filter(Boolean).join('\n')
|
|
820
823
|
|
|
821
824
|
case 'coordinator_delegation_nudge':
|
|
822
|
-
return
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
825
|
+
return params.isCoordinatorAgent
|
|
826
|
+
? [
|
|
827
|
+
'IMPORTANT: You have specialist workers available but you have been doing substantial work directly with tools.',
|
|
828
|
+
params.recommendedDelegateName
|
|
829
|
+
? `Delegate the remaining execution via \`spawn_subagent\` to ${params.recommendedDelegateName} now.`
|
|
830
|
+
: 'You MUST delegate the remaining work via `spawn_subagent` to the appropriate specialist worker NOW.',
|
|
831
|
+
'As a coordinator, your job is to orchestrate — not to do the work yourself. Direct tool use is only for quick lookups and validation.',
|
|
832
|
+
params.delegationRationale ? `Reason: ${params.delegationRationale}.` : '',
|
|
833
|
+
'Review the workers listed in your system prompt and delegate immediately.',
|
|
834
|
+
].filter(Boolean).join('\n')
|
|
835
|
+
: [
|
|
836
|
+
'You have delegation available and a teammate is a materially better fit for the remaining work.',
|
|
837
|
+
params.recommendedDelegateName
|
|
838
|
+
? `Use \`spawn_subagent\` to hand the execution to ${params.recommendedDelegateName} now.`
|
|
839
|
+
: 'Use `spawn_subagent` to hand the execution to the best-fit teammate now.',
|
|
840
|
+
params.delegationRationale ? `Reason: ${params.delegationRationale}.` : '',
|
|
841
|
+
'Keep your direct tool use to reconnaissance, validation, or synthesis unless delegation is blocked.',
|
|
842
|
+
].filter(Boolean).join('\n')
|
|
828
843
|
|
|
829
844
|
case 'loop_recovery': {
|
|
830
845
|
const freqTool = params.frequencyLimitedToolName
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Chatroom, Agent } from '@/types'
|
|
2
|
-
import {
|
|
2
|
+
import { patchChatroom } from '@/lib/server/chatrooms/chatroom-repository'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -35,25 +35,33 @@ export function isImplicitlyMentioned(text: string, agent: Agent): boolean {
|
|
|
35
35
|
* Useful for acknowledging tasks or agreeing with teammates.
|
|
36
36
|
*/
|
|
37
37
|
export function addAgentReaction(chatroomId: string, messageId: string, agentId: string, emoji: string) {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const updated = patchChatroom(chatroomId, (current) => {
|
|
39
|
+
const chatroom = current as Chatroom | null
|
|
40
|
+
if (!chatroom) return null
|
|
41
|
+
const message = chatroom.messages.find(m => m.id === messageId)
|
|
42
|
+
if (!message) return chatroom
|
|
43
|
+
if (message.reactions.some(r => r.reactorId === agentId && r.emoji === emoji)) return chatroom
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
return {
|
|
46
|
+
...chatroom,
|
|
47
|
+
messages: chatroom.messages.map((entry) => (
|
|
48
|
+
entry.id !== messageId
|
|
49
|
+
? entry
|
|
50
|
+
: {
|
|
51
|
+
...entry,
|
|
52
|
+
reactions: [
|
|
53
|
+
...entry.reactions,
|
|
54
|
+
{
|
|
55
|
+
emoji,
|
|
56
|
+
reactorId: agentId,
|
|
57
|
+
time: Date.now(),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
)),
|
|
62
|
+
}
|
|
52
63
|
})
|
|
53
|
-
|
|
54
|
-
chatrooms[chatroomId] = chatroom
|
|
55
|
-
saveChatrooms(chatrooms)
|
|
56
|
-
notify(`chatroom:${chatroomId}`)
|
|
64
|
+
if (updated) notify(`chatroom:${chatroomId}`)
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
/**
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import os from 'os'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import { loadSettings, loadSkills, loadCredentials, decryptKey, loadSessions, saveSessions } from '@/lib/server/storage'
|
|
5
4
|
import { buildCurrentDateTimePromptContext } from '@/lib/server/prompt-runtime-context'
|
|
6
5
|
import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
|
|
7
6
|
import { genId } from '@/lib/id'
|
|
8
7
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
9
8
|
import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
9
|
+
import { loadCredential, decryptKey } from '@/lib/server/credentials/credential-repository'
|
|
10
10
|
import { resolveProviderApiEndpoint, resolveProviderCredentialId } from '@/lib/server/provider-endpoint'
|
|
11
|
+
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
11
12
|
import { buildRuntimeSkillPromptBlocks, resolveRuntimeSkills } from '@/lib/server/skills/runtime-skill-resolver'
|
|
13
|
+
import { loadSkills } from '@/lib/server/skills/skill-repository'
|
|
14
|
+
import { loadSession, patchSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
15
|
+
import { appendMessage } from '@/lib/server/messages/message-repository'
|
|
12
16
|
import type { Chatroom, ChatroomMember, Agent, Session, Message, ChatroomMessage } from '@/types'
|
|
13
17
|
import { getEnabledCapabilityIds, getEnabledToolIds } from '@/lib/capability-selection'
|
|
14
18
|
|
|
@@ -16,8 +20,7 @@ import { getEnabledCapabilityIds, getEnabledToolIds } from '@/lib/capability-sel
|
|
|
16
20
|
export function resolveApiKey(credentialId: string | null | undefined): string | null {
|
|
17
21
|
const resolvedCredentialId = resolveProviderCredentialId({ credentialId })
|
|
18
22
|
if (!resolvedCredentialId) return null
|
|
19
|
-
const
|
|
20
|
-
const cred = creds[resolvedCredentialId]
|
|
23
|
+
const cred = loadCredential(resolvedCredentialId)
|
|
21
24
|
if (!cred?.encryptedKey) return null
|
|
22
25
|
try { return decryptKey(cred.encryptedKey) } catch { return null }
|
|
23
26
|
}
|
|
@@ -136,6 +139,7 @@ export function parseMentions(
|
|
|
136
139
|
|
|
137
140
|
// Check if the only explicit matches are the sender — if so, treat as "no explicit mentions"
|
|
138
141
|
const senderId = opts?.senderId
|
|
142
|
+
const explicitSelfMentioned = senderId ? mentioned.includes(senderId) : false
|
|
139
143
|
const explicitNonSelf = senderId ? mentioned.filter((id) => id !== senderId) : mentioned
|
|
140
144
|
|
|
141
145
|
// 2. Reply-based implicit mention
|
|
@@ -158,8 +162,9 @@ export function parseMentions(
|
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
//
|
|
162
|
-
|
|
165
|
+
// Preserve explicit self-mentions so agents can intentionally address themselves.
|
|
166
|
+
if (!senderId || explicitSelfMentioned) return mentioned
|
|
167
|
+
return mentioned.filter((mid) => mid !== senderId)
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
export function resolveReplyTargetAgentId(
|
|
@@ -312,9 +317,8 @@ export function ensureSyntheticSession(agent: Agent, chatroomId: string): Sessio
|
|
|
312
317
|
const roomWorkspace = resolveChatroomWorkspaceDir(chatroomId)
|
|
313
318
|
fs.mkdirSync(roomWorkspace, { recursive: true })
|
|
314
319
|
const sessionId = resolveSyntheticSessionId(chatroomId, agent.id)
|
|
315
|
-
const sessions = loadSessions()
|
|
316
320
|
const now = Date.now()
|
|
317
|
-
const existing =
|
|
321
|
+
const existing = loadSession(sessionId)
|
|
318
322
|
const session: Session = existing
|
|
319
323
|
? applyResolvedRoute({
|
|
320
324
|
...existing,
|
|
@@ -348,8 +352,7 @@ export function ensureSyntheticSession(agent: Agent, chatroomId: string): Sessio
|
|
|
348
352
|
}
|
|
349
353
|
if (session.codexThreadId === undefined) session.codexThreadId = null
|
|
350
354
|
if (session.opencodeSessionId === undefined) session.opencodeSessionId = null
|
|
351
|
-
|
|
352
|
-
saveSessions(sessions)
|
|
355
|
+
saveSession(sessionId, session)
|
|
353
356
|
return session
|
|
354
357
|
}
|
|
355
358
|
|
|
@@ -360,18 +363,16 @@ export function appendSyntheticSessionMessage(
|
|
|
360
363
|
): void {
|
|
361
364
|
const trimmed = String(text || '').trim()
|
|
362
365
|
if (!trimmed) return
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
if (!session) return
|
|
366
|
-
if (!Array.isArray(session.messages)) session.messages = []
|
|
367
|
-
session.messages.push({
|
|
366
|
+
const timestamp = Date.now()
|
|
367
|
+
appendMessage(sessionId, {
|
|
368
368
|
role,
|
|
369
369
|
text: trimmed,
|
|
370
|
-
time:
|
|
370
|
+
time: timestamp,
|
|
371
|
+
})
|
|
372
|
+
patchSession(sessionId, (current) => {
|
|
373
|
+
if (!current) return null
|
|
374
|
+
return { ...current, lastActiveAt: timestamp }
|
|
371
375
|
})
|
|
372
|
-
session.lastActiveAt = Date.now()
|
|
373
|
-
sessions[sessionId] = session
|
|
374
|
-
saveSessions(sessions)
|
|
375
376
|
}
|
|
376
377
|
|
|
377
378
|
/** Build agent's system prompt including skills and identity context */
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Chatroom } from '@/types'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
deleteStoredItem,
|
|
4
5
|
loadChatroom as loadStoredChatroom,
|
|
5
6
|
loadChatrooms as loadStoredChatrooms,
|
|
7
|
+
patchStoredItem,
|
|
6
8
|
saveChatrooms as saveStoredChatrooms,
|
|
7
9
|
upsertChatroom as upsertStoredChatroom,
|
|
10
|
+
upsertStoredItems,
|
|
8
11
|
} from '@/lib/server/storage'
|
|
9
12
|
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
10
13
|
|
|
@@ -20,13 +23,26 @@ export const chatroomRepository = createRecordRepository<Chatroom>(
|
|
|
20
23
|
upsert(id, value) {
|
|
21
24
|
upsertStoredChatroom(id, value as Chatroom)
|
|
22
25
|
},
|
|
26
|
+
upsertMany(entries) {
|
|
27
|
+
upsertStoredItems('chatrooms', entries as Array<[string, Chatroom]>)
|
|
28
|
+
},
|
|
29
|
+
patch(id, updater) {
|
|
30
|
+
return patchStoredItem('chatrooms', id, updater as (current: Chatroom | null) => Chatroom | null) as Chatroom | null
|
|
31
|
+
},
|
|
23
32
|
replace(data) {
|
|
24
33
|
saveStoredChatrooms(data)
|
|
25
34
|
},
|
|
35
|
+
delete(id) {
|
|
36
|
+
deleteStoredItem('chatrooms', id)
|
|
37
|
+
},
|
|
26
38
|
},
|
|
27
39
|
)
|
|
28
40
|
|
|
29
41
|
export const loadChatrooms = () => chatroomRepository.list()
|
|
30
42
|
export const loadChatroom = (id: string) => chatroomRepository.get(id)
|
|
43
|
+
export const loadChatroomMany = (ids: string[]) => chatroomRepository.getMany(ids)
|
|
31
44
|
export const saveChatrooms = (items: Record<string, Chatroom | Record<string, unknown>>) => chatroomRepository.replace(items as Record<string, Chatroom>)
|
|
32
45
|
export const upsertChatroom = (id: string, value: Chatroom | Record<string, unknown>) => chatroomRepository.upsert(id, value as Chatroom)
|
|
46
|
+
export const upsertChatrooms = (entries: Array<[string, Chatroom | Record<string, unknown>]>) => chatroomRepository.upsertMany(entries as Array<[string, Chatroom]>)
|
|
47
|
+
export const patchChatroom = (id: string, updater: (current: Chatroom | null) => Chatroom | null) => chatroomRepository.patch(id, updater)
|
|
48
|
+
export const deleteChatroom = (id: string) => chatroomRepository.delete(id)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import type { Agent, Chatroom } from '@/types'
|
|
4
|
+
import {
|
|
5
|
+
ensureChatroomRoutingGuidance,
|
|
6
|
+
selectChatroomRecipients,
|
|
7
|
+
synthesizeRoutingGuidanceFromRules,
|
|
8
|
+
} from './chatroom-routing'
|
|
9
|
+
|
|
10
|
+
const agents: Record<string, Agent> = {
|
|
11
|
+
ops: {
|
|
12
|
+
id: 'ops',
|
|
13
|
+
name: 'Ops',
|
|
14
|
+
description: 'Handles deploys and infrastructure',
|
|
15
|
+
provider: 'openai',
|
|
16
|
+
model: 'gpt-test',
|
|
17
|
+
systemPrompt: '',
|
|
18
|
+
capabilities: ['deploy', 'infrastructure'],
|
|
19
|
+
},
|
|
20
|
+
design: {
|
|
21
|
+
id: 'design',
|
|
22
|
+
name: 'Design',
|
|
23
|
+
description: 'Handles design critique and UI polish',
|
|
24
|
+
provider: 'openai',
|
|
25
|
+
model: 'gpt-test',
|
|
26
|
+
systemPrompt: '',
|
|
27
|
+
capabilities: ['design', 'ui'],
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeChatroom(overrides: Partial<Chatroom> = {}): Chatroom {
|
|
32
|
+
return {
|
|
33
|
+
id: 'room-1',
|
|
34
|
+
name: 'Test Room',
|
|
35
|
+
description: 'General routing test room',
|
|
36
|
+
agentIds: ['ops', 'design'],
|
|
37
|
+
messages: [],
|
|
38
|
+
createdAt: 1,
|
|
39
|
+
updatedAt: 1,
|
|
40
|
+
...overrides,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test('synthesizes guidance from legacy routing rules and migrates the chatroom', () => {
|
|
45
|
+
const chatroom = makeChatroom({
|
|
46
|
+
routingRules: [
|
|
47
|
+
{ id: 'rule-1', type: 'keyword', keywords: ['deploy', 'release'], agentId: 'ops', priority: 1 },
|
|
48
|
+
{ id: 'rule-2', type: 'capability', pattern: 'design review', agentId: 'design', priority: 2 },
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const guidance = synthesizeRoutingGuidanceFromRules(chatroom.routingRules, agents)
|
|
53
|
+
assert.match(String(guidance || ''), /deploy/i)
|
|
54
|
+
assert.match(String(guidance || ''), /Design/i)
|
|
55
|
+
|
|
56
|
+
const changed = ensureChatroomRoutingGuidance(chatroom, agents)
|
|
57
|
+
assert.equal(changed, true)
|
|
58
|
+
assert.equal(typeof chatroom.routingGuidance, 'string')
|
|
59
|
+
assert.equal(chatroom.routingRules, undefined)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('selects only member ids returned by the selector model', async () => {
|
|
63
|
+
const chatroom = makeChatroom({
|
|
64
|
+
routingGuidance: 'Route deployment incidents to Ops. Prefer Design for UI critique.',
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const selected = await selectChatroomRecipients({
|
|
68
|
+
text: 'Please diagnose the failed deployment.',
|
|
69
|
+
chatroom,
|
|
70
|
+
agentsById: agents,
|
|
71
|
+
}, {
|
|
72
|
+
generateText: async () => '{"agentIds":["ops","non-member","ops"]}',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(selected, ['ops'])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('fails open to no inferred mentions when there is no guidance or the selector output is invalid', async () => {
|
|
79
|
+
const unguided = await selectChatroomRecipients({
|
|
80
|
+
text: 'Anyone here?',
|
|
81
|
+
chatroom: makeChatroom(),
|
|
82
|
+
agentsById: agents,
|
|
83
|
+
}, {
|
|
84
|
+
generateText: async () => '{"agentIds":["ops"]}',
|
|
85
|
+
})
|
|
86
|
+
assert.deepEqual(unguided, [])
|
|
87
|
+
|
|
88
|
+
const invalid = await selectChatroomRecipients({
|
|
89
|
+
text: 'Please review the new layout.',
|
|
90
|
+
chatroom: makeChatroom({ routingGuidance: 'Prefer Design for UI review.' }),
|
|
91
|
+
agentsById: agents,
|
|
92
|
+
}, {
|
|
93
|
+
generateText: async () => 'not-json',
|
|
94
|
+
})
|
|
95
|
+
assert.deepEqual(invalid, [])
|
|
96
|
+
})
|
|
@@ -1,66 +1,220 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { HumanMessage } from '@langchain/core/messages'
|
|
2
|
+
import type { Agent, Chatroom, ChatroomRoutingRule } from '@/types'
|
|
3
|
+
import { buildLLM } from '@/lib/server/build-llm'
|
|
4
|
+
import { log } from '@/lib/server/logger'
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* Rules are evaluated in priority order (lower number = higher priority).
|
|
8
|
-
* First match wins — returns the matched agentIds.
|
|
9
|
-
*
|
|
10
|
-
* - 'keyword' rules: case-insensitive substring match against `keywords[]`,
|
|
11
|
-
* or regex match against `pattern`.
|
|
12
|
-
* - 'capability' rules: match `pattern` against each agent's `capabilities[]`.
|
|
13
|
-
*/
|
|
14
|
-
export function evaluateRoutingRules(
|
|
15
|
-
text: string,
|
|
16
|
-
rules: ChatroomRoutingRule[],
|
|
17
|
-
agents: Agent[],
|
|
18
|
-
): string[] {
|
|
19
|
-
if (!rules.length) return []
|
|
6
|
+
const TAG = 'chatroom-routing'
|
|
7
|
+
const SELECTOR_TIMEOUT_MS = 4_000
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
interface ChatroomRecipientSelection {
|
|
10
|
+
agentIds: string[]
|
|
11
|
+
}
|
|
23
12
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
function normalizeGuidance(value: string | null | undefined): string | null {
|
|
14
|
+
const trimmed = typeof value === 'string' ? value.trim() : ''
|
|
15
|
+
return trimmed || null
|
|
16
|
+
}
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
function extractFirstJsonObject(text: string): string | null {
|
|
19
|
+
const source = String(text || '').trim()
|
|
20
|
+
if (!source) return null
|
|
21
|
+
let start = -1
|
|
22
|
+
let depth = 0
|
|
23
|
+
let inString = false
|
|
24
|
+
let escaped = false
|
|
25
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
26
|
+
const char = source[index]
|
|
27
|
+
if (start === -1) {
|
|
28
|
+
if (char === '{') {
|
|
29
|
+
start = index
|
|
30
|
+
depth = 1
|
|
31
31
|
}
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
if (inString) {
|
|
35
|
+
if (escaped) escaped = false
|
|
36
|
+
else if (char === '\\') escaped = true
|
|
37
|
+
else if (char === '"') inString = false
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (char === '"') {
|
|
41
|
+
inString = true
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
if (char === '{') depth += 1
|
|
45
|
+
else if (char === '}') depth -= 1
|
|
46
|
+
if (depth === 0) return source.slice(start, index + 1)
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
51
|
+
function extractModelText(content: unknown): string {
|
|
52
|
+
if (typeof content === 'string') return content
|
|
53
|
+
if (!Array.isArray(content)) return ''
|
|
54
|
+
return content
|
|
55
|
+
.map((part) => (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') ? part.text : '')
|
|
56
|
+
.join('')
|
|
57
|
+
}
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
function parseRecipientSelection(text: string, allowedAgentIds: Set<string>): string[] {
|
|
60
|
+
const jsonText = extractFirstJsonObject(text)
|
|
61
|
+
if (!jsonText) return []
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(jsonText) as Partial<ChatroomRecipientSelection>
|
|
64
|
+
if (!Array.isArray(parsed.agentIds)) return []
|
|
65
|
+
const seen = new Set<string>()
|
|
66
|
+
const selected: string[] = []
|
|
67
|
+
for (const value of parsed.agentIds) {
|
|
68
|
+
if (typeof value !== 'string') continue
|
|
69
|
+
const agentId = value.trim()
|
|
70
|
+
if (!agentId || !allowedAgentIds.has(agentId) || seen.has(agentId)) continue
|
|
71
|
+
seen.add(agentId)
|
|
72
|
+
selected.push(agentId)
|
|
44
73
|
}
|
|
74
|
+
return selected
|
|
75
|
+
} catch {
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatLegacyRule(rule: ChatroomRoutingRule, agentsById: Record<string, Agent | undefined>): string | null {
|
|
81
|
+
const agentName = agentsById[rule.agentId]?.name || rule.agentId
|
|
82
|
+
if (rule.type === 'keyword') {
|
|
83
|
+
const parts = [
|
|
84
|
+
Array.isArray(rule.keywords) && rule.keywords.length > 0
|
|
85
|
+
? `topics or phrases like ${rule.keywords.map((keyword) => `"${keyword}"`).join(', ')}`
|
|
86
|
+
: null,
|
|
87
|
+
rule.pattern ? `messages matching ${JSON.stringify(rule.pattern)}` : null,
|
|
88
|
+
].filter(Boolean)
|
|
89
|
+
if (parts.length === 0) return null
|
|
90
|
+
return `Priority ${rule.priority}: route ${parts.join(' or ')} to ${agentName}.`
|
|
91
|
+
}
|
|
92
|
+
if (!rule.pattern) return null
|
|
93
|
+
return `Priority ${rule.priority}: prefer ${agentName} when the request best fits capability area ${JSON.stringify(rule.pattern)}.`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function synthesizeRoutingGuidanceFromRules(
|
|
97
|
+
rules: ChatroomRoutingRule[] | null | undefined,
|
|
98
|
+
agentsById: Record<string, Agent | undefined>,
|
|
99
|
+
): string | null {
|
|
100
|
+
if (!Array.isArray(rules) || rules.length === 0) return null
|
|
101
|
+
const lines = rules
|
|
102
|
+
.slice()
|
|
103
|
+
.sort((a, b) => a.priority - b.priority)
|
|
104
|
+
.map((rule) => formatLegacyRule(rule, agentsById))
|
|
105
|
+
.filter((line): line is string => typeof line === 'string' && line.trim().length > 0)
|
|
106
|
+
if (lines.length === 0) return null
|
|
107
|
+
return [
|
|
108
|
+
'Legacy routing guidance synthesized from older routing rules. Earlier priorities take precedence when multiple agents could fit.',
|
|
109
|
+
...lines,
|
|
110
|
+
].join('\n')
|
|
111
|
+
}
|
|
45
112
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
113
|
+
export function resolveChatroomRoutingGuidance(
|
|
114
|
+
chatroom: Chatroom,
|
|
115
|
+
agentsById: Record<string, Agent | undefined>,
|
|
116
|
+
): string | null {
|
|
117
|
+
return normalizeGuidance(chatroom.routingGuidance)
|
|
118
|
+
|| synthesizeRoutingGuidanceFromRules(chatroom.routingRules, agentsById)
|
|
119
|
+
}
|
|
49
120
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
121
|
+
export function ensureChatroomRoutingGuidance(
|
|
122
|
+
chatroom: Chatroom,
|
|
123
|
+
agentsById: Record<string, Agent | undefined>,
|
|
124
|
+
): boolean {
|
|
125
|
+
const guidance = resolveChatroomRoutingGuidance(chatroom, agentsById)
|
|
126
|
+
const nextGuidance = normalizeGuidance(guidance)
|
|
127
|
+
const hadRules = Array.isArray(chatroom.routingRules) && chatroom.routingRules.length > 0
|
|
128
|
+
const guidanceChanged = nextGuidance !== normalizeGuidance(chatroom.routingGuidance)
|
|
129
|
+
if (!guidanceChanged && !hadRules) return false
|
|
130
|
+
chatroom.routingGuidance = nextGuidance
|
|
131
|
+
delete chatroom.routingRules
|
|
132
|
+
return guidanceChanged || hadRules
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildRecipientSelectionPrompt(params: {
|
|
136
|
+
text: string
|
|
137
|
+
chatroom: Chatroom
|
|
138
|
+
guidance: string
|
|
139
|
+
members: Array<{
|
|
140
|
+
id: string
|
|
141
|
+
name: string
|
|
142
|
+
description: string
|
|
143
|
+
capabilities: string[]
|
|
144
|
+
}>
|
|
145
|
+
}): string {
|
|
146
|
+
return [
|
|
147
|
+
'Choose which chatroom members should receive the latest message.',
|
|
148
|
+
'Return JSON only.',
|
|
149
|
+
'Use only agent IDs from the provided member list.',
|
|
150
|
+
'Prefer the smallest relevant set. Return an empty array when no routing guidance clearly applies.',
|
|
151
|
+
'Respect explicit routing guidance over generic capability overlap.',
|
|
152
|
+
'',
|
|
153
|
+
'Output shape:',
|
|
154
|
+
'{"agentIds":["agent-id-1","agent-id-2"]}',
|
|
155
|
+
'',
|
|
156
|
+
`Chatroom description: ${JSON.stringify(params.chatroom.description || '')}`,
|
|
157
|
+
`Routing guidance: ${JSON.stringify(params.guidance)}`,
|
|
158
|
+
`Latest message: ${JSON.stringify(params.text)}`,
|
|
159
|
+
'Members:',
|
|
160
|
+
JSON.stringify(params.members),
|
|
161
|
+
].join('\n')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function selectChatroomRecipients(
|
|
165
|
+
params: {
|
|
166
|
+
text: string
|
|
167
|
+
chatroom: Chatroom
|
|
168
|
+
agentsById: Record<string, Agent | undefined>
|
|
169
|
+
},
|
|
170
|
+
hooks?: {
|
|
171
|
+
generateText?: (prompt: string) => Promise<string>
|
|
172
|
+
},
|
|
173
|
+
): Promise<string[]> {
|
|
174
|
+
const guidance = resolveChatroomRoutingGuidance(params.chatroom, params.agentsById)
|
|
175
|
+
if (!guidance) return []
|
|
176
|
+
|
|
177
|
+
const members = params.chatroom.agentIds
|
|
178
|
+
.map((agentId) => {
|
|
179
|
+
const agent = params.agentsById[agentId]
|
|
180
|
+
if (!agent) return null
|
|
181
|
+
return {
|
|
182
|
+
id: agent.id,
|
|
183
|
+
name: agent.name,
|
|
184
|
+
description: agent.description || '',
|
|
185
|
+
capabilities: Array.isArray(agent.capabilities) ? agent.capabilities.slice(0, 12) : [],
|
|
61
186
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
187
|
+
})
|
|
188
|
+
.filter((member): member is NonNullable<typeof member> => member !== null)
|
|
189
|
+
if (members.length === 0) return []
|
|
190
|
+
|
|
191
|
+
const prompt = buildRecipientSelectionPrompt({
|
|
192
|
+
text: params.text,
|
|
193
|
+
chatroom: params.chatroom,
|
|
194
|
+
guidance,
|
|
195
|
+
members,
|
|
196
|
+
})
|
|
197
|
+
const allowedAgentIds = new Set(members.map((member) => member.id))
|
|
64
198
|
|
|
65
|
-
|
|
199
|
+
try {
|
|
200
|
+
const responseText = await Promise.race([
|
|
201
|
+
hooks?.generateText
|
|
202
|
+
? hooks.generateText(prompt)
|
|
203
|
+
: (async () => {
|
|
204
|
+
const { llm } = await buildLLM()
|
|
205
|
+
const response = await llm.invoke([new HumanMessage(prompt)])
|
|
206
|
+
return extractModelText(response.content)
|
|
207
|
+
})(),
|
|
208
|
+
new Promise<never>((_, reject) => {
|
|
209
|
+
setTimeout(() => reject(new Error('chatroom-recipient-selector-timeout')), SELECTOR_TIMEOUT_MS)
|
|
210
|
+
}),
|
|
211
|
+
])
|
|
212
|
+
return parseRecipientSelection(responseText, allowedAgentIds)
|
|
213
|
+
} catch (error: unknown) {
|
|
214
|
+
log.warn(TAG, 'Failed to select chatroom recipients from routing guidance', {
|
|
215
|
+
error: error instanceof Error ? error.message : 'unknown',
|
|
216
|
+
chatroomId: params.chatroom.id,
|
|
217
|
+
})
|
|
218
|
+
return []
|
|
219
|
+
}
|
|
66
220
|
}
|