@swarmclawai/swarmclaw 1.2.4 → 1.2.6
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 +14 -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]/route.test.ts +49 -0
- 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 +23 -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 +5 -5
- package/src/components/auth/setup-wizard/index.tsx +4 -4
- package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
- package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
- package/src/components/auth/setup-wizard/utils.ts +1 -1
- 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 +60 -61
- package/src/components/providers/provider-sheet.tsx +74 -56
- 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/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +108 -0
- package/src/lib/providers/index.ts +38 -15
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +1 -1
- 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 +44 -267
- package/src/lib/server/storage-normalization.ts +75 -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 +277 -12
|
@@ -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
|
}
|
|
@@ -3,8 +3,10 @@ import path from 'path'
|
|
|
3
3
|
import { ImapFlow } from 'imapflow'
|
|
4
4
|
import { createTransport } from 'nodemailer'
|
|
5
5
|
import { simpleParser } from 'mailparser'
|
|
6
|
-
import {
|
|
6
|
+
import type { Connector } from '@/types'
|
|
7
|
+
import { loadConnectors } from '@/lib/server/connectors/connector-repository'
|
|
7
8
|
import { getExtensionManager } from '@/lib/server/extensions'
|
|
9
|
+
import { UPLOAD_DIR } from '@/lib/server/upload-path'
|
|
8
10
|
|
|
9
11
|
export interface MailboxConfig {
|
|
10
12
|
imapHost: string
|
|
@@ -79,7 +81,7 @@ export function getMailboxConfig(): MailboxConfig {
|
|
|
79
81
|
const emailSettings = extensionManager.getExtensionSettings('email') as Record<string, unknown>
|
|
80
82
|
const connectors = loadConnectors()
|
|
81
83
|
const emailConnector = Object.values(connectors)
|
|
82
|
-
.find((entry) => entry
|
|
84
|
+
.find((entry) => entry.platform === 'email') as Connector | undefined
|
|
83
85
|
const connectorConfig = emailConnector && typeof emailConnector.config === 'object' && emailConnector.config
|
|
84
86
|
? emailConnector.config as Record<string, unknown>
|
|
85
87
|
: {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import type { MailboxEnvelope } from '@/types'
|
|
3
|
-
import {
|
|
3
|
+
import { loadSession, patchSession } from '@/lib/server/sessions/session-repository'
|
|
4
4
|
import { requestMissionTicksForHumanReply } from '@/lib/server/missions/mission-service'
|
|
5
5
|
|
|
6
6
|
interface MailboxOptions {
|
|
@@ -83,9 +83,8 @@ function normalizeMailbox(target: { mailbox?: MailboxEnvelope[] | null }, now =
|
|
|
83
83
|
|
|
84
84
|
function findLatestPendingHumanRequestEnvelope(
|
|
85
85
|
sessionId: string,
|
|
86
|
-
|
|
86
|
+
target = loadSession(sessionId),
|
|
87
87
|
): MailboxEnvelope | null {
|
|
88
|
-
const target = sessions[sessionId]
|
|
89
88
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
90
89
|
const envelopes = normalizeMailbox(target)
|
|
91
90
|
const repliedCorrelationIds = new Set(
|
|
@@ -108,8 +107,7 @@ export function findPendingHumanRequestEnvelope(params: {
|
|
|
108
107
|
fromSessionId?: string | null
|
|
109
108
|
fromAgentId?: string | null
|
|
110
109
|
}): MailboxEnvelope | null {
|
|
111
|
-
const
|
|
112
|
-
const target = sessions[params.sessionId]
|
|
110
|
+
const target = loadSession(params.sessionId)
|
|
113
111
|
if (!target) throw new Error(`Session not found: ${params.sessionId}`)
|
|
114
112
|
const expectedSignature = normalizeHumanRequestSignature(params)
|
|
115
113
|
const envelopes = normalizeMailbox(target)
|
|
@@ -139,8 +137,7 @@ export function sendMailboxEnvelope(input: {
|
|
|
139
137
|
correlationId?: string | null
|
|
140
138
|
ttlSec?: number | null
|
|
141
139
|
}): MailboxEnvelope {
|
|
142
|
-
const
|
|
143
|
-
const target = sessions[input.toSessionId]
|
|
140
|
+
const target = loadSession(input.toSessionId)
|
|
144
141
|
if (!target) throw new Error(`Target session not found: ${input.toSessionId}`)
|
|
145
142
|
|
|
146
143
|
const now = Date.now()
|
|
@@ -162,12 +159,14 @@ export function sendMailboxEnvelope(input: {
|
|
|
162
159
|
ackAt: null,
|
|
163
160
|
}
|
|
164
161
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
162
|
+
patchSession(input.toSessionId, (current) => {
|
|
163
|
+
if (!current) return null
|
|
164
|
+
return {
|
|
165
|
+
...current,
|
|
166
|
+
mailbox: [...normalizeMailbox(current, now), envelope],
|
|
167
|
+
lastActiveAt: now,
|
|
168
|
+
}
|
|
169
|
+
})
|
|
171
170
|
if (envelope.type === 'human_reply') {
|
|
172
171
|
requestMissionTicksForHumanReply({
|
|
173
172
|
sessionId: input.toSessionId,
|
|
@@ -188,8 +187,7 @@ export function sendMailboxEnvelope(input: {
|
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
export function listMailbox(sessionId: string, opts: MailboxOptions = {}): MailboxEnvelope[] {
|
|
191
|
-
const
|
|
192
|
-
const target = sessions[sessionId]
|
|
190
|
+
const target = loadSession(sessionId)
|
|
193
191
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
194
192
|
const list = pruneExpired(normalizeMailboxList(target.mailbox || []))
|
|
195
193
|
const includeAcked = opts.includeAcked === true
|
|
@@ -201,36 +199,48 @@ export function listMailbox(sessionId: string, opts: MailboxOptions = {}): Mailb
|
|
|
201
199
|
}
|
|
202
200
|
|
|
203
201
|
export function ackMailboxEnvelope(sessionId: string, envelopeId: string): MailboxEnvelope | null {
|
|
204
|
-
const
|
|
205
|
-
const target = sessions[sessionId]
|
|
202
|
+
const target = loadSession(sessionId)
|
|
206
203
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
204
|
+
const ackAt = Date.now()
|
|
205
|
+
let acked: MailboxEnvelope | null = null
|
|
206
|
+
patchSession(sessionId, (current) => {
|
|
207
|
+
if (!current) return null
|
|
208
|
+
const list = normalizeMailbox(current)
|
|
209
|
+
const idx = list.findIndex((env) => env.id === envelopeId)
|
|
210
|
+
if (idx === -1) return current
|
|
211
|
+
list[idx] = {
|
|
212
|
+
...list[idx],
|
|
213
|
+
status: 'ack',
|
|
214
|
+
ackAt,
|
|
215
|
+
}
|
|
216
|
+
acked = list[idx]
|
|
217
|
+
return {
|
|
218
|
+
...current,
|
|
219
|
+
mailbox: list,
|
|
220
|
+
lastActiveAt: ackAt,
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
return acked
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
export function clearMailbox(sessionId: string, includeAcked = true): { before: number; after: number } {
|
|
223
|
-
const
|
|
224
|
-
const target = sessions[sessionId]
|
|
227
|
+
const target = loadSession(sessionId)
|
|
225
228
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
let before = 0
|
|
230
|
+
let after = 0
|
|
231
|
+
patchSession(sessionId, (current) => {
|
|
232
|
+
if (!current) return null
|
|
233
|
+
const list = normalizeMailbox(current)
|
|
234
|
+
const afterList = includeAcked ? [] : list.filter((env) => env.status !== 'ack')
|
|
235
|
+
before = list.length
|
|
236
|
+
after = afterList.length
|
|
237
|
+
return {
|
|
238
|
+
...current,
|
|
239
|
+
mailbox: afterList,
|
|
240
|
+
lastActiveAt: Date.now(),
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
return { before, after }
|
|
234
244
|
}
|
|
235
245
|
|
|
236
246
|
export function bridgeHumanReplyFromChat(input: {
|