@swarmclawai/swarmclaw 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +14 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +23 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -0,0 +1,429 @@
1
+ import { genId } from '@/lib/id'
2
+ import { resolveAgentToolSelection } from '@/lib/agent-default-tools'
3
+ import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
4
+ import { normalizeCapabilitySelection } from '@/lib/capability-selection'
5
+ import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
6
+ import { normalizeOrchestratorConfig } from '@/lib/orchestrator-config'
7
+ import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
8
+ import { suspendAgentReferences, purgeAgentReferences, restoreAgentSchedules } from '@/lib/server/agents/agent-cascade'
9
+ import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
10
+ import {
11
+ deleteAgent,
12
+ loadAgents,
13
+ loadTrashedAgents,
14
+ patchAgent,
15
+ saveAgent,
16
+ } from '@/lib/server/agents/agent-repository'
17
+ import { logActivity } from '@/lib/server/activity/activity-log'
18
+ import { getAgentSpendWindows } from '@/lib/server/cost'
19
+ import { serviceFail, serviceOk } from '@/lib/server/service-result'
20
+ import { listSessions, saveSession } from '@/lib/server/sessions/session-repository'
21
+ import { loadUsage } from '@/lib/server/usage/usage-repository'
22
+ import { notify } from '@/lib/server/ws-hub'
23
+ import type { Agent, Session } from '@/types'
24
+ import type { ServiceResult } from '@/lib/server/service-result'
25
+
26
+ function normalizeStringList(value: unknown): string[] {
27
+ return Array.isArray(value)
28
+ ? value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
29
+ : []
30
+ }
31
+
32
+ function normalizeOllamaMode(value: unknown): Agent['ollamaMode'] {
33
+ if (value === 'cloud') return 'cloud'
34
+ if (value === 'local') return 'local'
35
+ return null
36
+ }
37
+
38
+ function updateThreadShortcutSession(agentId: string, agent: Agent): void {
39
+ if (!agent.threadSessionId) return
40
+ const shortcut = listSessions()[agent.threadSessionId]
41
+ if (!shortcut) return
42
+ let changed = false
43
+ if (shortcut.name !== agent.name) {
44
+ shortcut.name = agent.name
45
+ changed = true
46
+ }
47
+ if (shortcut.shortcutForAgentId !== agentId) {
48
+ shortcut.shortcutForAgentId = agentId
49
+ changed = true
50
+ }
51
+ if (changed) saveSession(shortcut.id, shortcut)
52
+ }
53
+
54
+ function detachAgentSessions(agentId: string): number {
55
+ let detached = 0
56
+ for (const session of Object.values(listSessions())) {
57
+ if (!session || session.agentId !== agentId) continue
58
+ session.agentId = null
59
+ session.heartbeatEnabled = false
60
+ saveSession(session.id, session)
61
+ detached += 1
62
+ }
63
+ return detached
64
+ }
65
+
66
+ export function listAgentsForApi(): Record<string, Agent> {
67
+ const agents = loadAgents()
68
+ const sessions = listSessions()
69
+ const usage = loadUsage()
70
+ const now = Date.now()
71
+ for (const agent of Object.values(agents)) {
72
+ if (
73
+ (typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0)
74
+ || (typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0)
75
+ || (typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0)
76
+ ) {
77
+ const spend = getAgentSpendWindows(agent.id, now, {
78
+ sessions,
79
+ usage,
80
+ })
81
+ if (typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0) agent.monthlySpend = spend.monthly
82
+ if (typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0) agent.dailySpend = spend.daily
83
+ if (typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0) agent.hourlySpend = spend.hourly
84
+ }
85
+ }
86
+ return agents
87
+ }
88
+
89
+ export function createAgent(input: {
90
+ body: Record<string, unknown>
91
+ rawRecord?: Record<string, unknown> | null
92
+ }): Agent {
93
+ const body = input.body as Record<string, unknown>
94
+ const rawRecord = input.rawRecord || null
95
+ const orchestratorConfig = normalizeOrchestratorConfig({
96
+ provider: body.provider as string,
97
+ orchestratorEnabled: body.orchestratorEnabled,
98
+ orchestratorMission: body.orchestratorMission,
99
+ orchestratorWakeInterval: body.orchestratorWakeInterval,
100
+ orchestratorGovernance: body.orchestratorGovernance,
101
+ orchestratorMaxCyclesPerDay: body.orchestratorMaxCyclesPerDay,
102
+ })
103
+ const capabilitySelection = resolveAgentToolSelection({
104
+ hasExplicitTools: Boolean(rawRecord && Object.prototype.hasOwnProperty.call(rawRecord, 'tools')),
105
+ hasExplicitExtensions: Boolean(rawRecord && Object.prototype.hasOwnProperty.call(rawRecord, 'extensions')),
106
+ tools: Array.isArray(body.tools) ? normalizeStringList(body.tools) : undefined,
107
+ extensions: Array.isArray(body.extensions) ? normalizeStringList(body.extensions) : undefined,
108
+ })
109
+ const id = genId()
110
+ const now = Date.now()
111
+ const agent: Agent = {
112
+ id,
113
+ name: String(body.name || ''),
114
+ description: String(body.description || ''),
115
+ soul: typeof body.soul === 'string' && body.soul ? body.soul : undefined,
116
+ systemPrompt: String(body.systemPrompt || ''),
117
+ provider: String(body.provider || ''),
118
+ model: String(body.model || ''),
119
+ ollamaMode: body.provider === 'ollama' ? (normalizeOllamaMode(body.ollamaMode) || 'local') : null,
120
+ credentialId: (body.credentialId as string | null | undefined) || null,
121
+ fallbackCredentialIds: normalizeStringList(body.fallbackCredentialIds),
122
+ apiEndpoint: normalizeProviderEndpoint(String(body.provider || ''), (body.apiEndpoint as string | null | undefined) || null),
123
+ gatewayProfileId: (body.gatewayProfileId as string | null | undefined) || null,
124
+ preferredGatewayTags: normalizeStringList(body.preferredGatewayTags),
125
+ preferredGatewayUseCase: typeof body.preferredGatewayUseCase === 'string' && body.preferredGatewayUseCase.trim()
126
+ ? body.preferredGatewayUseCase.trim()
127
+ : null,
128
+ routingStrategy: body.routingStrategy as Agent['routingStrategy'],
129
+ routingTargets: Array.isArray(body.routingTargets)
130
+ ? body.routingTargets.map((target) => {
131
+ const row = target as Record<string, unknown>
132
+ const provider = typeof row.provider === 'string' ? row.provider : String(body.provider || '')
133
+ return {
134
+ ...row,
135
+ provider,
136
+ ollamaMode: provider === 'ollama' ? (normalizeOllamaMode(row.ollamaMode) || 'local') : null,
137
+ apiEndpoint: normalizeProviderEndpoint(provider, (row.apiEndpoint as string | null | undefined) || null),
138
+ }
139
+ }) as Agent['routingTargets']
140
+ : undefined,
141
+ delegationEnabled: body.delegationEnabled === true,
142
+ delegationTargetMode: body.delegationTargetMode === 'selected' ? 'selected' : 'all',
143
+ delegationTargetAgentIds: (body.delegationTargetMode === 'selected' ? normalizeStringList(body.delegationTargetAgentIds) : []),
144
+ tools: capabilitySelection.tools,
145
+ extensions: capabilitySelection.extensions,
146
+ skills: Array.isArray(body.skills) ? body.skills as Agent['skills'] : undefined,
147
+ skillIds: normalizeStringList(body.skillIds),
148
+ mcpServerIds: normalizeStringList(body.mcpServerIds),
149
+ mcpDisabledTools: normalizeStringList(body.mcpDisabledTools).length ? normalizeStringList(body.mcpDisabledTools) : undefined,
150
+ capabilities: Array.isArray(body.capabilities) ? body.capabilities as string[] : undefined,
151
+ thinkingLevel: (body.thinkingLevel as Agent['thinkingLevel']) || undefined,
152
+ autoRecovery: body.autoRecovery === true,
153
+ disabled: body.disabled === true,
154
+ heartbeatEnabled: body.heartbeatEnabled !== false,
155
+ heartbeatInterval: body.heartbeatInterval as Agent['heartbeatInterval'],
156
+ heartbeatIntervalSec: typeof body.heartbeatIntervalSec === 'number' ? body.heartbeatIntervalSec : null,
157
+ heartbeatModel: typeof body.heartbeatModel === 'string' ? body.heartbeatModel : undefined,
158
+ heartbeatPrompt: typeof body.heartbeatPrompt === 'string' ? body.heartbeatPrompt : undefined,
159
+ orchestratorEnabled: orchestratorConfig.orchestratorEnabled,
160
+ orchestratorMission: orchestratorConfig.orchestratorMission,
161
+ orchestratorWakeInterval: orchestratorConfig.orchestratorWakeInterval,
162
+ orchestratorGovernance: orchestratorConfig.orchestratorGovernance,
163
+ orchestratorMaxCyclesPerDay: orchestratorConfig.orchestratorMaxCyclesPerDay,
164
+ elevenLabsVoiceId: typeof body.elevenLabsVoiceId === 'string' ? body.elevenLabsVoiceId : undefined,
165
+ monthlyBudget: typeof body.monthlyBudget === 'number' ? body.monthlyBudget : null,
166
+ dailyBudget: typeof body.dailyBudget === 'number' ? body.dailyBudget : null,
167
+ hourlyBudget: typeof body.hourlyBudget === 'number' ? body.hourlyBudget : null,
168
+ budgetAction: (body.budgetAction as Agent['budgetAction']) || 'warn',
169
+ identityState: (body.identityState as Agent['identityState']) ?? null,
170
+ memoryScopeMode: (body.memoryScopeMode as Agent['memoryScopeMode']) || undefined,
171
+ memoryTierPreference: (body.memoryTierPreference as Agent['memoryTierPreference']) || undefined,
172
+ proactiveMemory: body.proactiveMemory !== false,
173
+ autoDraftSkillSuggestions: body.autoDraftSkillSuggestions as Agent['autoDraftSkillSuggestions'],
174
+ projectId: typeof body.projectId === 'string' && body.projectId.trim() ? body.projectId.trim() : undefined,
175
+ avatarSeed: typeof body.avatarSeed === 'string' ? body.avatarSeed : undefined,
176
+ avatarUrl: typeof body.avatarUrl === 'string' ? body.avatarUrl : undefined,
177
+ sessionResetMode: (body.sessionResetMode as Agent['sessionResetMode']) ?? null,
178
+ sessionIdleTimeoutSec: typeof body.sessionIdleTimeoutSec === 'number' ? body.sessionIdleTimeoutSec : null,
179
+ sessionMaxAgeSec: typeof body.sessionMaxAgeSec === 'number' ? body.sessionMaxAgeSec : null,
180
+ sessionDailyResetAt: typeof body.sessionDailyResetAt === 'string' ? body.sessionDailyResetAt : null,
181
+ sessionResetTimezone: typeof body.sessionResetTimezone === 'string' ? body.sessionResetTimezone : null,
182
+ sandboxConfig: normalizeAgentSandboxConfig(body.sandboxConfig),
183
+ createdAt: now,
184
+ updatedAt: now,
185
+ }
186
+ saveAgent(id, agent)
187
+ logActivity({ entityType: 'agent', entityId: id, action: 'created', actor: 'user', summary: `Agent created: "${agent.name}"` })
188
+ notify('agents')
189
+ return agent
190
+ }
191
+
192
+ export function updateAgent(agentId: string, body: Record<string, unknown>): Agent | null {
193
+ const updated = patchAgent(agentId, (current) => {
194
+ if (!current) return null
195
+ const agent = { ...current, ...body, updatedAt: Date.now() }
196
+ if (body.tools !== undefined || body.extensions !== undefined) {
197
+ const nextSelection = normalizeCapabilitySelection({
198
+ tools: Array.isArray(body.tools) ? body.tools : agent.tools,
199
+ extensions: Array.isArray(body.extensions) ? body.extensions : agent.extensions,
200
+ })
201
+ agent.tools = nextSelection.tools
202
+ agent.extensions = nextSelection.extensions
203
+ }
204
+ if (body.delegationEnabled !== undefined) {
205
+ agent.delegationEnabled = body.delegationEnabled === true
206
+ }
207
+ if (body.delegationTargetMode === 'all' || body.delegationTargetMode === 'selected') {
208
+ agent.delegationTargetMode = body.delegationTargetMode
209
+ }
210
+ if (body.delegationTargetAgentIds !== undefined) {
211
+ agent.delegationTargetAgentIds = normalizeStringList(body.delegationTargetAgentIds)
212
+ }
213
+ if (agent.delegationTargetMode !== 'selected') {
214
+ agent.delegationTargetAgentIds = []
215
+ }
216
+ if (body.apiEndpoint !== undefined) {
217
+ agent.apiEndpoint = normalizeProviderEndpoint(
218
+ (body.provider as string) || agent.provider,
219
+ body.apiEndpoint as string | null | undefined,
220
+ )
221
+ }
222
+ if (body.provider !== undefined && body.provider !== 'ollama' && body.ollamaMode === undefined) {
223
+ agent.ollamaMode = null
224
+ }
225
+ if (body.sandboxConfig !== undefined) {
226
+ agent.sandboxConfig = normalizeAgentSandboxConfig(body.sandboxConfig)
227
+ }
228
+ if (
229
+ body.provider !== undefined
230
+ || body.orchestratorEnabled !== undefined
231
+ || body.orchestratorMission !== undefined
232
+ || body.orchestratorWakeInterval !== undefined
233
+ || body.orchestratorGovernance !== undefined
234
+ || body.orchestratorMaxCyclesPerDay !== undefined
235
+ ) {
236
+ const orchestratorConfig = normalizeOrchestratorConfig({
237
+ provider: typeof body.provider === 'string' ? body.provider : agent.provider,
238
+ orchestratorEnabled: body.orchestratorEnabled ?? agent.orchestratorEnabled,
239
+ orchestratorMission: body.orchestratorMission ?? agent.orchestratorMission,
240
+ orchestratorWakeInterval: body.orchestratorWakeInterval ?? agent.orchestratorWakeInterval,
241
+ orchestratorGovernance: body.orchestratorGovernance ?? agent.orchestratorGovernance,
242
+ orchestratorMaxCyclesPerDay: body.orchestratorMaxCyclesPerDay ?? agent.orchestratorMaxCyclesPerDay,
243
+ })
244
+ agent.orchestratorEnabled = orchestratorConfig.orchestratorEnabled
245
+ agent.orchestratorMission = orchestratorConfig.orchestratorMission
246
+ agent.orchestratorWakeInterval = orchestratorConfig.orchestratorWakeInterval
247
+ agent.orchestratorGovernance = orchestratorConfig.orchestratorGovernance
248
+ agent.orchestratorMaxCyclesPerDay = orchestratorConfig.orchestratorMaxCyclesPerDay
249
+ }
250
+ if (body.preferredGatewayTags !== undefined) {
251
+ agent.preferredGatewayTags = normalizeStringList(body.preferredGatewayTags)
252
+ }
253
+ if (body.preferredGatewayUseCase !== undefined) {
254
+ agent.preferredGatewayUseCase = typeof body.preferredGatewayUseCase === 'string' && body.preferredGatewayUseCase.trim()
255
+ ? body.preferredGatewayUseCase.trim()
256
+ : null
257
+ }
258
+ if (body.routingTargets !== undefined && Array.isArray(body.routingTargets)) {
259
+ agent.routingTargets = body.routingTargets.map((target, index) => {
260
+ const row = target as Record<string, unknown>
261
+ const provider = typeof row.provider === 'string' && row.provider.trim() ? row.provider : agent.provider
262
+ return {
263
+ id: typeof row.id === 'string' && row.id.trim() ? row.id.trim() : `route-${index + 1}`,
264
+ label: typeof row.label === 'string' ? row.label : undefined,
265
+ role: row.role,
266
+ provider,
267
+ model: typeof row.model === 'string' ? row.model : '',
268
+ ollamaMode: provider === 'ollama'
269
+ ? (row.ollamaMode === 'cloud' ? 'cloud' : 'local')
270
+ : null,
271
+ credentialId: row.credentialId ?? null,
272
+ fallbackCredentialIds: Array.isArray(row.fallbackCredentialIds) ? row.fallbackCredentialIds : [],
273
+ apiEndpoint: normalizeProviderEndpoint(
274
+ provider,
275
+ typeof row.apiEndpoint === 'string' ? row.apiEndpoint : null,
276
+ ),
277
+ gatewayProfileId: row.gatewayProfileId ?? null,
278
+ preferredGatewayTags: normalizeStringList(row.preferredGatewayTags),
279
+ preferredGatewayUseCase: typeof row.preferredGatewayUseCase === 'string' && row.preferredGatewayUseCase.trim()
280
+ ? row.preferredGatewayUseCase.trim()
281
+ : null,
282
+ priority: typeof row.priority === 'number' ? row.priority : index + 1,
283
+ }
284
+ }) as Agent['routingTargets']
285
+ }
286
+ delete (agent as Record<string, unknown>).platformAssignScope
287
+ delete (agent as Record<string, unknown>).subAgentIds
288
+ delete (agent as Record<string, unknown>).id
289
+ agent.id = agentId
290
+ return agent as Agent
291
+ })
292
+ if (!updated) return null
293
+
294
+ if (updated.threadSessionId) {
295
+ ensureAgentThreadSession(agentId)
296
+ updateThreadShortcutSession(agentId, updated)
297
+ }
298
+
299
+ logActivity({ entityType: 'agent', entityId: agentId, action: 'updated', actor: 'user', summary: `Agent updated: "${updated.name}"` })
300
+ return updated
301
+ }
302
+
303
+ export function trashAgent(agentId: string): { ok: false } | { ok: true; detachedSessions: number; cascade: ReturnType<typeof suspendAgentReferences> } {
304
+ const trashed = patchAgent(agentId, (current) => {
305
+ if (!current) return null
306
+ return { ...current, trashedAt: Date.now() }
307
+ })
308
+ if (!trashed) return { ok: false }
309
+
310
+ logActivity({ entityType: 'agent', entityId: agentId, action: 'deleted', actor: 'user', summary: `Agent trashed: "${trashed.name}"` })
311
+ const detachedSessions = detachAgentSessions(agentId)
312
+ const cascade = suspendAgentReferences(agentId)
313
+ return { ok: true, detachedSessions, cascade }
314
+ }
315
+
316
+ export function restoreTrashedAgent(agentId: string): Agent | null {
317
+ const agent = patchAgent(agentId, (current) => {
318
+ if (!current || !current.trashedAt) return null
319
+ const next = { ...current }
320
+ delete next.trashedAt
321
+ next.updatedAt = Date.now()
322
+ return next
323
+ })
324
+ if (!agent) return null
325
+ notify('agents')
326
+ const restoredSchedules = restoreAgentSchedules(agentId)
327
+ if (restoredSchedules) notify('schedules')
328
+ return agent
329
+ }
330
+
331
+ export function permanentlyDeleteTrashedAgent(agentId: string): { ok: false; reason: 'not_found' | 'not_trashed' } | { ok: true; purged: ReturnType<typeof purgeAgentReferences> } {
332
+ const agent = loadAgents({ includeTrashed: true })[agentId]
333
+ if (!agent) return { ok: false, reason: 'not_found' }
334
+ if (!agent.trashedAt) return { ok: false, reason: 'not_trashed' }
335
+
336
+ const purged = purgeAgentReferences(agentId)
337
+ deleteAgent(agentId)
338
+ notify('agents')
339
+ return { ok: true, purged }
340
+ }
341
+
342
+ export function cloneAgent(agentId: string): Agent | null {
343
+ const source = loadAgents({ includeTrashed: true })[agentId]
344
+ if (!source) return null
345
+ const newId = crypto.randomUUID()
346
+ const now = Date.now()
347
+ const cloned = JSON.parse(JSON.stringify(source)) as Agent
348
+ cloned.id = newId
349
+ cloned.name = `${source.name} (Copy)`
350
+ cloned.createdAt = now
351
+ cloned.updatedAt = now
352
+ cloned.totalCost = 0
353
+ cloned.lastUsedAt = undefined
354
+ cloned.threadSessionId = null
355
+ cloned.pinned = false
356
+ cloned.trashedAt = undefined
357
+
358
+ saveAgent(newId, cloned)
359
+ logActivity({
360
+ entityType: 'agent',
361
+ entityId: newId,
362
+ action: 'created',
363
+ actor: 'user',
364
+ summary: `Agent cloned from "${source.name}": "${cloned.name}"`,
365
+ })
366
+ notify('agents')
367
+ return cloned
368
+ }
369
+
370
+ export function bulkPatchAgents(patches: unknown): { updated: number; errors: string[] } {
371
+ if (!Array.isArray(patches) || patches.length === 0) {
372
+ return { updated: 0, errors: ['patches must be a non-empty array'] }
373
+ }
374
+ let updated = 0
375
+ const errors: string[] = []
376
+ for (const entry of patches) {
377
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
378
+ errors.push('Invalid patch entry (not an object)')
379
+ continue
380
+ }
381
+ const { id, patch } = entry as { id?: unknown; patch?: unknown }
382
+ if (typeof id !== 'string' || !id.trim()) {
383
+ errors.push('Patch entry missing valid id')
384
+ continue
385
+ }
386
+ if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
387
+ errors.push(`Patch for ${id} is not a valid object`)
388
+ continue
389
+ }
390
+ const result = patchAgent(id, (current) => current ? { ...current, ...(patch as Record<string, unknown>), updatedAt: Date.now() } : null)
391
+ if (!result) {
392
+ errors.push(`Agent ${id} not found`)
393
+ continue
394
+ }
395
+ updated += 1
396
+ logActivity({
397
+ entityType: 'agent',
398
+ entityId: id,
399
+ action: 'updated',
400
+ actor: 'user',
401
+ summary: `Bulk patch: updated agent "${result.name || id}"`,
402
+ })
403
+ }
404
+ if (updated > 0) notify('agents')
405
+ return { updated, errors }
406
+ }
407
+
408
+ export function getAgentThreadSession(agentId: string, user = 'default'): ServiceResult<Session> {
409
+ const agent = loadAgents()[agentId]
410
+ if (!agent) {
411
+ return serviceFail(404, 'Agent not found')
412
+ }
413
+ const session = ensureAgentThreadSession(agentId, user, agent)
414
+ if (!session) {
415
+ if (isAgentDisabled(agent)) {
416
+ return serviceFail(409, buildAgentDisabledMessage(agent, 'start new chats'))
417
+ }
418
+ return serviceFail(404, 'Agent not found')
419
+ }
420
+ return serviceOk(session)
421
+ }
422
+
423
+ export function getAgentStatus(agentId: string): Agent | null {
424
+ return loadAgents()[agentId] || null
425
+ }
426
+
427
+ export function listTrashedAgentsForApi(): Record<string, Agent> {
428
+ return loadTrashedAgents()
429
+ }
@@ -2,9 +2,10 @@ import { genId } from '@/lib/id'
2
2
  import type { Agent, Session } from '@/types'
