@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
@@ -1,28 +1,10 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadSession, upsertSession } from '@/lib/server/storage'
2
+ import { retryChatTurn } from '@/lib/server/chats/chat-session-service'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
4
 
5
5
  export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
6
  const { id } = await params
7
- const session = loadSession(id)
8
- if (!session) return notFound()
9
-
10
- const msgs = session.messages
11
- // Pop trailing assistant messages to find the last user message
12
- while (msgs.length && msgs[msgs.length - 1].role === 'assistant') {
13
- msgs.pop()
14
- }
15
- if (!msgs.length) {
16
- return NextResponse.json({ message: '', imagePath: null }, { status: 200 })
17
- }
18
-
19
- const lastUser = msgs[msgs.length - 1]
20
- const message = lastUser.text
21
- const imagePath = lastUser.imagePath || null
22
-
23
- // Remove the last user message too — it will be re-sent by the client
24
- msgs.pop()
25
- upsertSession(id, session)
26
-
27
- return NextResponse.json({ message, imagePath })
7
+ const result = retryChatTurn(id)
8
+ if (!result.ok) return notFound()
9
+ return NextResponse.json(result.payload)
28
10
  }
@@ -1,158 +1,30 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents } from '@/lib/server/storage'
3
2
  import { notFound } from '@/lib/server/collection-helpers'
4
- import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
5
- import { resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
6
- import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
7
- import { cleanupSessionProcesses } from '@/lib/server/runtime/process-manager'
8
- import { deleteSession, getSession, saveSession } from '@/lib/server/sessions/session-repository'
9
- import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
10
- import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
11
- import { normalizeCapabilitySelection } from '@/lib/capability-selection'
12
- import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
3
+ import {
4
+ deleteChatSession,
5
+ getChatSessionForApi,
6
+ updateChatSession,
7
+ } from '@/lib/server/chats/chat-session-service'
13
8
  import { safeParseBody } from '@/lib/server/safe-parse-body'
14
9
 
15
10
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
16
11
  const { id } = await params
17
- const session = getSession(id)
12
+ const session = getChatSessionForApi(id)
18
13
  if (!session) return notFound()
19
-
20
- const run = getSessionRunState(id)
21
- const queue = getSessionQueueSnapshot(id)
22
- session.active = !!run.runningRunId
23
- session.queuedCount = queue.queueLength
24
- session.currentRunId = run.runningRunId || null
25
-
26
- return NextResponse.json(enrichSessionWithMissionSummary(session))
14
+ return NextResponse.json(session)
27
15
  }
28
16
 
29
17
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
30
18
  const { id } = await params
31
19
  const { data: updates, error } = await safeParseBody(req)
32
20
  if (error) return error
33
- const session = getSession(id) as Record<string, unknown> | null
21
+ const session = updateChatSession(id, updates as Record<string, unknown>)
34
22
  if (!session) return notFound()
