@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.
Files changed (262) hide show
  1. package/README.md +14 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +23 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -0,0 +1,410 @@
1
+ import os from 'node:os'
2
+ import path from 'node:path'
3
+
4
+ import { genId } from '@/lib/id'
5
+ import { normalizeCapabilitySelection } from '@/lib/capability-selection'
6
+ import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
7
+ import { loadAgent } from '@/lib/server/agents/agent-repository'
8
+ import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
9
+ import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
10
+ import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
11
+ import { cleanupSessionProcesses } from '@/lib/server/runtime/process-manager'
12
+ import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
13
+ import {
14
+ cancelQueuedRunById,
15
+ cancelQueuedRunsForSession,
16
+ enqueueSessionRun,
17
+ getSessionQueueSnapshot,
18
+ getSessionRunState,
19
+ } from '@/lib/server/runtime/session-run-manager'
20
+ import { deleteSession, getSession, listSessions, saveSession } from '@/lib/server/sessions/session-repository'
21
+ import {
22
+ clearMessages,
23
+ deleteSessionMessages,
24
+ getMessages,
25
+ truncateAfter,
26
+ } from '@/lib/server/messages/message-repository'
27
+ import { deleteSessionWorkingState } from '@/lib/server/working-state/service'
28
+ import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
29
+ import { serviceFail, serviceOk } from '@/lib/server/service-result'
30
+ import { WORKSPACE_DIR } from '@/lib/server/data-dir'
31
+ import { buildSessionListSummary } from '@/lib/chat/session-summary'
32
+ import type { Session } from '@/types'
33
+ import type { ServiceResult } from '@/lib/server/service-result'
34
+ import { notify } from '@/lib/server/ws-hub'
35
+
36
+ function normalizeCwd(value: unknown): string {
37
+ const raw = typeof value === 'string' ? value.trim() : ''
38
+ if (raw.startsWith('~/')) return path.join(os.homedir(), raw.slice(2))
39
+ if (raw === '~') return os.homedir()
40
+ if (!raw) return WORKSPACE_DIR
41
+ return raw
42
+ }
43
+
44
+ function emptyDelegateResumeIds() {
45
+ return {
46
+ claudeCode: null,
47
+ codex: null,
48
+ opencode: null,
49
+ gemini: null,
50
+ }
51
+ }
52
+
53
+ export function listChatsForApi(): Record<string, ReturnType<typeof buildSessionListSummary>> {
54
+ const sessions = listSessions()
55
+ for (const id of Object.keys(sessions)) {
56
+ const run = getSessionRunState(id)
57
+ const queue = getSessionQueueSnapshot(id)
58
+ sessions[id].active = !!run.runningRunId
59
+ sessions[id].queuedCount = queue.queueLength
60
+ sessions[id].currentRunId = run.runningRunId || null
61
+ }
62
+ return Object.fromEntries(
63
+ Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(enrichSessionWithMissionSummary(session))]),
64
+ )
65
+ }
66
+
67
+ export function getChatSessionForApi(sessionId: string): Session | null {
68
+ const session = getSession(sessionId)
69
+ if (!session) return null
70
+ const run = getSessionRunState(sessionId)
71
+ const queue = getSessionQueueSnapshot(sessionId)
72
+ session.active = !!run.runningRunId
73
+ session.queuedCount = queue.queueLength
74
+ session.currentRunId = run.runningRunId || null
75
+ return enrichSessionWithMissionSummary(session)
76
+ }
77
+
78
+ export function createChatSession(input: Record<string, unknown>): ServiceResult<Session> {
79
+ const id = typeof input.id === 'string' && input.id.trim() ? input.id.trim() : genId()
80
+ const sessions = listSessions()
81
+ if (typeof input.id === 'string' && sessions[id]) {
82
+ return serviceOk(sessions[id])
83
+ }
84
+ const agent = typeof input.agentId === 'string' ? loadAgent(input.agentId) : null
85
+ if (isAgentDisabled(agent)) {
86
+ return serviceFail(409, buildAgentDisabledMessage(agent, 'start chats'))
87
+ }
88
+ const explicitOllamaMode = input.ollamaMode === 'cloud' ? 'cloud' : input.ollamaMode === 'local' ? 'local' : null
89
+ const routePreferredGatewayTags = Array.isArray(input.routePreferredGatewayTags)
90
+ ? input.routePreferredGatewayTags.filter((tag): tag is string => typeof tag === 'string' && tag.trim().length > 0)
91
+ : []
92
+ const routePreferredGatewayUseCase = typeof input.routePreferredGatewayUseCase === 'string' && input.routePreferredGatewayUseCase.trim()
93
+ ? input.routePreferredGatewayUseCase.trim()
94
+ : null
95
+ const resolvedRoute = agent ? resolvePrimaryAgentRoute(agent, undefined, {
96
+ preferredGatewayTags: routePreferredGatewayTags,
97
+ preferredGatewayUseCase: routePreferredGatewayUseCase,
98
+ }) : null
99
+ const resolvedCapabilities = normalizeCapabilitySelection({
100
+ tools: Array.isArray(input.tools) ? input.tools : agent?.tools,
101
+ extensions: Array.isArray(input.extensions) ? input.extensions : agent?.extensions,
102
+ })
103
+ const provider = (
104
+ typeof input.provider === 'string' && input.provider.trim()
105
+ ? input.provider.trim()
106
+ : agent?.provider || 'claude-cli'
107
+ ) as Session['provider']
108
+ const now = Date.now()
109
+ const baseSession: Session = {
110
+ id,
111
+ name: (input.name as string) || 'New Chat',
112
+ cwd: normalizeCwd(input.cwd),
113
+ user: (input.user as string) || 'user',
114
+ provider,
115
+ model: (input.model as string) || agent?.model || '',
116
+ ollamaMode: explicitOllamaMode ?? agent?.ollamaMode ?? (provider === 'ollama' ? 'local' : null),
117
+ credentialId: (input.credentialId as string | null | undefined) || agent?.credentialId || null,
118
+ fallbackCredentialIds: Array.isArray(input.fallbackCredentialIds) ? input.fallbackCredentialIds : agent?.fallbackCredentialIds || [],
119
+ apiEndpoint: normalizeProviderEndpoint(
120
+ provider,
121
+ (input.apiEndpoint as string | null | undefined) || agent?.apiEndpoint || null,
122
+ ),
123
+ routePreferredGatewayTags,
124
+ routePreferredGatewayUseCase,
125
+ claudeSessionId: null,
126
+ codexThreadId: null,
127
+ opencodeSessionId: null,
128
+ delegateResumeIds: emptyDelegateResumeIds(),
129
+ messages: Array.isArray(input.messages) ? input.messages : [],
130
+ createdAt: now,
131
+ lastActiveAt: now,
132
+ sessionType: (input.sessionType as Session['sessionType']) || 'human',
133
+ agentId: (input.agentId as string | null | undefined) || null,
134
+ parentSessionId: (input.parentSessionId as string | null | undefined) || null,
135
+ tools: resolvedCapabilities.tools,
136
+ extensions: resolvedCapabilities.extensions,
137
+ heartbeatEnabled: (input.heartbeatEnabled as boolean | null | undefined) ?? null,
138
+ heartbeatIntervalSec: (input.heartbeatIntervalSec as number | null | undefined) ?? null,
139
+ sessionResetMode: (input.sessionResetMode as Session['sessionResetMode']) ?? agent?.sessionResetMode ?? null,
140
+ sessionIdleTimeoutSec: (input.sessionIdleTimeoutSec as number | null | undefined) ?? agent?.sessionIdleTimeoutSec ?? null,
141
+ sessionMaxAgeSec: (input.sessionMaxAgeSec as number | null | undefined) ?? agent?.sessionMaxAgeSec ?? null,
142
+ sessionDailyResetAt: (input.sessionDailyResetAt as string | null | undefined) ?? agent?.sessionDailyResetAt ?? null,
143
+ sessionResetTimezone: (input.sessionResetTimezone as string | null | undefined) ?? agent?.sessionResetTimezone ?? null,
144
+ thinkingLevel: (input.thinkingLevel as Session['thinkingLevel']) ?? null,
145
+ connectorThinkLevel: (input.connectorThinkLevel as Session['connectorThinkLevel']) ?? null,
146
+ connectorSessionScope: (input.connectorSessionScope as Session['connectorSessionScope']) ?? null,
147
+ connectorReplyMode: (input.connectorReplyMode as Session['connectorReplyMode']) ?? null,
148
+ connectorThreadBinding: (input.connectorThreadBinding as Session['connectorThreadBinding']) ?? null,
149
+ connectorGroupPolicy: (input.connectorGroupPolicy as Session['connectorGroupPolicy']) ?? null,
150
+ connectorIdleTimeoutSec: (input.connectorIdleTimeoutSec as number | null | undefined) ?? null,
151
+ connectorMaxAgeSec: (input.connectorMaxAgeSec as number | null | undefined) ?? null,
152
+ connectorContext: input.connectorContext === null
153
+ ? undefined
154
+ : (input.connectorContext as Session['connectorContext']),
155
+ identityState: (input.identityState as Session['identityState']) ?? agent?.identityState ?? null,
156
+ sessionArchiveState: (input.sessionArchiveState as Session['sessionArchiveState']) ?? null,
157
+ }
158
+ const session: Session = (input.provider || input.model || input.credentialId || input.apiEndpoint)
159
+ ? baseSession
160
+ : applyResolvedRoute(baseSession, resolvedRoute)
161
+ saveSession(id, session)
162
+ notify('sessions')
163
+ return serviceOk(session)
164
+ }
165
+
166
+ export function deleteChats(ids: string[]): { deleted: number; requested: number } {
167
+ let deleted = 0
168
+ const sessions = listSessions()
169
+ for (const id of ids) {
170
+ if (!sessions[id]) continue
171
+ stopActiveSessionProcess(id)
172
+ deleteSessionWorkingState(id)
173
+ clearMainLoopStateForSession(id)
174
+ deleteSessionMessages(id)
175
+ deleteSession(id)
176
+ deleted += 1
177
+ }
178
+ if (deleted > 0) notify('sessions')
179
+ return { deleted, requested: ids.length }
180
+ }
181
+
182
+ export function updateChatSession(sessionId: string, updates: Record<string, unknown>): Session | null {
183
+ const original = getSession(sessionId)
184
+ if (!original) return null
185
+ const session = original as unknown as Record<string, unknown>
186
+
187
+ if (updates.resetMainLoopState === true) {
188
+ clearMainLoopStateForSession(sessionId)
189
+ deleteSessionWorkingState(sessionId)
190
+ }
191
+
192
+ const agentIdUpdateProvided = updates.agentId !== undefined
193
+ let nextAgentId = session.agentId
194
+ if (agentIdUpdateProvided) {
195
+ session.agentId = updates.agentId
196
+ nextAgentId = updates.agentId
197
+ }
198
+
199
+ const linkedAgent = nextAgentId ? loadAgent(String(nextAgentId)) : null
200
+ const routePreferredGatewayTags = updates.routePreferredGatewayTags !== undefined
201
+ ? (Array.isArray(updates.routePreferredGatewayTags)
202
+ ? updates.routePreferredGatewayTags.filter((tag): tag is string => typeof tag === 'string' && tag.trim().length > 0)
203
+ : [])
204
+ : ((session.routePreferredGatewayTags as string[]) || [])
205
+ const routePreferredGatewayUseCase = updates.routePreferredGatewayUseCase !== undefined
206
+ ? (typeof updates.routePreferredGatewayUseCase === 'string' && updates.routePreferredGatewayUseCase.trim()
207
+ ? updates.routePreferredGatewayUseCase.trim()
208
+ : null)
209
+ : ((session.routePreferredGatewayUseCase as string | null) || null)
210
+ const linkedRoute = linkedAgent ? resolvePrimaryAgentRoute(linkedAgent, undefined, {
211
+ preferredGatewayTags: routePreferredGatewayTags,
212
+ preferredGatewayUseCase: routePreferredGatewayUseCase,
213
+ }) : null
214
+
215
+ if (updates.name !== undefined) session.name = updates.name
216
+ if (updates.cwd !== undefined) session.cwd = normalizeCwd(updates.cwd)
217
+ if (updates.provider !== undefined) session.provider = updates.provider
218
+ else if (agentIdUpdateProvided && linkedAgent?.provider) session.provider = linkedAgent.provider
219
+ if (updates.model !== undefined) session.model = updates.model
220
+ else if (agentIdUpdateProvided && linkedRoute?.model) session.model = linkedRoute.model
221
+ else if (agentIdUpdateProvided && linkedAgent?.model !== undefined) session.model = linkedAgent.model
222
+ if (updates.ollamaMode !== undefined) session.ollamaMode = updates.ollamaMode
223
+ else if (updates.provider !== undefined && updates.provider !== 'ollama') session.ollamaMode = null
224
+ else if (agentIdUpdateProvided && linkedRoute) session.ollamaMode = linkedRoute.ollamaMode ?? null
225
+ else if (agentIdUpdateProvided && linkedAgent) session.ollamaMode = linkedAgent.ollamaMode ?? null
226
+ if (updates.credentialId !== undefined) session.credentialId = updates.credentialId
227
+ else if (agentIdUpdateProvided && linkedRoute) session.credentialId = linkedRoute.credentialId ?? null
228
+ else if (agentIdUpdateProvided && linkedAgent) session.credentialId = linkedAgent.credentialId ?? null
229
+ if (updates.fallbackCredentialIds !== undefined) session.fallbackCredentialIds = updates.fallbackCredentialIds
230
+ else if (agentIdUpdateProvided && linkedRoute) session.fallbackCredentialIds = [...linkedRoute.fallbackCredentialIds]
231
+ if (updates.gatewayProfileId !== undefined) session.gatewayProfileId = updates.gatewayProfileId
232
+ else if (agentIdUpdateProvided && linkedRoute) session.gatewayProfileId = linkedRoute.gatewayProfileId ?? null
233
+ if (updates.routePreferredGatewayTags !== undefined) session.routePreferredGatewayTags = routePreferredGatewayTags
234
+ if (updates.routePreferredGatewayUseCase !== undefined) session.routePreferredGatewayUseCase = routePreferredGatewayUseCase
235
+
236
+ if (updates.tools !== undefined || updates.extensions !== undefined || (agentIdUpdateProvided && linkedAgent)) {
237
+ const nextSelection = normalizeCapabilitySelection({
238
+ tools: Array.isArray(updates.tools)
239
+ ? updates.tools
240
+ : (agentIdUpdateProvided && linkedAgent ? linkedAgent.tools : session.tools as string[] | undefined),
241
+ extensions: Array.isArray(updates.extensions)
242
+ ? updates.extensions
243
+ : (agentIdUpdateProvided && linkedAgent ? linkedAgent.extensions : session.extensions as string[] | undefined),
244
+ })
245
+ session.tools = nextSelection.tools
246
+ session.extensions = nextSelection.extensions
247
+ }
248
+
249
+ if (updates.apiEndpoint !== undefined) {
250
+ session.apiEndpoint = normalizeProviderEndpoint(
251
+ (updates.provider || session.provider) as string,
252
+ updates.apiEndpoint as string | null | undefined,
253
+ )
254
+ } else if (agentIdUpdateProvided && linkedRoute) {
255
+ session.apiEndpoint = linkedRoute.apiEndpoint ?? null
256
+ } else if (agentIdUpdateProvided && linkedAgent) {
257
+ session.apiEndpoint = normalizeProviderEndpoint(linkedAgent.provider, linkedAgent.apiEndpoint ?? null)
258
+ }
259
+ if (updates.heartbeatEnabled !== undefined) session.heartbeatEnabled = updates.heartbeatEnabled
260
+ if (updates.heartbeatIntervalSec !== undefined) session.heartbeatIntervalSec = updates.heartbeatIntervalSec
261
+ if (updates.sessionResetMode !== undefined) session.sessionResetMode = updates.sessionResetMode
262
+ if (updates.sessionIdleTimeoutSec !== undefined) session.sessionIdleTimeoutSec = updates.sessionIdleTimeoutSec
263
+ if (updates.sessionMaxAgeSec !== undefined) session.sessionMaxAgeSec = updates.sessionMaxAgeSec
264
+ if (updates.sessionDailyResetAt !== undefined) session.sessionDailyResetAt = updates.sessionDailyResetAt
265
+ if (updates.sessionResetTimezone !== undefined) session.sessionResetTimezone = updates.sessionResetTimezone
266
+ if (updates.thinkingLevel !== undefined) session.thinkingLevel = updates.thinkingLevel
267
+ if (updates.connectorThinkLevel !== undefined) session.connectorThinkLevel = updates.connectorThinkLevel
268
+ if (updates.connectorSessionScope !== undefined) session.connectorSessionScope = updates.connectorSessionScope
269
+ if (updates.connectorReplyMode !== undefined) session.connectorReplyMode = updates.connectorReplyMode
270
+ if (updates.connectorThreadBinding !== undefined) session.connectorThreadBinding = updates.connectorThreadBinding
271
+ if (updates.connectorGroupPolicy !== undefined) session.connectorGroupPolicy = updates.connectorGroupPolicy
272
+ if (updates.connectorIdleTimeoutSec !== undefined) session.connectorIdleTimeoutSec = updates.connectorIdleTimeoutSec
273
+ if (updates.connectorMaxAgeSec !== undefined) session.connectorMaxAgeSec = updates.connectorMaxAgeSec
274
+ if (updates.connectorContext !== undefined) session.connectorContext = updates.connectorContext
275
+ if (updates.identityState !== undefined) session.identityState = updates.identityState
276
+ if (updates.sessionArchiveState !== undefined) session.sessionArchiveState = updates.sessionArchiveState
277
+ if (updates.lastSessionResetAt !== undefined) session.lastSessionResetAt = updates.lastSessionResetAt
278
+ if (updates.lastSessionResetReason !== undefined) session.lastSessionResetReason = updates.lastSessionResetReason
279
+ if (updates.pinned !== undefined) session.pinned = !!updates.pinned
280
+ if (updates.claudeSessionId !== undefined) session.claudeSessionId = updates.claudeSessionId
281
+ if (updates.codexThreadId !== undefined) session.codexThreadId = updates.codexThreadId
282
+ if (updates.opencodeSessionId !== undefined) session.opencodeSessionId = updates.opencodeSessionId
283
+ if (updates.delegateResumeIds !== undefined) session.delegateResumeIds = updates.delegateResumeIds
284
+ if (!Array.isArray(session.messages)) session.messages = []
285
+
286
+ saveSession(sessionId, original)
287
+ notify('sessions')
288
+ return enrichSessionWithMissionSummary(original)
289
+ }
290
+
291
+ export function deleteChatSession(sessionId: string): boolean {
292
+ if (!getSession(sessionId)) return false
293
+ stopActiveSessionProcess(sessionId)
294
+ cleanupSessionProcesses(sessionId)
295
+ deleteSessionMessages(sessionId)
296
+ deleteSession(sessionId)
297
+ notify('sessions')
298
+ return true
299
+ }
300
+
301
+ export function getQueueSnapshot(sessionId: string) {
302
+ const session = getSession(sessionId)
303
+ if (!session) return null
304
+ return getSessionQueueSnapshot(sessionId)
305
+ }
306
+
307
+ export function queueChatMessage(sessionId: string, body: Record<string, unknown>): ServiceResult<Record<string, unknown>> {
308
+ const session = getSession(sessionId)
309
+ if (!session) return serviceFail(404, 'Not found')
310
+ const message = typeof body.message === 'string' ? body.message : ''
311
+ const imagePath = typeof body.imagePath === 'string' ? body.imagePath : undefined
312
+ const imageUrl = typeof body.imageUrl === 'string' ? body.imageUrl : undefined
313
+ const attachedFiles = Array.isArray(body.attachedFiles)
314
+ ? body.attachedFiles.filter((file): file is string => typeof file === 'string' && file.trim().length > 0)
315
+ : undefined
316
+ const replyToId = typeof body.replyToId === 'string' ? body.replyToId : undefined
317
+ const hasFiles = !!(imagePath || imageUrl || attachedFiles?.length)
318
+ if (!message.trim() && !hasFiles) {
319
+ return serviceFail(400, 'message or file is required')
320
+ }
321
+ const queued = enqueueSessionRun({
322
+ sessionId,
323
+ missionId: session.missionId || null,
324
+ message,
325
+ imagePath,
326
+ imageUrl,
327
+ attachedFiles,
328
+ source: 'chat',
329
+ mode: 'followup',
330
+ replyToId,
331
+ })
332
+ return serviceOk({
333
+ queued: {
334
+ runId: queued.runId,
335
+ position: queued.position,
336
+ },
337
+ snapshot: getSessionQueueSnapshot(sessionId),
338
+ })
339
+ }
340
+
341
+ export function cancelQueuedChatMessages(sessionId: string, runId?: string): ServiceResult<Record<string, unknown>> | null {
342
+ const session = getSession(sessionId)
343
+ if (!session) return null
344
+ const normalizedRunId = typeof runId === 'string' ? runId.trim() : ''
345
+ if (normalizedRunId) {
346
+ const snapshot = getSessionQueueSnapshot(sessionId)
347
+ if (!snapshot.items.some((item) => item.runId === normalizedRunId)) {
348
+ return serviceFail(404, 'Queued run not found')
349
+ }
350
+ cancelQueuedRunById(normalizedRunId, 'Removed from queue')
351
+ return serviceOk({ cancelled: 1, snapshot: getSessionQueueSnapshot(sessionId) })
352
+ }
353
+ const cancelled = cancelQueuedRunsForSession(sessionId, 'Cleared queued messages')
354
+ return serviceOk({ cancelled, snapshot: getSessionQueueSnapshot(sessionId) })
355
+ }
356
+
357
+ export function clearChatMessages(sessionId: string): boolean {
358
+ const session = getSession(sessionId)
359
+ if (!session) return false
360
+ clearMessages(sessionId)
361
+ session.messages = []
362
+ session.claudeSessionId = null
363
+ session.codexThreadId = null
364
+ session.opencodeSessionId = null
365
+ session.delegateResumeIds = emptyDelegateResumeIds()
366
+ saveSession(sessionId, session)
367
+ notify('sessions')
368
+ return true
369
+ }
370
+
371
+ export function retryChatTurn(sessionId: string): ServiceResult<{ message: string; imagePath: string | null }> {
372
+ const session = getSession(sessionId)
373
+ if (!session) return serviceFail(404, 'Session not found')
374
+ const msgs = getMessages(sessionId)
375
+ // Remove trailing assistant messages
376
+ while (msgs.length && msgs[msgs.length - 1].role === 'assistant') {
377
+ msgs.pop()
378
+ }
379
+ if (!msgs.length) {
380
+ clearMessages(sessionId)
381
+ return serviceOk({ message: '', imagePath: null })
382
+ }
383
+ const lastUser = msgs[msgs.length - 1]
384
+ const message = lastUser.text
385
+ const imagePath = lastUser.imagePath || null
386
+ msgs.pop()
387
+ // Truncate to the new length (keep seq 0..msgs.length-1)
388
+ if (msgs.length === 0) {
389
+ clearMessages(sessionId)
390
+ } else {
391
+ truncateAfter(sessionId, msgs.length - 1)
392
+ }
393
+ return serviceOk({ message, imagePath })
394
+ }
395
+
396
+ export function editAndResendChatTurn(sessionId: string, messageIndex: number, newText: string): ServiceResult<{ message: string }> {
397
+ const session = getSession(sessionId)
398
+ if (!session) return serviceFail(404, 'Not found')
399
+ const msgCount = getMessages(sessionId).length
400
+ if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= msgCount) {
401
+ return serviceFail(400, 'Invalid message index')
402
+ }
403
+ // Keep messages up to but not including messageIndex
404
+ if (messageIndex === 0) {
405
+ clearMessages(sessionId)
406
+ } else {
407
+ truncateAfter(sessionId, messageIndex - 1)
408
+ }
409
+ return serviceOk({ message: newText })
410
+ }
@@ -5,7 +5,7 @@ import type {
5
5
  ConnectorAccessSnapshot,
6
6
  WhatsAppApprovedContact,
7
7
  } from '@/types'
