@swarmclawai/swarmclaw 1.2.4 → 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.
Files changed (260) 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 +15 -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/query/client.ts +17 -0
  109. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  110. package/src/lib/server/agents/agent-service.ts +429 -0
  111. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  112. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  113. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  114. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  115. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  116. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  117. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  118. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  119. package/src/lib/server/build-llm.ts +7 -15
  120. package/src/lib/server/capability-router.test.ts +70 -1
  121. package/src/lib/server/capability-router.ts +24 -99
  122. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  123. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  124. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  125. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  126. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  127. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  128. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  129. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  130. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  131. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  132. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  133. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  134. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  135. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  136. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  137. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  138. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  139. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  140. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  141. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  142. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  143. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  144. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  145. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  146. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  147. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  148. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  149. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  150. package/src/lib/server/chats/chat-session-service.ts +410 -0
  151. package/src/lib/server/connectors/access.ts +1 -1
  152. package/src/lib/server/connectors/commands.ts +7 -6
  153. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  154. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  155. package/src/lib/server/connectors/connector-service.ts +453 -0
  156. package/src/lib/server/connectors/delivery.ts +17 -12
  157. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  158. package/src/lib/server/connectors/media.ts +1 -1
  159. package/src/lib/server/connectors/response-media.ts +1 -1
  160. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  161. package/src/lib/server/connectors/session.ts +9 -7
  162. package/src/lib/server/connectors/voice-note.ts +2 -1
  163. package/src/lib/server/context-manager.ts +20 -1
  164. package/src/lib/server/cost.ts +2 -3
  165. package/src/lib/server/credentials/credential-repository.ts +43 -4
  166. package/src/lib/server/credentials/credential-service.ts +112 -0
  167. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  168. package/src/lib/server/daemon/controller.ts +577 -0
  169. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  170. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  171. package/src/lib/server/daemon/types.ts +101 -0
  172. package/src/lib/server/embeddings.ts +3 -9
  173. package/src/lib/server/eval/agent-regression.ts +3 -2
  174. package/src/lib/server/eval/runner.ts +2 -2
  175. package/src/lib/server/execution-brief.test.ts +167 -0
  176. package/src/lib/server/execution-brief.ts +295 -0
  177. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  178. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  179. package/src/lib/server/execution-engine/index.ts +35 -0
  180. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  181. package/src/lib/server/execution-engine/types.ts +33 -0
  182. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  183. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  184. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  185. package/src/lib/server/messages/message-repository.ts +330 -0
  186. package/src/lib/server/missions/mission-service/core.ts +8 -6
  187. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  188. package/src/lib/server/openclaw/doctor.ts +1 -1
  189. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  190. package/src/lib/server/openclaw/gateway.ts +5 -14
  191. package/src/lib/server/openclaw/health.ts +3 -11
  192. package/src/lib/server/openclaw/sync.ts +8 -6
  193. package/src/lib/server/persistence/storage-context.ts +3 -0
  194. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  195. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  196. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  197. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  198. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  199. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  200. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  201. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  202. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  203. package/src/lib/server/protocols/protocol-types.ts +10 -7
  204. package/src/lib/server/provider-endpoint.ts +7 -12
  205. package/src/lib/server/provider-model-discovery.ts +2 -11
  206. package/src/lib/server/query-expansion.ts +5 -6
  207. package/src/lib/server/run-context.test.ts +365 -0
  208. package/src/lib/server/run-context.ts +367 -0
  209. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  210. package/src/lib/server/runtime/queue/core.ts +61 -190
  211. package/src/lib/server/runtime/run-ledger.ts +8 -0
  212. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  213. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  214. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  215. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  216. package/src/lib/server/service-result.ts +16 -0
  217. package/src/lib/server/session-note.ts +2 -3
  218. package/src/lib/server/session-reset-policy.ts +4 -3
  219. package/src/lib/server/session-tools/connector.ts +9 -6
  220. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  221. package/src/lib/server/session-tools/crud.ts +162 -10
  222. package/src/lib/server/session-tools/delegate.ts +1 -1
  223. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  224. package/src/lib/server/session-tools/memory.ts +6 -4
  225. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  226. package/src/lib/server/session-tools/session-info.ts +119 -12
  227. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  228. package/src/lib/server/session-tools/skills.ts +15 -15
  229. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  230. package/src/lib/server/session-tools/subagent.ts +125 -7
  231. package/src/lib/server/session-tools/team-context.ts +4 -3
  232. package/src/lib/server/session-tools/wallet.ts +0 -58
  233. package/src/lib/server/sessions/session-lineage.ts +55 -0
  234. package/src/lib/server/sessions/session-repository.ts +2 -2
  235. package/src/lib/server/skills/learned-skills.ts +24 -23
  236. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  237. package/src/lib/server/skills/skill-repository.ts +136 -13
  238. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  239. package/src/lib/server/storage-normalization.test.ts +44 -267
  240. package/src/lib/server/storage-normalization.ts +75 -0
  241. package/src/lib/server/storage.ts +19 -0
  242. package/src/lib/server/structured-extract.ts +3 -14
  243. package/src/lib/server/tasks/task-followups.ts +16 -11
  244. package/src/lib/server/tasks/task-result.test.ts +25 -29
  245. package/src/lib/server/tasks/task-result.ts +5 -9
  246. package/src/lib/server/tasks/task-route-service.ts +449 -0
  247. package/src/lib/server/text-normalization.ts +41 -0
  248. package/src/lib/server/tool-planning.ts +6 -42
  249. package/src/lib/server/upload-path.ts +5 -0
  250. package/src/lib/server/working-state/extraction.ts +614 -0
  251. package/src/lib/server/working-state/normalization.ts +866 -0
  252. package/src/lib/server/working-state/prompt.ts +60 -0
  253. package/src/lib/server/working-state/repository.ts +38 -0
  254. package/src/lib/server/working-state/service.test.ts +253 -0
  255. package/src/lib/server/working-state/service.ts +293 -0
  256. package/src/lib/validation/schemas.ts +1 -0
  257. package/src/lib/ws-client.ts +3 -3
  258. package/src/stores/slices/task-slice.ts +1 -4
  259. package/src/stores/use-chatroom-store.ts +2 -2
  260. package/src/types/index.ts +277 -12