35
-
36
- if (updates.resetMainLoopState === true) {
37
- clearMainLoopStateForSession(id)
38
- }
39
-
40
- const agentIdUpdateProvided = updates.agentId !== undefined
41
- let nextAgentId = session.agentId
42
- if (agentIdUpdateProvided) {
43
- session.agentId = updates.agentId
44
- nextAgentId = updates.agentId
45
- }
46
-
47
- const linkedAgent = nextAgentId ? loadAgents()[nextAgentId as string] : null
48
- const routePreferredGatewayTags = updates.routePreferredGatewayTags !== undefined
49
- ? (Array.isArray(updates.routePreferredGatewayTags)
50
- ? updates.routePreferredGatewayTags.filter((tag: unknown): tag is string => typeof tag === 'string' && tag.trim().length > 0)
51
- : [])
52
- : ((session.routePreferredGatewayTags as string[]) || [])
53
- const routePreferredGatewayUseCase = updates.routePreferredGatewayUseCase !== undefined
54
- ? (typeof updates.routePreferredGatewayUseCase === 'string' && updates.routePreferredGatewayUseCase.trim()
55
- ? updates.routePreferredGatewayUseCase.trim()
56
- : null)
57
- : ((session.routePreferredGatewayUseCase as string | null) || null)
58
- const linkedRoute = linkedAgent ? resolvePrimaryAgentRoute(linkedAgent, undefined, {
59
- preferredGatewayTags: routePreferredGatewayTags,
60
- preferredGatewayUseCase: routePreferredGatewayUseCase,
61
- }) : null
62
-
63
- if (updates.name !== undefined) session.name = updates.name
64
- if (updates.cwd !== undefined) session.cwd = updates.cwd
65
- if (updates.provider !== undefined) session.provider = updates.provider
66
- else if (agentIdUpdateProvided && linkedAgent?.provider) session.provider = linkedAgent.provider
67
-
68
- if (updates.model !== undefined) session.model = updates.model
69
- else if (agentIdUpdateProvided && linkedRoute?.model) session.model = linkedRoute.model
70
- else if (agentIdUpdateProvided && linkedAgent?.model !== undefined) session.model = linkedAgent.model
71
-
72
- if (updates.ollamaMode !== undefined) session.ollamaMode = updates.ollamaMode
73
- else if (updates.provider !== undefined && updates.provider !== 'ollama') session.ollamaMode = null
74
- else if (agentIdUpdateProvided && linkedRoute) session.ollamaMode = linkedRoute.ollamaMode ?? null
75
- else if (agentIdUpdateProvided && linkedAgent) session.ollamaMode = linkedAgent.ollamaMode ?? null
76
-
77
- if (updates.credentialId !== undefined) session.credentialId = updates.credentialId
78
- else if (agentIdUpdateProvided && linkedRoute) session.credentialId = linkedRoute.credentialId ?? null
79
- else if (agentIdUpdateProvided && linkedAgent) session.credentialId = linkedAgent.credentialId ?? null
80
-
81
- if (updates.fallbackCredentialIds !== undefined) session.fallbackCredentialIds = updates.fallbackCredentialIds
82
- else if (agentIdUpdateProvided && linkedRoute) session.fallbackCredentialIds = [...linkedRoute.fallbackCredentialIds]
83
-
84
- if (updates.gatewayProfileId !== undefined) session.gatewayProfileId = updates.gatewayProfileId
85
- else if (agentIdUpdateProvided && linkedRoute) session.gatewayProfileId = linkedRoute.gatewayProfileId ?? null
86
-
87
- if (updates.routePreferredGatewayTags !== undefined) {
88
- session.routePreferredGatewayTags = routePreferredGatewayTags
89
- }
90
- if (updates.routePreferredGatewayUseCase !== undefined) {
91
- session.routePreferredGatewayUseCase = routePreferredGatewayUseCase
92
- }
93
-
94
- if (updates.tools !== undefined || updates.extensions !== undefined || (agentIdUpdateProvided && linkedAgent)) {
95
- const nextSelection = normalizeCapabilitySelection({
96
- tools: Array.isArray(updates.tools)
97
- ? updates.tools
98
- : (agentIdUpdateProvided && linkedAgent ? linkedAgent.tools : session.tools as string[] | undefined),
99
- extensions: Array.isArray(updates.extensions)
100
- ? updates.extensions
101
- : (agentIdUpdateProvided && linkedAgent ? linkedAgent.extensions : session.extensions as string[] | undefined),
102
- })
103
- session.tools = nextSelection.tools
104
- session.extensions = nextSelection.extensions
105
- }
106
-
107
- if (updates.apiEndpoint !== undefined) {
108
- session.apiEndpoint = normalizeProviderEndpoint(
109
- (updates.provider || session.provider) as string,
110
- updates.apiEndpoint as string | null | undefined,
111
- )
112
- } else if (agentIdUpdateProvided && linkedRoute) {
113
- session.apiEndpoint = linkedRoute.apiEndpoint ?? null
114
- } else if (agentIdUpdateProvided && linkedAgent) {
115
- session.apiEndpoint = normalizeProviderEndpoint(
116
- linkedAgent.provider,
117
- linkedAgent.apiEndpoint ?? null,
118
- )
119
- }
120
- if (updates.heartbeatEnabled !== undefined) session.heartbeatEnabled = updates.heartbeatEnabled
121
- if (updates.heartbeatIntervalSec !== undefined) session.heartbeatIntervalSec = updates.heartbeatIntervalSec
122
- if (updates.sessionResetMode !== undefined) session.sessionResetMode = updates.sessionResetMode
123
- if (updates.sessionIdleTimeoutSec !== undefined) session.sessionIdleTimeoutSec = updates.sessionIdleTimeoutSec
124
- if (updates.sessionMaxAgeSec !== undefined) session.sessionMaxAgeSec = updates.sessionMaxAgeSec
125
- if (updates.sessionDailyResetAt !== undefined) session.sessionDailyResetAt = updates.sessionDailyResetAt
126
- if (updates.sessionResetTimezone !== undefined) session.sessionResetTimezone = updates.sessionResetTimezone
127
- if (updates.thinkingLevel !== undefined) session.thinkingLevel = updates.thinkingLevel
128
- if (updates.connectorThinkLevel !== undefined) session.connectorThinkLevel = updates.connectorThinkLevel
129
- if (updates.connectorSessionScope !== undefined) session.connectorSessionScope = updates.connectorSessionScope
130
- if (updates.connectorReplyMode !== undefined) session.connectorReplyMode = updates.connectorReplyMode
131
- if (updates.connectorThreadBinding !== undefined) session.connectorThreadBinding = updates.connectorThreadBinding
132
- if (updates.connectorGroupPolicy !== undefined) session.connectorGroupPolicy = updates.connectorGroupPolicy
133
- if (updates.connectorIdleTimeoutSec !== undefined) session.connectorIdleTimeoutSec = updates.connectorIdleTimeoutSec
134
- if (updates.connectorMaxAgeSec !== undefined) session.connectorMaxAgeSec = updates.connectorMaxAgeSec
135
- if (updates.connectorContext !== undefined) session.connectorContext = updates.connectorContext
136
- if (updates.identityState !== undefined) session.identityState = updates.identityState
137
- if (updates.sessionArchiveState !== undefined) session.sessionArchiveState = updates.sessionArchiveState
138
- if (updates.lastSessionResetAt !== undefined) session.lastSessionResetAt = updates.lastSessionResetAt
139
- if (updates.lastSessionResetReason !== undefined) session.lastSessionResetReason = updates.lastSessionResetReason
140
- if (updates.pinned !== undefined) session.pinned = !!updates.pinned
141
- if (updates.claudeSessionId !== undefined) session.claudeSessionId = updates.claudeSessionId
142
- if (updates.codexThreadId !== undefined) session.codexThreadId = updates.codexThreadId
143
- if (updates.opencodeSessionId !== undefined) session.opencodeSessionId = updates.opencodeSessionId
144
- if (updates.delegateResumeIds !== undefined) session.delegateResumeIds = updates.delegateResumeIds
145
- if (!Array.isArray(session.messages)) session.messages = []
146
-
147
- saveSession(id, session)
148
- return NextResponse.json(enrichSessionWithMissionSummary(session as never))
23
+ return NextResponse.json(session)
149
24
  }