3
3
  import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
4
4
  import { isAgentDisabled } from '@/lib/server/agents/agent-availability'
5
+ import { loadAgent, loadAgents, upsertAgent } from '@/lib/server/agents/agent-repository'
5
6
  import { WORKSPACE_DIR } from '@/lib/server/data-dir'
6
- import { loadAgents, loadSession, loadSessions, upsertAgent, upsertStoredItem } from '@/lib/server/storage'
7
7
  import { getEnabledCapabilitySelection } from '@/lib/capability-selection'
8
+ import { loadSession, loadSessions, upsertSession } from '@/lib/server/sessions/session-repository'
8
9
 
9
10
  function buildEmptyDelegateResumeIds(): NonNullable<Session['delegateResumeIds']> {
10
11
  return {
@@ -108,7 +109,7 @@ function shouldHealAgentCredentialId(agent: Agent, session: Session): boolean {
108
109
  }
109
110
 
110
111
  export function ensureAgentThreadSession(agentId: string, user = 'default', preloadedAgent?: Agent): Session | null {
111
- const agent = preloadedAgent ?? (loadAgents()[agentId] as Agent | undefined)
112
+ const agent = preloadedAgent ?? loadAgent(agentId) ?? (loadAgents()[agentId] as Agent | undefined)
112
113
  if (!agent) return null
113
114
 
114
115
  const now = Date.now()
@@ -124,7 +125,7 @@ export function ensureAgentThreadSession(agentId: string, user = 'default', prel
124
125
  agent.updatedAt = now
125
126
  upsertAgent(agentId, agent)
126
127
  }
127
- upsertStoredItem('sessions', existingId, session)
128
+ upsertSession(existingId, session)
128
129
  return session
129
130
  }
130
131
  // Session was deleted — fall through to legacy search / creation
@@ -147,7 +148,7 @@ export function ensureAgentThreadSession(agentId: string, user = 'default', prel
147
148
  }
148
149
  agent.updatedAt = now
149
150
  upsertAgent(agentId, agent)
150
- upsertStoredItem('sessions', legacySession.id, session)
151
+ upsertSession(legacySession.id, session)
151
152
  return session
152
153
  }
153
154
 
@@ -158,7 +159,7 @@ export function ensureAgentThreadSession(agentId: string, user = 'default', prel
158
159
  if (shouldHealAgentCredentialId(agent, session)) {
159
160
  agent.credentialId = session.credentialId ?? null
160
161
  }
161
- upsertStoredItem('sessions', sessionId, session)
162
+ upsertSession(sessionId, session)
162
163
 
163
164
  agent.threadSessionId = sessionId
164
165
  agent.updatedAt = now
@@ -1,4 +1,5 @@
1
1
  import type { GoalContract } from '@/types'
2
+ import { cleanText } from '@/lib/server/text-normalization'
2
3
 
3
4
  const PLAN_LINE_RE = /\[MAIN_LOOP_PLAN\]\s*(\{[^\n]*\})/i
4
5
  const REVIEW_LINE_RE = /\[MAIN_LOOP_REVIEW\]\s*(\{[^\n]*\})/i
@@ -15,10 +16,6 @@ export interface MainLoopReviewMeta {
15
16
  needs_replan?: boolean
16
17
  }
17
18
 
18
- function cleanText(value: string, max = 400): string {
19
- return (value || '').replace(/\s+/g, ' ').trim().slice(0, max)
20
- }
21
-
22
19
  function uniqueStrings(input: string[]): string[] {
23
20
  const seen = new Set<string>()
24
21
  const out: string[] = []