@@ -0,0 +1,453 @@
1
+ import { genId } from '@/lib/id'
2
+ import { logActivity } from '@/lib/server/activity/activity-log'
3
+ import {
4
+ deleteConnector,
5
+ loadConnector,
6
+ loadConnectorHealth,
7
+ loadConnectors,
8
+ upsertConnector,
9
+ } from '@/lib/server/connectors/connector-repository'
10
+ import {
11
+ buildConnectorAccessSnapshot,
12
+ resolveConnectorOwnerSenderId,
13
+ } from '@/lib/server/connectors/access'
14
+ import {
15
+ addAllowedSender,
16
+ approvePairingCode,
17
+ approvePendingSender,
18
+ clearSenderAddressingOverride,
19
+ normalizeSenderId,
20
+ parseAllowFromCsv,
21
+ parseDmAddressingMode,
22
+ parsePairingPolicy,
23
+ removeAllowedSender,
24
+ rejectPendingSender,
25
+ setSenderAddressingOverride,
26
+ senderMatchesAnyEntry,
27
+ } from '@/lib/server/connectors/pairing'
28
+ import {
29
+ ensureDaemonProcessRunning,
30
+ getDaemonConnectorRuntime,
31
+ listDaemonConnectorRuntime,
32
+ runDaemonConnectorAction,
33
+ } from '@/lib/server/daemon/controller'
34
+ import { log } from '@/lib/server/logger'
35
+ import { serviceFail, serviceOk } from '@/lib/server/service-result'
36
+ import { notify } from '@/lib/server/ws-hub'
37
+ import { errorMessage } from '@/lib/shared-utils'
38
+ import type {
39
+ Connector,
40
+ ConnectorAccessMutationAction,
41
+ ConnectorAccessMutationResponse,
42
+ ConnectorHealthEvent,
43
+ } from '@/types'
44
+ import type { ServiceResult } from '@/lib/server/service-result'
45
+ import type { DaemonConnectorRuntimeState } from '@/lib/server/daemon/types'
46
+
47
+ function cloneConnector<T extends Connector>(connector: T): T {
48
+ return {
49
+ ...connector,
50
+ config: connector.config ? { ...connector.config } : {},
51
+ }
52
+ }
53
+
54
+ function persistConnector(connector: Connector): void {
55
+ connector.updatedAt = Date.now()
56
+ upsertConnector(connector.id, connector)
57
+ }
58
+
59
+ function applyRuntimeFields(connector: Connector, runtime: DaemonConnectorRuntimeState | null): Connector {
60
+ connector.status = runtime?.status
61
+ ? runtime.status
62
+ : connector.lastError
63
+ ? 'error'
64
+ : 'stopped'
65
+
66
+ if (connector.platform === 'whatsapp') {
67
+ connector.authenticated = runtime?.authenticated
68
+ connector.hasCredentials = runtime?.hasCredentials
69
+ if (runtime?.qrDataUrl) connector.qrDataUrl = runtime.qrDataUrl
70
+ else delete connector.qrDataUrl
71
+ }
72
+
73
+ if (runtime?.reconnectAttempts !== undefined) {
74
+ const ext = connector as unknown as Record<string, unknown>
75
+ ext.reconnectAttempts = runtime.reconnectAttempts
76
+ ext.nextRetryAt = runtime.nextRetryAt
77
+ ext.reconnectError = runtime.reconnectError
78
+ ext.reconnectExhausted = runtime.reconnectExhausted
79
+ }
80
+
81
+ if (runtime?.presence && connector.status === 'running') {
82
+ connector.presence = runtime.presence
83
+ } else {
84
+ delete connector.presence
85
+ }
86
+
87
+ return connector
88
+ }
89
+
90
+ function setConnectorSenderList(connector: Connector, key: string, values: string[]): void {
91
+ if (!connector.config) connector.config = {}
92
+ if (values.length === 0) {
93
+ delete connector.config[key]
94
+ return
95
+ }
96
+ connector.config[key] = values.join(',')
97
+ }
98
+
99
+ function addConnectorSenderListEntry(connector: Connector, key: string, senderId: string): boolean {
100
+ const normalized = normalizeSenderId(senderId)
101
+ if (!normalized) return false
102
+ const current = parseAllowFromCsv(connector.config?.[key])
103
+ if (senderMatchesAnyEntry(normalized, current)) return false
104
+ setConnectorSenderList(connector, key, [...current, normalized])
105
+ return true
106
+ }
107
+
108
+ function removeConnectorSenderListEntry(connector: Connector, key: string, senderId: string): boolean {
109
+ const normalized = normalizeSenderId(senderId)
110
+ if (!normalized) return false
111
+ const current = parseAllowFromCsv(connector.config?.[key])
112
+ const next = current.filter((entry) => !senderMatchesAnyEntry(normalized, [entry]))
113
+ if (next.length === current.length) return false
114
+ setConnectorSenderList(connector, key, next)
115
+ return true
116
+ }
117
+
118
+ function requireSenderId(body: Record<string, unknown>): string {
119
+ const senderId = typeof body.senderId === 'string' ? body.senderId.trim() : ''
120
+ if (!senderId) throw new Error('senderId is required for this action')
121
+ return senderId
122
+ }
123
+
124
+ export async function listConnectorsWithRuntime(): Promise<Record<string, Connector>> {
125
+ await ensureDaemonProcessRunning('api/connectors:get')
126
+ const connectors = Object.fromEntries(
127
+ Object.entries(loadConnectors()).map(([id, connector]) => [id, cloneConnector(connector)]),
128
+ ) as Record<string, Connector>
129
+ const runtimeByConnector = await listDaemonConnectorRuntime()
130
+ for (const connector of Object.values(connectors)) {
131
+ applyRuntimeFields(connector, runtimeByConnector[connector.id] || null)
132
+ }
133
+ return connectors
134
+ }
135
+
136
+ export async function getConnectorWithRuntime(id: string): Promise<Connector | null> {
137
+ await ensureDaemonProcessRunning('api/connectors/[id]:get')
138
+ const connector = loadConnector(id)
139
+ if (!connector) return null
140
+ const current = cloneConnector(connector)
141
+ return applyRuntimeFields(current, await getDaemonConnectorRuntime(id))
142
+ }
143
+
144
+ export function createConnector(body: Record<string, unknown>): Connector {
145
+ const id = genId()
146
+ const connector: Connector = {
147
+ id,
148
+ name: (body.name as string) || `${String(body.platform || '')} Connector`,
149
+ platform: body.platform as Connector['platform'],
150
+ agentId: (body.agentId as string | null | undefined) || null,
151
+ chatroomId: (body.chatroomId as string | null | undefined) || null,
152
+ credentialId: (body.credentialId as string | null | undefined) || null,
153
+ config: body.config && typeof body.config === 'object' && !Array.isArray(body.config)
154
+ ? body.config as Record<string, string>
155
+ : {},
156
+ isEnabled: false,
157
+ status: 'stopped',
158
+ lastError: null,
159
+ createdAt: Date.now(),
160
+ updatedAt: Date.now(),
161
+ }
162
+ upsertConnector(id, connector)
163
+ notify('connectors')
164
+ return connector
165
+ }
166
+
167
+ export async function autoStartConnectorIfNeeded(connector: Connector, body: Record<string, unknown>): Promise<void> {
168
+ const hasCredentials = connector.platform === 'whatsapp'
169
+ || connector.platform === 'openclaw'
170
+ || (connector.platform === 'bluebubbles' && (!!connector.credentialId || !!connector.config.password))
171
+ || !!connector.credentialId
172
+ if (!hasCredentials || body.autoStart === false) return
173
+ try {
174
+ await runDaemonConnectorAction(connector.id, 'start', 'connectors:auto-start')
175
+ } catch (err: unknown) {
176
+ log.warn('connectors', `Auto-start failed for connector ${connector.id}`, errorMessage(err))
177
+ }
178
+ }
179
+
180
+ export async function updateConnectorFromRoute(id: string, body: Record<string, unknown>): Promise<ServiceResult<Connector>> {
181
+ await ensureDaemonProcessRunning('api/connectors/[id]:put')
182
+ const connector = loadConnector(id)
183
+ if (!connector) return serviceFail(404, 'Connector not found')
184
+
185
+ if (body.action === 'start' || body.action === 'stop' || body.action === 'repair') {
186
+ try {
187
+ if (body.action === 'start') {
188
+ await runDaemonConnectorAction(id, 'start', 'api/connectors/[id]:action:start')
189
+ logActivity({ entityType: 'connector', entityId: id, action: 'started', actor: 'user', summary: `Connector started: "${connector.name}"` })
190
+ } else if (body.action === 'stop') {
191
+ await runDaemonConnectorAction(id, 'stop', 'api/connectors/[id]:action:stop')
192
+ logActivity({ entityType: 'connector', entityId: id, action: 'stopped', actor: 'user', summary: `Connector stopped: "${connector.name}"` })
193
+ } else {
194
+ await runDaemonConnectorAction(id, 'repair', 'api/connectors/[id]:action:repair')
195
+ logActivity({ entityType: 'connector', entityId: id, action: 'started', actor: 'user', summary: `Connector repaired: "${connector.name}"` })
196
+ }
197
+ } catch (err: unknown) {
198
+ log.error('connectors', `Action failed for connector ${id}`, errorMessage(err))
199
+ return serviceFail(500, 'Connector action failed')
200
+ }
201
+ notify('connectors')
202
+ const updated = await getConnectorWithRuntime(id)
203
+ return serviceOk(updated || connector)
204
+ }
205
+
206
+ const next = cloneConnector(connector)
207
+ if (body.name !== undefined) next.name = typeof body.name === 'string' ? body.name : next.name
208
+ if (body.agentId !== undefined) next.agentId = typeof body.agentId === 'string' || body.agentId === null ? body.agentId : next.agentId
209
+ if (body.chatroomId !== undefined) next.chatroomId = typeof body.chatroomId === 'string' || body.chatroomId === null ? body.chatroomId : next.chatroomId
210
+ if (body.credentialId !== undefined) next.credentialId = typeof body.credentialId === 'string' || body.credentialId === null ? body.credentialId : next.credentialId
211
+ if (body.config !== undefined) next.config = body.config && typeof body.config === 'object' && !Array.isArray(body.config) ? body.config as Record<string, string> : next.config
212
+ if (body.isEnabled !== undefined) next.isEnabled = typeof body.isEnabled === 'boolean' ? body.isEnabled : next.isEnabled
213
+ persistConnector(next)
214
+
215
+ try {
216
+ const runtime = await getDaemonConnectorRuntime(id)
217
+ const wasRunning = runtime?.status === 'running'
218
+ const shouldStop = body.isEnabled === false
219
+ const shouldReload = wasRunning && (
220
+ body.name !== undefined
221
+ || body.agentId !== undefined
222
+ || body.chatroomId !== undefined
223
+ || body.credentialId !== undefined
224
+ || body.config !== undefined
225
+ || body.isEnabled !== undefined
226
+ )
227
+ const shouldStart = body.isEnabled === true && !wasRunning
228
+ if (shouldStop) {
229
+ await runDaemonConnectorAction(id, 'stop', 'api/connectors/[id]:reload:stop')
230
+ } else if (shouldReload || shouldStart) {
231
+ await runDaemonConnectorAction(id, 'start', 'api/connectors/[id]:reload:start')
232
+ }
233
+ } catch (err: unknown) {
234
+ log.warn('connectors', `Failed to reload connector ${id} after update`, errorMessage(err))
235
+ }
236
+
237
+ notify('connectors')
238
+ return serviceOk(await getConnectorWithRuntime(id) || next)
239
+ }
240
+
241
+ export async function deleteConnectorFromRoute(id: string): Promise<ServiceResult<{ ok: true }>> {
242
+ const connector = loadConnector(id)
243
+ if (!connector) return serviceFail(404, 'Connector not found')
244
+ try {
245
+ await runDaemonConnectorAction(id, 'stop', 'api/connectors/[id]:delete')
246
+ } catch (err: unknown) {
247
+ log.warn('connectors', `Failed to stop connector ${id} during delete`, errorMessage(err))
248
+ }
249
+ try {
250
+ const { clearConnectorPairingState } = await import('@/lib/server/connectors/pairing')
251
+ clearConnectorPairingState(id)
252
+ } catch (err: unknown) {
253
+ log.warn('connectors', `Failed to clear pairing state for ${id}`, errorMessage(err))
254
+ }
255
+ deleteConnector(id)
256
+ notify('connectors')
257
+ return serviceOk({ ok: true as const })
258
+ }
259
+
260
+ export function getConnectorHealthForApi(id: string): { events: ConnectorHealthEvent[]; uptimePercent: number } | null {
261
+ const connector = loadConnector(id)
262
+ if (!connector) return null
263
+ const allHealth = loadConnectorHealth()
264
+ const events: ConnectorHealthEvent[] = []
265
+ for (const raw of Object.values(allHealth)) {
266
+ const entry = raw as ConnectorHealthEvent
267
+ if (entry.connectorId !== id) continue
268
+ events.push(entry)
269
+ }
270
+ events.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
271
+ return { events, uptimePercent: computeUptime(events) }
272
+ }
273
+
274
+ function computeUptime(events: ConnectorHealthEvent[]): number {
275
+ if (events.length === 0) return 0
276
+ const firstTime = new Date(events[0].timestamp).getTime()
277
+ const now = Date.now()
278
+ const totalMs = now - firstTime
279
+ if (totalMs <= 0) return 100
280
+ let uptimeMs = 0
281
+ let lastUpAt: number | null = null
282
+ for (const event of events) {
283
+ const time = new Date(event.timestamp).getTime()
284
+ if (event.event === 'started' || event.event === 'reconnected') {
285
+ if (lastUpAt === null) lastUpAt = time
286
+ } else if (event.event === 'stopped' || event.event === 'error' || event.event === 'disconnected') {
287
+ if (lastUpAt !== null) {
288
+ uptimeMs += time - lastUpAt
289
+ lastUpAt = null
290
+ }
291
+ }
292
+ }
293
+ if (lastUpAt !== null) uptimeMs += now - lastUpAt
294
+ return Math.round((uptimeMs / totalMs) * 10000) / 100
295
+ }
296
+
297
+ export async function updateConnectorAccess(
298
+ connectorId: string,
299
+ body: Record<string, unknown>,
300
+ ): Promise<ServiceResult<ConnectorAccessMutationResponse>> {
301
+ await ensureDaemonProcessRunning('api/connectors/[id]/access:put')
302
+ const connector = loadConnector(connectorId)
303
+ if (!connector) return serviceFail(404, 'Connector not found')
304
+ const current = cloneConnector(connector)
305
+
306
+ try {
307
+ const action = typeof body.action === 'string' ? body.action.trim().toLowerCase() as ConnectorAccessMutationAction : null
308
+ if (!action) {
309
+ return serviceFail(400, 'Missing access action')
310
+ }
311
+ let connectorChanged = false
312
+ let responseSenderId = typeof body.senderId === 'string' ? body.senderId.trim() : ''
313
+ const responseSenderIdAlt = typeof body.senderIdAlt === 'string' ? body.senderIdAlt.trim() : ''
314
+ let summary = `Updated access controls for "${current.name}".`
315
+
316
+ switch (action) {
317
+ case 'set_policy': {
318
+ const rawPolicy = typeof body.dmPolicy === 'string' ? body.dmPolicy.trim() : ''
319
+ if (!rawPolicy) delete current.config.dmPolicy
320
+ else current.config.dmPolicy = parsePairingPolicy(rawPolicy, 'open')
321
+ connectorChanged = true
322
+ summary = `Updated DM policy for "${current.name}".`
323
+ break
324
+ }
325
+ case 'set_dm_addressing_mode': {
326
+ const rawMode = typeof body.dmAddressingMode === 'string' ? body.dmAddressingMode.trim() : ''
327
+ const nextMode = parseDmAddressingMode(rawMode || 'open', 'open')
328
+ if (nextMode === 'open') delete current.config.dmAddressingMode
329
+ else current.config.dmAddressingMode = nextMode
330
+ connectorChanged = true
331
+ summary = `Updated DM addressing mode for "${current.name}" to ${nextMode}.`
332
+ break
333
+ }
334
+ case 'allow_sender': {
335
+ const senderId = requireSenderId(body)
336
+ addAllowedSender(current.id, senderId)
337
+ connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
338
+ summary = `Allowed sender ${normalizeSenderId(senderId)} on "${current.name}".`
339
+ break
340
+ }
341
+ case 'remove_allowed_sender': {
342
+ const senderId = requireSenderId(body)
343
+ removeAllowedSender(current.id, senderId)
344
+ connectorChanged = removeConnectorSenderListEntry(current, 'allowFrom', senderId) || connectorChanged
345
+ summary = `Removed connector-managed access for ${normalizeSenderId(senderId)} on "${current.name}".`
346
+ break
347
+ }
348
+ case 'block_sender': {
349
+ const senderId = requireSenderId(body)
350
+ connectorChanged = addConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
351
+ connectorChanged = removeConnectorSenderListEntry(current, 'allowFrom', senderId) || connectorChanged
352
+ removeAllowedSender(current.id, senderId)
353
+ rejectPendingSender(current.id, senderId)
354
+ const ownerSenderId = resolveConnectorOwnerSenderId(current)
355
+ if (ownerSenderId && senderMatchesAnyEntry(senderId, [ownerSenderId])) {
356
+ delete current.config.ownerSenderId
357
+ connectorChanged = true
358
+ }
359
+ summary = `Blocked sender ${normalizeSenderId(senderId)} on "${current.name}".`
360
+ break
361
+ }
362
+ case 'unblock_sender': {
363
+ const senderId = requireSenderId(body)
364
+ connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
365
+ summary = `Removed sender ${normalizeSenderId(senderId)} from the deny list on "${current.name}".`
366
+ break
367
+ }
368
+ case 'approve_pairing': {
369
+ if (typeof body.code === 'string' && body.code.trim()) {
370
+ const approved = approvePairingCode(current.id, body.code)
371
+ if (!approved.ok) {
372
+ return serviceFail(400, approved.reason || 'Pairing approval failed.')
373
+ }
374
+ if (approved.senderId) {
375
+ responseSenderId = approved.senderId
376
+ connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', approved.senderId) || connectorChanged
377
+ }
378
+ summary = `Approved pairing on "${current.name}".`
379
+ } else {
380
+ const senderId = requireSenderId(body)
381
+ const approved = approvePendingSender(current.id, senderId)
382
+ if (!approved.ok) {
383
+ return serviceFail(400, approved.reason || 'Pairing approval failed.')
384
+ }
385
+ connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', senderId) || connectorChanged
386
+ summary = `Approved pairing for ${normalizeSenderId(senderId)} on "${current.name}".`
387
+ }
388
+ break
389
+ }
390
+ case 'reject_pairing': {
391
+ const senderId = requireSenderId(body)
392
+ rejectPendingSender(current.id, senderId)
393
+ summary = `Rejected pairing for ${normalizeSenderId(senderId)} on "${current.name}".`
394
+ break
395
+ }
396
+ case 'set_owner': {
397
+ const senderId = requireSenderId(body)
398
+ const normalized = normalizeSenderId(senderId)
399
+ if (!normalized) return serviceFail(400, 'Could not normalize owner sender ID')
400
+ current.config.ownerSenderId = normalized
401
+ connectorChanged = true
402
+ connectorChanged = removeConnectorSenderListEntry(current, 'denyFrom', normalized) || connectorChanged
403
+ summary = `Set connector owner for "${current.name}" to ${normalized}.`
404
+ break
405
+ }
406
+ case 'clear_owner': {
407
+ if (current.config?.ownerSenderId) {
408
+ delete current.config.ownerSenderId
409
+ connectorChanged = true
410
+ }
411
+ summary = `Cleared connector owner override for "${current.name}".`
412
+ break
413
+ }
414
+ case 'set_sender_dm_addressing': {
415
+ const senderId = requireSenderId(body)
416
+ const rawMode = typeof body.dmAddressingMode === 'string' ? body.dmAddressingMode.trim() : ''
417
+ const nextMode = parseDmAddressingMode(rawMode || 'open', 'open')
418
+ setSenderAddressingOverride(current.id, senderId, nextMode)
419
+ summary = `Updated DM addressing override for ${normalizeSenderId(senderId)} on "${current.name}" to ${nextMode}.`
420
+ break
421
+ }
422
+ case 'clear_sender_dm_addressing': {
423
+ const senderId = requireSenderId(body)
424
+ clearSenderAddressingOverride(current.id, senderId)
425
+ summary = `Cleared DM addressing override for ${normalizeSenderId(senderId)} on "${current.name}".`
426
+ break
427
+ }
428
+ default:
429
+ return serviceFail(400, `Unsupported access action: ${action}`)
430
+ }
431
+
432
+ if (connectorChanged) persistConnector(current)
433
+ logActivity({
434
+ entityType: 'connector',
435
+ entityId: current.id,
436
+ action: 'access-updated',
437
+ actor: 'user',
438
+ summary,
439
+ detail: { action },
440
+ })
441
+ notify('connectors')
442
+ return serviceOk({
443
+ ok: true,
444
+ snapshot: buildConnectorAccessSnapshot({
445
+ connector: current,
446
+ senderId: responseSenderId || null,
447
+ senderIdAlt: responseSenderIdAlt || null,
448
+ }),
449
+ })
450
+ } catch (err: unknown) {
451
+ return serviceFail(400, errorMessage(err))
452
+ }
453
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Connector, MessageSource } from '@/types'
2
- import { loadConnectors } from '../storage'
2
+ import { loadConnectors } from './connector-repository'
3
+ import { getMessages, replaceMessageAt } from '@/lib/server/messages/message-repository'
3
4
  import { notify } from '../ws-hub'