150
25
 
151
26
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
152
27
  const { id } = await params
153
- if (!getSession(id)) return notFound()
154
- stopActiveSessionProcess(id)
155
- cleanupSessionProcesses(id)
156
- deleteSession(id)
28
+ if (!deleteChatSession(id)) return notFound()
157
29
  return new NextResponse('OK')
158
30
  }
@@ -1,7 +1,8 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/runtime/heartbeat-defaults'
3
- import { disableAllSessionHeartbeats, loadSettings, saveSettings } from '@/lib/server/storage'
4
3
  import { cancelAllHeartbeatRuns } from '@/lib/server/runtime/session-run-manager'
4
+ import { disableAllSessionHeartbeats } from '@/lib/server/sessions/session-repository'
5
+ import { loadSettings, saveSettings } from '@/lib/server/settings/settings-repository'
5
6
 
6
7
  export async function POST(req: Request) {
7
8
  const body = await req.json().catch(() => ({}))
@@ -0,0 +1,7 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { migrateAllSessions } from '@/lib/server/messages/message-repository'
3
+
4
+ export async function POST() {
5
+ const result = migrateAllSessions()
6
+ return NextResponse.json(result)
7
+ }
@@ -1,44 +1,17 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { genId } from '@/lib/id'
3
- import os from 'os'
4
- import path from 'path'
5
2
  import { perf } from '@/lib/server/runtime/perf'
6
- import { loadAgents } from '@/lib/server/storage'
7
- import { WORKSPACE_DIR } from '@/lib/server/data-dir'
8
- import { notify } from '@/lib/server/ws-hub'
9
- import { deleteSession, listSessions, replaceSessions } from '@/lib/server/sessions/session-repository'
10
- import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
11
- import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
12
- import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
13
- import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
14
- import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
15
- import { buildSessionListSummary } from '@/lib/chat/session-summary'
16
- import { normalizeCapabilitySelection } from '@/lib/capability-selection'
17
- import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
3
+ import {
4
+ createChatSession,
5
+ deleteChats,
6
+ listChatsForApi,
7
+ } from '@/lib/server/chats/chat-session-service'
8
+ import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
18
9
  export const dynamic = 'force-dynamic'
19
10
 
20
- async function ensureDaemonIfNeeded(source: string) {
21
- const { ensureDaemonStarted } = await import('@/lib/server/runtime/daemon-state')
22
- ensureDaemonStarted(source)
23
- }
24
-
25
11
 
26
12
  export async function GET(req: Request) {
27
13
  const endPerf = perf.start('api', 'GET /api/chats')
28
- // Note: pruneThreadConnectorMirrors and materializeStreamingAssistantArtifacts
29
- // are handled by the daemon periodic health check, not on every list fetch.
30
- const sessions = listSessions()
31
- for (const id of Object.keys(sessions)) {
32
- const run = getSessionRunState(id)
33
- const queue = getSessionQueueSnapshot(id)
34
- sessions[id].active = !!run.runningRunId
35
- sessions[id].queuedCount = queue.queueLength
36
- sessions[id].currentRunId = run.runningRunId || null
37
- }
38
-
39
- const summarized = Object.fromEntries(
40
- Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(enrichSessionWithMissionSummary(session))]),
41
- )
14
+ const summarized = listChatsForApi()
42
15
 