8
- import { loadSettings } from '../storage'
8
+ import { loadSettings } from '../settings/settings-repository'
9
9
  import {
10
10
  createOrTouchPairingRequest,
11
11
  getSenderAddressingOverride,
@@ -1,7 +1,8 @@
1
1
  import { getProvider } from '@/lib/providers'
2
2
  import type { Connector } from '@/types'
3
- import { loadAgents } from '../storage'
3
+ import { loadAgents } from '@/lib/server/agents/agent-repository'
4
4
  import { syncSessionArchiveMemory } from '@/lib/server/memory/session-archive-memory'
5
+ import { getMessages, replaceAllMessages } from '@/lib/server/messages/message-repository'
5
6
  import { getEnabledCapabilityIds } from '@/lib/capability-selection'
6
7
  import { resolvePairingAccess } from './access'
7
8
  import {
@@ -189,9 +190,9 @@ export async function handleConnectorCommand(params: {
189
190
 
190
191
  if (command.name === 'status') {
191
192
  const policy = resolveConnectorSessionPolicy(connector, msg, session)
192
- const all = Array.isArray(session.messages) ? session.messages : []
193
- const userCount = all.filter((message: { role?: string }) => message?.role === 'user').length
194
- const assistantCount = all.filter((message: { role?: string }) => message?.role === 'assistant').length
193
+ const all = getMessages(session.id)
194
+ const userCount = all.filter((message) => message?.role === 'user').length
195
+ const assistantCount = all.filter((message) => message?.role === 'assistant').length
195
196
  const toolsCount = getEnabledCapabilityIds(session).length
196
197
  const statusText = [
197
198
  `Status for ${connector.platform} / ${connector.name}:`,
@@ -232,7 +233,7 @@ export async function handleConnectorCommand(params: {
232
233
  if (command.name === 'compact') {
233
234
  const keepParsed = Number.parseInt(command.args, 10)
234
235
  const keepLastN = Number.isFinite(keepParsed) ? Math.max(4, Math.min(50, keepParsed)) : 10
235
- const history = Array.isArray(session.messages) ? session.messages : []
236
+ const history = getMessages(session.id)
236
237
  if (history.length <= keepLastN) {
237
238
  const text = `Nothing to compact. Current history has ${history.length} message(s), keepLastN=${keepLastN}.`
238
239
  pushSessionMessage(session, 'user', inboundText)
@@ -249,7 +250,7 @@ export async function handleConnectorCommand(params: {
249
250
  time: Date.now(),
250
251
  kind: 'system' as const,
251
252
  }
252
- session.messages = [summaryMessage, ...recentMessages]
253
+ replaceAllMessages(session.id, [summaryMessage, ...recentMessages])
253
254
  session.lastActiveAt = Date.now()
254
255
  const text = `Compacted ${oldMessages.length} message(s). Kept ${recentMessages.length} recent message(s) plus a summary.`
255
256
  pushSessionMessage(session, 'assistant', text)
@@ -6,6 +6,7 @@ import {
6
6
  loadAgents, loadCredentials, decryptKey, loadSettings, loadSkills,
7
7
  loadChatrooms, saveChatrooms,
8
8
  } from '../storage'
9
+ import { getMessages } from '@/lib/server/messages/message-repository'
9
10
  import { dedup, errorMessage, hmrSingleton } from '@/lib/shared-utils'
10
11
  import path from 'path'
11
12
  import { streamAgentChat } from '@/lib/server/chat-execution/stream-agent-chat'
@@ -22,7 +23,10 @@ import {
22
23
  resolveApiKey as resolveApiKeyHelper,
23
24
  } from '@/lib/server/chatrooms/chatroom-helpers'
24
25
  import { filterHealthyChatroomAgents } from '@/lib/server/chatrooms/chatroom-health'
25
- import { evaluateRoutingRules } from '@/lib/server/chatrooms/chatroom-routing'
26
+ import {
27
+ ensureChatroomRoutingGuidance,
28
+ selectChatroomRecipients,
29
+ } from '@/lib/server/chatrooms/chatroom-routing'
26
30
  import { markProviderFailure, markProviderSuccess } from '../provider-health'
27
31
  import { buildIdentityContinuityContext } from '../identity-continuity'
28
32
  import { buildRuntimeSkillPromptBlocks, resolveRuntimeSkills } from '@/lib/server/skills/runtime-skill-resolver'
@@ -630,11 +634,14 @@ async function routeMessageToChatroom(connector: Connector, msg: InboundMessage)
630
634
  const threadContextBlock = buildConnectorThreadContextBlock(msg)
631
635
 
632
636
  // Parse mentions from the message text
637
+ ensureChatroomRoutingGuidance(chatroom, agents)
633
638
  let mentions = parseMentions(msg.text || '', agents, chatroom.agentIds)
634
- // Routing rules: if no explicit mentions, evaluate keyword/capability rules
635
- if (mentions.length === 0 && chatroom.routingRules?.length) {
636
- const agentList = chatroom.agentIds.map((id) => agents[id]).filter(Boolean)
637
- mentions = evaluateRoutingRules(msg.text || '', chatroom.routingRules, agentList)
639
+ if (mentions.length === 0 && !chatroom.autoAddress) {
640
+ mentions = await selectChatroomRecipients({
641
+ text: msg.text || '',
642
+ chatroom,
643
+ agentsById: agents,
644
+ })
638
645
  }
639
646
  // Auto-address: if enabled and still no mentions, address all agents
640
647
  if (chatroom.autoAddress && mentions.length === 0) {
@@ -1245,7 +1252,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
1245
1252
  }
1246
1253
  }
1247
1254
  },
1248
- history: modelHistoryTailWithAttribution(session.messages, 50, 48_000),
1255
+ history: modelHistoryTailWithAttribution(getMessages(session.id), 50, 48_000),
1249
1256
  })
1250
1257
  settledConnectorToolEvents = [
1251
1258
  ...pruneIncompleteToolEvents(streamedConnectorToolEvents),
@@ -1300,7 +1307,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
1300
1307
  }
1301
1308
  },
1302
1309
  active: new Map(),
1303
- loadHistory: () => modelHistoryTailWithAttribution(session.messages, 50, 48_000),
1310
+ loadHistory: () => modelHistoryTailWithAttribution(getMessages(session.id), 50, 48_000),
1304
1311
  })
1305
1312
  mediaExtractionText = fullText
1306
1313
  }
@@ -3,6 +3,7 @@ import {
3
3
  loadConnectors,
4
4
  loadSession, upsertSession,
5
5
  } from '../storage'
6
+ import { getMessages, replaceMessageAt } from '@/lib/server/messages/message-repository'
6
7
  import { errorMessage } from '@/lib/shared-utils'
7
8
  import path from 'path'
8
9
  import { notify } from '../ws-hub'
@@ -247,7 +248,7 @@ export async function sendConnectorMessage(params: {
247
248
  lastOutboundAt: Date.now(),
248
249
  lastOutboundMessageId: result?.messageId || session.connectorContext?.lastOutboundMessageId || null,
249
250
  }
250
- const history = Array.isArray(session.messages) ? session.messages : []
251
+ const history = getMessages(session.id)
251
252
  for (let i = history.length - 1; i >= 0; i -= 1) {
252
253
  const entry = history[i]
253
254
  if (entry?.role !== 'assistant') continue
@@ -255,17 +256,21 @@ export async function sendConnectorMessage(params: {
255
256
  if (source.connectorId !== connectorId) continue
256
257
  if (source.channelId !== channelId) continue
257
258
  if (!source.messageId && result?.messageId) {
258
- entry.source = {
259
- platform: source.platform || connector.platform,
260
- connectorId: source.connectorId || connectorId,
261
- connectorName: source.connectorName || connector.name,
262
- channelId: source.channelId || channelId,
263
- senderId: source.senderId,
264
- senderName: source.senderName,
265
- messageId: result.messageId,
266
- threadId: source.threadId || params.threadId,
267
- replyToMessageId: source.replyToMessageId || params.replyToMessageId,
259
+ const updatedEntry = {
260
+ ...entry,
261
+ source: {
262
+ platform: source.platform || connector.platform,
263
+ connectorId: source.connectorId || connectorId,
264
+ connectorName: source.connectorName || connector.name,
265
+ channelId: source.channelId || channelId,
266
+ senderId: source.senderId,
267
+ senderName: source.senderName,
268
+ messageId: result.messageId,
269
+ threadId: source.threadId || params.threadId,
270
+ replyToMessageId: source.replyToMessageId || params.replyToMessageId,
271
+ },
268
272
  }
273
+ replaceMessageAt(session.id, i, updatedEntry)
269
274
  }
270
275
  break
271
276
  }