4
5
  import { resolveConnectorSessionPolicy, shouldReplyToInboundMessage } from './policy'
5
6
  import { runningConnectors } from './runtime-state'
@@ -68,7 +69,7 @@ export async function recordConnectorOutboundDelivery(params: {
68
69
  lastOutboundMessageId: params.messageId || session.connectorContext?.lastOutboundMessageId || null,
69
70
  threadId: params.inbound.threadId || session.connectorContext?.threadId || null,
70
71
  }
71
- const history = Array.isArray(session.messages) ? session.messages : []
72
+ const history = getMessages(session.id)
72
73
  for (let i = history.length - 1; i >= 0; i -= 1) {
73
74
  const entry = history[i]
74
75
  if (entry?.role !== 'assistant') continue
@@ -76,17 +77,21 @@ export async function recordConnectorOutboundDelivery(params: {
76
77
  if (source.connectorId !== connector.id) continue
77
78
  if (source.channelId !== params.inbound.channelId) continue
78
79
  if (!source.messageId && params.messageId) {
79
- entry.source = {
80
- platform: source.platform || connector.platform,
81
- connectorId: source.connectorId || connector.id,
82
- connectorName: source.connectorName || connector.name,
83
- channelId: source.channelId || params.inbound.channelId,
84
- senderId: source.senderId,
85
- senderName: source.senderName,
86
- messageId: params.messageId,
87
- replyToMessageId: source.replyToMessageId || params.inbound.messageId,
88
- threadId: source.threadId || params.inbound.threadId,
80
+ const updatedEntry = {
81
+ ...entry,
82
+ source: {
83
+ platform: source.platform || connector.platform,
84
+ connectorId: source.connectorId || connector.id,
85
+ connectorName: source.connectorName || connector.name,
86
+ channelId: source.channelId || params.inbound.channelId,
87
+ senderId: source.senderId,
88
+ senderName: source.senderName,
89
+ messageId: params.messageId,
90
+ replyToMessageId: source.replyToMessageId || params.inbound.messageId,
91
+ threadId: source.threadId || params.inbound.threadId,
92
+ },
89
93
  }
94
+ replaceMessageAt(session.id, i, updatedEntry)
90
95
  }
91
96
  break
92
97
  }
@@ -1,7 +1,8 @@
1
1
  import { log } from '@/lib/server/logger'
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
- import { decryptKey, loadCredentials, loadSettings } from '../storage'
4
+ import { listCredentialIdsByProvider, resolveCredentialSecret } from '@/lib/server/credentials/credential-service'
5
+ import { loadSettings } from '../settings/settings-repository'
5
6
  import { mimeFromPath } from './media'
6
7
  import type { InboundMessage, InboundMedia } from './types'
7
8
  import { errorMessage } from '@/lib/shared-utils'
@@ -90,25 +91,15 @@ function resolveOpenAiApiKey(preferredCredentialId?: string | null): string | nu
90
91
  const envKey = String(process.env.SWARMCLAW_OPENAI_STT_API_KEY || process.env.OPENAI_API_KEY || '').trim()
91
92
  if (envKey) return envKey
92
93
 
93
- const creds = loadCredentials() as Record<string, { provider?: string; encryptedKey?: string }>
94
94
  const candidates: string[] = []
95
95
  if (preferredCredentialId) candidates.push(preferredCredentialId)
96
- for (const [id, cred] of Object.entries(creds)) {
97
- const provider = String(cred?.provider || '').trim().toLowerCase()
98
- if (provider === 'openai') candidates.push(id)
99
- }
96
+ candidates.push(...listCredentialIdsByProvider('openai'))
100
97
  const seen = new Set<string>()
101
98
  for (const id of candidates) {
102
99
  if (!id || seen.has(id)) continue
103
100
  seen.add(id)
104
- const cred = creds[id]
105
- const provider = String(cred?.provider || '').trim().toLowerCase()
106
- if (provider !== 'openai') continue
107
- if (!cred?.encryptedKey) continue
108
- try {
109
- const decrypted = decryptKey(cred.encryptedKey).trim()
110
- if (decrypted) return decrypted
111
- } catch { /* ignore invalid credential */ }
101
+ const decrypted = resolveCredentialSecret(id)?.trim()
102
+ if (decrypted) return decrypted
112
103
  }
113
104
 
114
105
  return null
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { genId } from '@/lib/id'
4
- import { UPLOAD_DIR } from '../storage'
4
+ import { UPLOAD_DIR } from '../upload-path'
5
5
  import type { InboundMedia, InboundMediaType } from './types'
6
6
 
7
7
  const MIME_EXT_MAP: Record<string, string> = {
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
- import { UPLOAD_DIR } from '../storage'
3
+ import { UPLOAD_DIR } from '../upload-path'
4
4
  import { safeJsonParseObject } from '../json-utils'
5
5
  import type { InboundMessage, InboundMedia } from './types'
6
6
 
@@ -3,7 +3,9 @@
3
3
  * Populates the field from existing senderId, senderIdAlt, channelId, channelIdAlt, peerKey.
4
4
  */
5
5
  import { log } from '@/lib/server/logger'
6
- import { loadSessions, loadSettings, saveSettings, upsertStoredItem } from '../storage'
6
+ import { saveSession, loadSessions } from '@/lib/server/sessions/session-repository'
7
+ import { getMessages, getMessageCount, replaceAllMessages } from '@/lib/server/messages/message-repository'
8
+ import { loadSettings, saveSettings } from '../settings/settings-repository'
7
9
  import type { Session } from '@/types'
8
10
  import { isDirectConnectorSession } from './session-kind'
9
11
 
@@ -38,7 +40,7 @@ export function backfillAllKnownPeerIds(): { migrated: number; skipped: boolean
38
40
  ...ctx,
39
41
  allKnownPeerIds: [...ids],
40
42
  }
41
- upsertStoredItem('sessions', session.id, session)
43
+ saveSession(session.id, session)
42
44
  migrated++
43
45
  }
44
46
 
@@ -63,19 +65,21 @@ export function pruneThreadConnectorMirrors(): { cleanedSessions: number; remove
63
65
 
64
66
  for (const session of Object.values(sessions)) {
65
67
  if (isDirectConnectorSession(session)) continue
66
- if (!Array.isArray(session.messages) || session.messages.length === 0) continue
68
+ const msgCount = getMessageCount(session.id)
69
+ if (msgCount === 0) continue
67
70
 
68
- const filteredMessages = session.messages.filter((message) => !(
71
+ const allMessages = getMessages(session.id)
72
+ const filteredMessages = allMessages.filter((message) => !(
69
73
  message?.historyExcluded === true
70
74
  && typeof message?.source?.connectorId === 'string'
71
75
  && message.source.connectorId.trim().length > 0
72
76
  ))
73
77
 
74
- const removed = session.messages.length - filteredMessages.length
78
+ const removed = allMessages.length - filteredMessages.length
75
79
  if (removed <= 0) continue
76
80
 
77
- session.messages = filteredMessages
78
- upsertStoredItem('sessions', session.id, session)
81
+ replaceAllMessages(session.id, filteredMessages)
82
+ saveSession(session.id, session)
79
83
  cleanedSessions += 1
80
84
  removedMessages += removed
81
85
  }
@@ -5,6 +5,10 @@ import { WORKSPACE_DIR } from '../data-dir'
5
5
  import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
6
6
  import { resolveEffectiveSessionMemoryScopeMode } from '@/lib/server/memory/session-memory-scope'
7
7
  import { syncSessionArchiveMemory } from '@/lib/server/memory/session-archive-memory'
8
+ import {
9
+ appendMessage,
10
+ getLastMessage,
11
+ } from '@/lib/server/messages/message-repository'
8
12
  import { loadAgents, loadSessions, loadStoredItem, upsertStoredItem } from '../storage'
9
13
  import { notify } from '../ws-hub'
10
14
  import {
@@ -416,7 +420,7 @@ function mirrorConnectorMessageToAgentThread(
416
420
  : ensureAgentThreadSession(session.agentId)
417
421
  if (!threadSession || threadSession.id === session.id) return
418
422
 
419
- const last = Array.isArray(threadSession.messages) ? threadSession.messages[threadSession.messages.length - 1] : null
423
+ const last = getLastMessage(threadSession.id)
420
424
  const source = message.source as MessageSource | undefined
421
425
  const lastSource = (last?.source || null) as MessageSource | null
422
426
  if (
@@ -431,17 +435,16 @@ function mirrorConnectorMessageToAgentThread(
431
435
  return
432
436
  }
433
437
 
434
- if (!Array.isArray(threadSession.messages)) threadSession.messages = []
435
- threadSession.messages.push({
438
+ const mirrorMsg = {
436
439
  ...message,
437
440
  time: typeof message.time === 'number' ? message.time : Date.now(),
438
441
  historyExcluded: true,
439
- } as Session['messages'][number])
442
+ } as Session['messages'][number]
443
+ appendMessage(threadSession.id, mirrorMsg)
440
444
  threadSession.lastActiveAt = Date.now()
441
445
 
442
446
  upsertStoredItem('sessions', threadSession.id, threadSession)
443
447
  notify('sessions')
444
- notify(`messages:${threadSession.id}`)
445
448
  }
446
449
 
447
450
  export function pushSessionMessage(
@@ -451,9 +454,8 @@ export function pushSessionMessage(
451
454
  extra: Record<string, unknown> = {},
452
455
  ): void {
453
456
  if (!text.trim()) return
454
- if (!Array.isArray(session.messages)) session.messages = []
455
457
  const message = { role, text: text.trim(), time: Date.now(), ...extra }
456
- session.messages.push(message)
458
+ appendMessage(session.id, message as Session['messages'][number])
457
459
  session.lastActiveAt = Date.now()
458
460
  mirrorConnectorMessageToAgentThread(session, message)
459
461
  }
@@ -1,8 +1,9 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { genId } from '@/lib/id'
4
- import { loadAgent, UPLOAD_DIR } from '../storage'
4
+ import { loadAgent } from '@/lib/server/agents/agent-repository'
5
5
  import { synthesizeElevenLabsMp3 } from '../elevenlabs'
6
+ import { UPLOAD_DIR } from '../upload-path'
6
7
  import { isAudioMime, mimeFromPath } from './media'
7
8
 
8
9
  export function resolveConnectorVoiceId(params: {