43
16
  const { searchParams } = new URL(req.url)
44
17
  const limitParam = searchParams.get('limit')
@@ -56,112 +29,18 @@ export async function GET(req: Request) {
56
29
  }
57
30
 
58
31
  export async function DELETE(req: Request) {
59
- await ensureDaemonIfNeeded('api/chats:delete')
32
+ await ensureDaemonProcessRunning('api/chats:delete')
60
33
  const { ids } = await req.json().catch(() => ({ ids: [] })) as { ids: string[] }
61
34
  if (!Array.isArray(ids) || !ids.length) {
62
35
  return new NextResponse('Missing ids', { status: 400 })
63
36
  }
64
- const sessions = listSessions()
65
- let deleted = 0
66
- for (const id of ids) {
67
- if (!sessions[id]) continue
68
- stopActiveSessionProcess(id)
69
- deleteSession(id)
70
- deleted += 1
71
- }
72
- notify('sessions')
73
- return NextResponse.json({ deleted, requested: ids.length })
37
+ return NextResponse.json(deleteChats(ids))
74
38
  }
75
39
 
76
40
  export async function POST(req: Request) {
77
- await ensureDaemonIfNeeded('api/chats:post')
41
+ await ensureDaemonProcessRunning('api/chats:post')
78
42
  const body = await req.json().catch(() => ({}))
79
- let cwd = (body.cwd || '').trim()
80
- if (cwd.startsWith('~/')) cwd = path.join(os.homedir(), cwd.slice(2))
81
- else if (cwd === '~') cwd = os.homedir()
82
- else if (!cwd) cwd = WORKSPACE_DIR
83
-
84
- const id = body.id || genId()
85
- const sessions = listSessions()
86
- const agent = body.agentId ? loadAgents()[body.agentId] : null
87
- if (isAgentDisabled(agent)) {
88
- return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'start chats') }, { status: 409 })
89
- }
90
- const routePreferredGatewayTags = Array.isArray(body.routePreferredGatewayTags)
91
- ? body.routePreferredGatewayTags.filter((tag: unknown): tag is string => typeof tag === 'string' && tag.trim().length > 0)
92
- : []
93
- const routePreferredGatewayUseCase = typeof body.routePreferredGatewayUseCase === 'string' && body.routePreferredGatewayUseCase.trim()
94
- ? body.routePreferredGatewayUseCase.trim()
95
- : null
96
- const resolvedRoute = agent ? resolvePrimaryAgentRoute(agent, undefined, {
97
- preferredGatewayTags: routePreferredGatewayTags,
98
- preferredGatewayUseCase: routePreferredGatewayUseCase,
99
- }) : null
100
- const resolvedCapabilities = normalizeCapabilitySelection({
101
- tools: Array.isArray(body.tools) ? body.tools : agent?.tools,
102
- extensions: Array.isArray(body.extensions) ? body.extensions : agent?.extensions,
103
- })
104
-
105
- // If session with this ID already exists, return it as-is
106
- if (body.id && sessions[id]) {
107
- return NextResponse.json(sessions[id])
108
- }
109
-
110
- const sessionName = body.name || 'New Chat'
111
-
112
- const nextSession = {
113
- id, name: sessionName, cwd,
114
- user: body.user || 'user',
115
- provider: body.provider || agent?.provider || 'claude-cli',
116
- model: body.model || agent?.model || '',
117
- ollamaMode: body.ollamaMode ?? agent?.ollamaMode ?? ((body.provider || agent?.provider) === 'ollama' ? 'local' : null),
118
- credentialId: body.credentialId || agent?.credentialId || null,
119
- fallbackCredentialIds: body.fallbackCredentialIds || agent?.fallbackCredentialIds || [],
120
- apiEndpoint: normalizeProviderEndpoint(
121
- body.provider || agent?.provider || 'claude-cli',
122
- body.apiEndpoint || agent?.apiEndpoint || null,
123
- ),
124
- routePreferredGatewayTags,
125
- routePreferredGatewayUseCase,
126
- claudeSessionId: null,
127
- codexThreadId: null,
128
- opencodeSessionId: null,
129
- delegateResumeIds: {
130
- claudeCode: null,
131
- codex: null,
132
- opencode: null,
133
- gemini: null,
134
- },
135
- messages: Array.isArray(body.messages) ? body.messages : [],
136
- createdAt: Date.now(), lastActiveAt: Date.now(),
137
- sessionType: body.sessionType || 'human',
138
- agentId: body.agentId || null,
139
- parentSessionId: body.parentSessionId || null,
140
- tools: resolvedCapabilities.tools,
141
- extensions: resolvedCapabilities.extensions,
142
- heartbeatEnabled: body.heartbeatEnabled ?? null,
143
- heartbeatIntervalSec: body.heartbeatIntervalSec ?? null,
144
- sessionResetMode: body.sessionResetMode ?? agent?.sessionResetMode ?? null,
145
- sessionIdleTimeoutSec: body.sessionIdleTimeoutSec ?? agent?.sessionIdleTimeoutSec ?? null,
146
- sessionMaxAgeSec: body.sessionMaxAgeSec ?? agent?.sessionMaxAgeSec ?? null,
147
- sessionDailyResetAt: body.sessionDailyResetAt ?? agent?.sessionDailyResetAt ?? null,
148
- sessionResetTimezone: body.sessionResetTimezone ?? agent?.sessionResetTimezone ?? null,
149
- thinkingLevel: body.thinkingLevel ?? null,
150
- connectorThinkLevel: body.connectorThinkLevel ?? null,
151
- connectorSessionScope: body.connectorSessionScope ?? null,
152
- connectorReplyMode: body.connectorReplyMode ?? null,
153
- connectorThreadBinding: body.connectorThreadBinding ?? null,
154
- connectorGroupPolicy: body.connectorGroupPolicy ?? null,
155
- connectorIdleTimeoutSec: body.connectorIdleTimeoutSec ?? null,
156
- connectorMaxAgeSec: body.connectorMaxAgeSec ?? null,
157
- connectorContext: body.connectorContext ?? null,
158
- identityState: body.identityState ?? agent?.identityState ?? null,
159
- sessionArchiveState: body.sessionArchiveState ?? null,
160
- }
161
- sessions[id] = (body.provider || body.model || body.credentialId || body.apiEndpoint)
162
- ? nextSession
163
- : applyResolvedRoute(nextSession, resolvedRoute)
164
- replaceSessions(sessions)
165
- notify('sessions')
166
- return NextResponse.json(sessions[id])
43
+ const result = createChatSession(body as Record<string, unknown>)
44
+ if (!result.ok) return NextResponse.json(result.payload, { status: result.status })
45
+ return NextResponse.json(result.payload)
167
46
  }
@@ -1,77 +1,15 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import type { Connector, ConnectorAccessMutationAction, ConnectorAccessMutationResponse } from '@/types'
4
3
  import { notFound } from '@/lib/server/collection-helpers'
5
- import {
6
- loadConnectors,
7
- logActivity,
8
- upsertStoredItem,
9
- } from '@/lib/server/storage'
10
- import { ensureDaemonStarted } from '@/lib/server/runtime/daemon-state'
11
- import { notify } from '@/lib/server/ws-hub'
12
- import { errorMessage } from '@/lib/shared-utils'
13
4
  import {
14
5
  buildConnectorAccessSnapshot,
15
- resolveConnectorOwnerSenderId,
16
6
  } from '@/lib/server/connectors/access'
17
- import {
18
- addAllowedSender,
19
- approvePairingCode,
20
- approvePendingSender,
21
- clearSenderAddressingOverride,
22
- normalizeSenderId,
23
- parseAllowFromCsv,
24
- parseDmAddressingMode,
25
- parsePairingPolicy,
26
- removeAllowedSender,
27
- rejectPendingSender,
28
- setSenderAddressingOverride,
29
- senderMatchesAnyEntry,
30
- } from '@/lib/server/connectors/pairing'
31
-
32
- function setConnectorSenderList(connector: Connector, key: string, values: string[]): void {
33
- if (!connector.config) connector.config = {}
34
- if (values.length === 0) {
35
- delete connector.config[key]
36
- return
37
- }
38
- connector.config[key] = values.join(',')
39
- }
40
-
41
- function addConnectorSenderListEntry(connector: Connector, key: string, senderId: string): boolean {
42
- const normalized = normalizeSenderId(senderId)
43
- if (!normalized) return false
44
- const current = parseAllowFromCsv(connector.config?.[key])
45
- if (senderMatchesAnyEntry(normalized, current)) return false
46
- setConnectorSenderList(connector, key, [...current, normalized])
47
- return true
48
- }
49
-
50
- function removeConnectorSenderListEntry(connector: Connector, key: string, senderId: string): boolean {
51
- const normalized = normalizeSenderId(senderId)
52
- if (!normalized) return false
53
- const current = parseAllowFromCsv(connector.config?.[key])
54
- const next = current.filter((entry) => !senderMatchesAnyEntry(normalized, [entry]))
55
- if (next.length === current.length) return false
56
- setConnectorSenderList(connector, key, next)
57
- return true
58
- }
59
-
60
- function persistConnector(connector: Connector): void {
61
- connector.updatedAt = Date.now()
62
- upsertStoredItem('connectors', connector.id, connector)
63
- }
64
-
65
- function requireSenderId(body: Record<string, unknown>): string {
66
- const senderId = typeof body.senderId === 'string' ? body.senderId.trim() : ''
67
- if (!senderId) {
68
- throw new Error('senderId is required for this action')
69
- }
70
- return senderId
71
- }
7
+ import { loadConnectors } from '@/lib/server/connectors/connector-repository'
8
+ import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
9
+ import { updateConnectorAccess } from '@/lib/server/connectors/connector-service'
72
10
 
73
11
  export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
74
- ensureDaemonStarted('api/connectors/[id]/access:get')
12
+ await ensureDaemonProcessRunning('api/connectors/[id]/access:get')
75
13
  const { id } = await params
76
14
  const connectors = loadConnectors()
77
15
  const connector = connectors[id]
@@ -88,169 +26,14 @@ export async function GET(req: Request, { params }: { params: Promise<{ id: stri
88
26
  }
89
27
 
90
28
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
91
- ensureDaemonStarted('api/connectors/[id]/access:put')
92
29
  const { id } = await params
93
- const connectors = loadConnectors()
94
- const connector = connectors[id]
30
+ const connector = loadConnectors()[id]
95
31
  if (!connector) return notFound()
96
-
97
- try {
98
- const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
99
- if (error) return error
100
- const action = typeof body.action === 'string' ? body.action.trim().toLowerCase() as ConnectorAccessMutationAction : null
101
- if (!action) {
102
- return NextResponse.json({ error: 'Missing access action' }, { status: 400 })
103
- }
104
-
105
- let connectorChanged = false
106
- let responseSenderId = typeof body.senderId === 'string' ? body.senderId.trim() : ''
107
- const responseSenderIdAlt = typeof body.senderIdAlt === 'string' ? body.senderIdAlt.trim() : ''
108
- let summary = `Updated access controls for "${connector.name}".`
109
-
110
- switch (action) {
111
- case 'set_policy': {
112
- const rawPolicy = typeof body.dmPolicy === 'string' ? body.dmPolicy.trim() : ''
113
- if (!rawPolicy) {
114
- delete connector.config.dmPolicy
115
- } else {
116
- connector.config.dmPolicy = parsePairingPolicy(rawPolicy, 'open')
117
- }
118
- connectorChanged = true
119
- summary = `Updated DM policy for "${connector.name}".`
120
- break
121
- }
122
- case 'set_dm_addressing_mode': {
123
- const rawMode = typeof body.dmAddressingMode === 'string' ? body.dmAddressingMode.trim() : ''
124
- const nextMode = parseDmAddressingMode(rawMode || 'open', 'open')
125
- if (nextMode === 'open') delete connector.config.dmAddressingMode
126
- else connector.config.dmAddressingMode = nextMode
127
- connectorChanged = true
128
- summary = `Updated DM addressing mode for "${connector.name}" to ${nextMode}.`
129
- break
130
- }
131
- case 'allow_sender': {
132
- const senderId = requireSenderId(body)
133
- addAllowedSender(connector.id, senderId)
134
- connectorChanged = removeConnectorSenderListEntry(connector, 'denyFrom', senderId) || connectorChanged
135
- summary = `Allowed sender ${normalizeSenderId(senderId)} on "${connector.name}".`
136
- break
137
- }
138
- case 'remove_allowed_sender': {
139
- const senderId = requireSenderId(body)
140
- removeAllowedSender(connector.id, senderId)
141
- connectorChanged = removeConnectorSenderListEntry(connector, 'allowFrom', senderId) || connectorChanged
142
- summary = `Removed connector-managed access for ${normalizeSenderId(senderId)} on "${connector.name}".`
143
- break
144
- }
145
- case 'block_sender': {
146
- const senderId = requireSenderId(body)
147
- connectorChanged = addConnectorSenderListEntry(connector, 'denyFrom', senderId) || connectorChanged
148
- connectorChanged = removeConnectorSenderListEntry(connector, 'allowFrom', senderId) || connectorChanged
149
- removeAllowedSender(connector.id, senderId)
150
- rejectPendingSender(connector.id, senderId)
151
- const ownerSenderId = resolveConnectorOwnerSenderId(connector)
152
- if (ownerSenderId && senderMatchesAnyEntry(senderId, [ownerSenderId])) {
153
- delete connector.config.ownerSenderId
154
- connectorChanged = true
155
- }
156
- summary = `Blocked sender ${normalizeSenderId(senderId)} on "${connector.name}".`
157
- break
158
- }
159
- case 'unblock_sender': {
160
- const senderId = requireSenderId(body)
161
- connectorChanged = removeConnectorSenderListEntry(connector, 'denyFrom', senderId) || connectorChanged
162
- summary = `Removed sender ${normalizeSenderId(senderId)} from the deny list on "${connector.name}".`
163
- break
164
- }
165
- case 'approve_pairing': {
166
- if (typeof body.code === 'string' && body.code.trim()) {
167
- const approved = approvePairingCode(connector.id, body.code)
168
- if (!approved.ok) {
169
- return NextResponse.json({ error: approved.reason || 'Pairing approval failed.' }, { status: 400 })
170
- }
171
- if (approved.senderId) {
172
- responseSenderId = approved.senderId
173
- connectorChanged = removeConnectorSenderListEntry(connector, 'denyFrom', approved.senderId) || connectorChanged
174
- }
175
- summary = `Approved pairing on "${connector.name}".`
176
- } else {
177
- const senderId = requireSenderId(body)
178
- const approved = approvePendingSender(connector.id, senderId)
179
- if (!approved.ok) {
180
- return NextResponse.json({ error: approved.reason || 'Pairing approval failed.' }, { status: 400 })
181
- }
182
- connectorChanged = removeConnectorSenderListEntry(connector, 'denyFrom', senderId) || connectorChanged
183
- summary = `Approved pairing for ${normalizeSenderId(senderId)} on "${connector.name}".`
184
- }
185
- break
186
- }
187
- case 'reject_pairing': {
188
- const senderId = requireSenderId(body)
189
- rejectPendingSender(connector.id, senderId)
190
- summary = `Rejected pairing for ${normalizeSenderId(senderId)} on "${connector.name}".`
191
- break
192
- }
193
- case 'set_owner': {
194
- const senderId = requireSenderId(body)
195
- const normalized = normalizeSenderId(senderId)
196
- if (!normalized) {
197
- return NextResponse.json({ error: 'Could not normalize owner sender ID' }, { status: 400 })
198
- }
199
- connector.config.ownerSenderId = normalized
200
- connectorChanged = true
201
- connectorChanged = removeConnectorSenderListEntry(connector, 'denyFrom', normalized) || connectorChanged
202
- summary = `Set connector owner for "${connector.name}" to ${normalized}.`
203
- break
204
- }
205
- case 'clear_owner': {
206
- if (connector.config?.ownerSenderId) {
207
- delete connector.config.ownerSenderId
208
- connectorChanged = true
209
- }
210
- summary = `Cleared connector owner override for "${connector.name}".`
211
- break
212
- }
213
- case 'set_sender_dm_addressing': {
214
- const senderId = requireSenderId(body)
215
- const rawMode = typeof body.dmAddressingMode === 'string' ? body.dmAddressingMode.trim() : ''
216
- const nextMode = parseDmAddressingMode(rawMode || 'open', 'open')
217
- setSenderAddressingOverride(connector.id, senderId, nextMode)
218
- summary = `Updated DM addressing override for ${normalizeSenderId(senderId)} on "${connector.name}" to ${nextMode}.`
219
- break
220
- }
221
- case 'clear_sender_dm_addressing': {
222
- const senderId = requireSenderId(body)
223
- clearSenderAddressingOverride(connector.id, senderId)
224
- summary = `Cleared DM addressing override for ${normalizeSenderId(senderId)} on "${connector.name}".`
225
- break
226
- }
227
- default:
228
- return NextResponse.json({ error: `Unsupported access action: ${action}` }, { status: 400 })
229
- }
230
-
231
- if (connectorChanged) {
232
- persistConnector(connector)
233
- }
234
-
235
- logActivity({
236
- entityType: 'connector',
237
- entityId: connector.id,
238
- action: 'access-updated',
239
- actor: 'user',
240
- summary,
241
- detail: { action },
242
- })
243
- notify('connectors')
244
-
245
- return NextResponse.json<ConnectorAccessMutationResponse>({
246
- ok: true,
247
- snapshot: buildConnectorAccessSnapshot({
248
- connector,
249
- senderId: responseSenderId || null,
250
- senderIdAlt: responseSenderIdAlt || null,
251
- }),
252
- })
253
- } catch (err: unknown) {
254
- return NextResponse.json({ error: errorMessage(err) }, { status: 400 })
255
- }
32
+ const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
33
+ if (error) return error
34
+ const result = await updateConnectorAccess(id, body)
35
+ if (!result.ok && result.status === 404) return notFound()
36
+ return result.ok
37
+ ? NextResponse.json(result.payload)
38
+ : NextResponse.json(result.payload, { status: result.status })
256
39
  }
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadConnectors } from '@/lib/server/storage'
3
2
  import { notFound } from '@/lib/server/collection-helpers'
4
3
  import { buildConnectorDoctorPreview, buildConnectorDoctorReport, type ConnectorDoctorPreviewInput } from '@/lib/server/connectors/doctor'
4
+ import { loadConnectors } from '@/lib/server/connectors/connector-repository'
5
5
 
6
6
  export const dynamic = 'force-dynamic'
7
7