@swarmclawai/swarmclaw 0.7.2 → 0.7.4

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 (274) hide show
  1. package/README.md +116 -50
  2. package/bin/package-manager.js +157 -0
  3. package/bin/package-manager.test.js +90 -0
  4. package/bin/server-cmd.js +38 -7
  5. package/bin/swarmclaw.js +54 -4
  6. package/bin/update-cmd.js +48 -10
  7. package/bin/update-cmd.test.js +55 -0
  8. package/package.json +8 -3
  9. package/scripts/postinstall.mjs +26 -0
  10. package/src/app/api/agents/[id]/route.ts +43 -0
  11. package/src/app/api/agents/[id]/thread/route.ts +39 -8
  12. package/src/app/api/agents/route.ts +35 -2
  13. package/src/app/api/auth/route.ts +77 -8
  14. package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
  15. package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
  16. package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
  17. package/src/app/api/chatrooms/[id]/route.ts +6 -0
  18. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  19. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  20. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  21. package/src/app/api/chats/[id]/route.ts +30 -0
  22. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  23. package/src/app/api/chats/heartbeat/route.ts +2 -1
  24. package/src/app/api/chats/route.ts +23 -1
  25. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  26. package/src/app/api/connectors/doctor/route.ts +13 -0
  27. package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
  28. package/src/app/api/external-agents/[id]/route.ts +31 -0
  29. package/src/app/api/external-agents/register/route.ts +3 -0
  30. package/src/app/api/external-agents/route.ts +66 -0
  31. package/src/app/api/files/open/route.ts +16 -14
  32. package/src/app/api/gateways/[id]/health/route.ts +28 -0
  33. package/src/app/api/gateways/[id]/route.ts +79 -0
  34. package/src/app/api/gateways/route.ts +57 -0
  35. package/src/app/api/memory/maintenance/route.ts +11 -1
  36. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  37. package/src/app/api/openclaw/gateway/route.ts +10 -7
  38. package/src/app/api/openclaw/skills/route.ts +12 -4
  39. package/src/app/api/plugins/dependencies/route.ts +24 -0
  40. package/src/app/api/plugins/install/route.ts +15 -92
  41. package/src/app/api/plugins/route.ts +3 -26
  42. package/src/app/api/plugins/settings/route.ts +17 -12
  43. package/src/app/api/plugins/ui/route.ts +1 -0
  44. package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
  45. package/src/app/api/schedules/[id]/route.ts +38 -9
  46. package/src/app/api/schedules/route.ts +51 -28
  47. package/src/app/api/settings/route.ts +55 -17
  48. package/src/app/api/setup/doctor/route.ts +6 -4
  49. package/src/app/api/tasks/[id]/route.ts +16 -6
  50. package/src/app/api/tasks/bulk/route.ts +3 -3
  51. package/src/app/api/tasks/route.ts +9 -4
  52. package/src/app/api/webhooks/[id]/route.ts +8 -1
  53. package/src/app/page.tsx +135 -17
  54. package/src/cli/binary.test.js +142 -0
  55. package/src/cli/index.js +38 -11
  56. package/src/cli/index.test.js +195 -0
  57. package/src/cli/index.ts +21 -12
  58. package/src/cli/server-cmd.test.js +59 -0
  59. package/src/cli/spec.js +20 -2
  60. package/src/components/agents/agent-card.tsx +15 -12
  61. package/src/components/agents/agent-chat-list.tsx +101 -1
  62. package/src/components/agents/agent-list.tsx +46 -9
  63. package/src/components/agents/agent-sheet.tsx +456 -23
  64. package/src/components/agents/inspector-panel.tsx +110 -49
  65. package/src/components/agents/sandbox-env-panel.tsx +4 -1
  66. package/src/components/auth/access-key-gate.tsx +36 -97
  67. package/src/components/auth/setup-wizard.tsx +970 -275
  68. package/src/components/chat/chat-area.tsx +70 -27
  69. package/src/components/chat/chat-card.tsx +6 -21
  70. package/src/components/chat/chat-header.tsx +263 -366
  71. package/src/components/chat/chat-list.tsx +62 -26
  72. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  73. package/src/components/chat/message-list.tsx +145 -19
  74. package/src/components/chatrooms/chatroom-input.tsx +96 -33
  75. package/src/components/chatrooms/chatroom-list.tsx +141 -72
  76. package/src/components/chatrooms/chatroom-message.tsx +7 -6
  77. package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
  78. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
  79. package/src/components/chatrooms/chatroom-view.tsx +422 -209
  80. package/src/components/chatrooms/reaction-picker.tsx +38 -33
  81. package/src/components/connectors/connector-list.tsx +265 -127
  82. package/src/components/connectors/connector-sheet.tsx +217 -0
  83. package/src/components/gateways/gateway-sheet.tsx +567 -0
  84. package/src/components/home/home-view.tsx +128 -4
  85. package/src/components/input/chat-input.tsx +135 -86
  86. package/src/components/layout/app-layout.tsx +385 -194
  87. package/src/components/layout/mobile-header.tsx +26 -8
  88. package/src/components/memory/memory-browser.tsx +71 -6
  89. package/src/components/memory/memory-card.tsx +18 -0
  90. package/src/components/memory/memory-detail.tsx +58 -31
  91. package/src/components/memory/memory-sheet.tsx +32 -4
  92. package/src/components/plugins/plugin-list.tsx +15 -3
  93. package/src/components/plugins/plugin-sheet.tsx +118 -9
  94. package/src/components/projects/project-detail.tsx +189 -1
  95. package/src/components/providers/provider-list.tsx +158 -2
  96. package/src/components/providers/provider-sheet.tsx +81 -70
  97. package/src/components/shared/agent-picker-list.tsx +2 -2
  98. package/src/components/shared/bottom-sheet.tsx +31 -15
  99. package/src/components/shared/command-palette.tsx +111 -24
  100. package/src/components/shared/confirm-dialog.tsx +45 -30
  101. package/src/components/shared/model-combobox.tsx +90 -8
  102. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  103. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  104. package/src/components/shared/settings/section-heartbeat.tsx +88 -6
  105. package/src/components/shared/settings/section-orchestrator.tsx +6 -3
  106. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  107. package/src/components/shared/settings/section-secrets.tsx +6 -6
  108. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  109. package/src/components/shared/settings/section-voice.tsx +5 -1
  110. package/src/components/shared/settings/section-web-search.tsx +10 -2
  111. package/src/components/shared/settings/settings-page.tsx +248 -47
  112. package/src/components/tasks/approvals-panel.tsx +211 -18
  113. package/src/components/tasks/task-board.tsx +242 -46
  114. package/src/components/ui/dialog.tsx +2 -2
  115. package/src/components/usage/metrics-dashboard.tsx +74 -1
  116. package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
  117. package/src/components/wallets/wallet-panel.tsx +17 -5
  118. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  119. package/src/lib/auth.ts +17 -0
  120. package/src/lib/chat-streaming-state.test.ts +108 -0
  121. package/src/lib/chat-streaming-state.ts +108 -0
  122. package/src/lib/heartbeat-defaults.ts +48 -0
  123. package/src/lib/memory-presentation.ts +59 -0
  124. package/src/lib/openclaw-agent-id.test.ts +14 -0
  125. package/src/lib/openclaw-agent-id.ts +31 -0
  126. package/src/lib/provider-model-discovery-client.ts +29 -0
  127. package/src/lib/providers/index.ts +12 -5
  128. package/src/lib/runtime-loop.ts +105 -3
  129. package/src/lib/safe-storage.ts +6 -1
  130. package/src/lib/server/agent-assignment.test.ts +112 -0
  131. package/src/lib/server/agent-assignment.ts +169 -0
  132. package/src/lib/server/agent-runtime-config.test.ts +141 -0
  133. package/src/lib/server/agent-runtime-config.ts +277 -0
  134. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  135. package/src/lib/server/approvals-auto-approve.test.ts +264 -0
  136. package/src/lib/server/approvals.ts +483 -75
  137. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  138. package/src/lib/server/browser-state.test.ts +118 -0
  139. package/src/lib/server/browser-state.ts +123 -0
  140. package/src/lib/server/build-llm.test.ts +44 -0
  141. package/src/lib/server/build-llm.ts +11 -4
  142. package/src/lib/server/builtin-plugins.ts +34 -0
  143. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  144. package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
  145. package/src/lib/server/chat-execution.ts +402 -125
  146. package/src/lib/server/chatroom-health.test.ts +26 -0
  147. package/src/lib/server/chatroom-health.ts +2 -3
  148. package/src/lib/server/chatroom-helpers.test.ts +74 -2
  149. package/src/lib/server/chatroom-helpers.ts +144 -11
  150. package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
  151. package/src/lib/server/connectors/discord.ts +175 -11
  152. package/src/lib/server/connectors/doctor.test.ts +80 -0
  153. package/src/lib/server/connectors/doctor.ts +116 -0
  154. package/src/lib/server/connectors/manager.ts +994 -130
  155. package/src/lib/server/connectors/policy.test.ts +222 -0
  156. package/src/lib/server/connectors/policy.ts +452 -0
  157. package/src/lib/server/connectors/slack.ts +189 -10
  158. package/src/lib/server/connectors/telegram.ts +65 -15
  159. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  160. package/src/lib/server/connectors/thread-context.ts +72 -0
  161. package/src/lib/server/connectors/types.ts +41 -11
  162. package/src/lib/server/daemon-state.ts +62 -3
  163. package/src/lib/server/data-dir.ts +13 -0
  164. package/src/lib/server/delegation-jobs.test.ts +140 -0
  165. package/src/lib/server/delegation-jobs.ts +248 -0
  166. package/src/lib/server/document-utils.test.ts +47 -0
  167. package/src/lib/server/document-utils.ts +397 -0
  168. package/src/lib/server/eval/agent-regression.test.ts +47 -0
  169. package/src/lib/server/eval/agent-regression.ts +1742 -0
  170. package/src/lib/server/eval/runner.ts +11 -1
  171. package/src/lib/server/eval/store.ts +2 -1
  172. package/src/lib/server/heartbeat-service.ts +23 -43
  173. package/src/lib/server/heartbeat-source.test.ts +22 -0
  174. package/src/lib/server/heartbeat-source.ts +7 -0
  175. package/src/lib/server/identity-continuity.test.ts +77 -0
  176. package/src/lib/server/identity-continuity.ts +127 -0
  177. package/src/lib/server/mailbox-utils.ts +347 -0
  178. package/src/lib/server/main-agent-loop.ts +31 -964
  179. package/src/lib/server/memory-db.ts +4 -6
  180. package/src/lib/server/memory-tiers.ts +40 -0
  181. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  182. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  183. package/src/lib/server/openclaw-exec-config.ts +6 -5
  184. package/src/lib/server/openclaw-gateway.ts +123 -36
  185. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  186. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  187. package/src/lib/server/openclaw-sync.ts +3 -2
  188. package/src/lib/server/orchestrator-lg.ts +18 -8
  189. package/src/lib/server/orchestrator.ts +5 -4
  190. package/src/lib/server/playwright-proxy.mjs +27 -3
  191. package/src/lib/server/plugins.test.ts +215 -0
  192. package/src/lib/server/plugins.ts +832 -69
  193. package/src/lib/server/provider-health.ts +33 -3
  194. package/src/lib/server/provider-model-discovery.ts +481 -0
  195. package/src/lib/server/queue.ts +4 -21
  196. package/src/lib/server/runtime-settings.test.ts +119 -0
  197. package/src/lib/server/runtime-settings.ts +12 -92
  198. package/src/lib/server/schedule-normalization.ts +187 -0
  199. package/src/lib/server/scheduler.ts +2 -0
  200. package/src/lib/server/session-archive-memory.test.ts +85 -0
  201. package/src/lib/server/session-archive-memory.ts +230 -0
  202. package/src/lib/server/session-mailbox.ts +8 -18
  203. package/src/lib/server/session-reset-policy.test.ts +99 -0
  204. package/src/lib/server/session-reset-policy.ts +311 -0
  205. package/src/lib/server/session-run-manager.ts +33 -80
  206. package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
  207. package/src/lib/server/session-tools/calendar.ts +2 -12
  208. package/src/lib/server/session-tools/connector.ts +109 -8
  209. package/src/lib/server/session-tools/context.ts +14 -2
  210. package/src/lib/server/session-tools/crawl.ts +447 -0
  211. package/src/lib/server/session-tools/crud.ts +96 -34
  212. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  213. package/src/lib/server/session-tools/delegate.ts +406 -20
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
  215. package/src/lib/server/session-tools/discovery.ts +40 -12
  216. package/src/lib/server/session-tools/document.ts +283 -0
  217. package/src/lib/server/session-tools/email.ts +1 -3
  218. package/src/lib/server/session-tools/extract.ts +137 -0
  219. package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
  220. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  221. package/src/lib/server/session-tools/file.ts +243 -24
  222. package/src/lib/server/session-tools/http.ts +9 -3
  223. package/src/lib/server/session-tools/human-loop.ts +227 -0
  224. package/src/lib/server/session-tools/image-gen.ts +1 -3
  225. package/src/lib/server/session-tools/index.ts +87 -2
  226. package/src/lib/server/session-tools/mailbox.ts +276 -0
  227. package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
  228. package/src/lib/server/session-tools/memory.ts +35 -3
  229. package/src/lib/server/session-tools/monitor.ts +162 -12
  230. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  231. package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
  232. package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
  233. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  234. package/src/lib/server/session-tools/platform.ts +142 -4
  235. package/src/lib/server/session-tools/plugin-creator.ts +95 -25
  236. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  237. package/src/lib/server/session-tools/replicate.ts +1 -3
  238. package/src/lib/server/session-tools/sandbox.ts +51 -92
  239. package/src/lib/server/session-tools/schedule.ts +20 -10
  240. package/src/lib/server/session-tools/session-info.ts +58 -4
  241. package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
  242. package/src/lib/server/session-tools/shell.ts +2 -2
  243. package/src/lib/server/session-tools/subagent.ts +195 -27
  244. package/src/lib/server/session-tools/table.ts +587 -0
  245. package/src/lib/server/session-tools/wallet.ts +13 -10
  246. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  247. package/src/lib/server/session-tools/web.ts +947 -108
  248. package/src/lib/server/storage.ts +255 -10
  249. package/src/lib/server/stream-agent-chat.test.ts +61 -0
  250. package/src/lib/server/stream-agent-chat.ts +185 -25
  251. package/src/lib/server/structured-extract.test.ts +72 -0
  252. package/src/lib/server/structured-extract.ts +373 -0
  253. package/src/lib/server/task-mention.test.ts +16 -2
  254. package/src/lib/server/task-mention.ts +61 -11
  255. package/src/lib/server/tool-aliases.ts +80 -12
  256. package/src/lib/server/tool-capability-policy.ts +7 -1
  257. package/src/lib/server/tool-retry.ts +2 -0
  258. package/src/lib/server/watch-jobs.test.ts +173 -0
  259. package/src/lib/server/watch-jobs.ts +532 -0
  260. package/src/lib/server/ws-hub.ts +5 -3
  261. package/src/lib/setup-defaults.ts +352 -11
  262. package/src/lib/tool-definitions.ts +3 -4
  263. package/src/lib/validation/schemas.test.ts +26 -0
  264. package/src/lib/validation/schemas.ts +62 -1
  265. package/src/lib/ws-client.ts +14 -12
  266. package/src/proxy.ts +5 -5
  267. package/src/stores/use-app-store.ts +43 -7
  268. package/src/stores/use-chat-store.ts +31 -2
  269. package/src/stores/use-chatroom-store.ts +153 -26
  270. package/src/types/index.ts +470 -44
  271. package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
  272. package/src/components/chat/new-chat-sheet.tsx +0 -253
  273. package/src/lib/server/main-session.ts +0 -17
  274. package/src/lib/server/session-run-manager.test.ts +0 -26
@@ -7,7 +7,7 @@ import { api } from '@/lib/api-client'
7
7
  import { BottomSheet } from '@/components/shared/bottom-sheet'
8
8
  import { toast } from 'sonner'
9
9
  import { ModelCombobox } from '@/components/shared/model-combobox'
10
- import type { ProviderType, ClaudeSkill, AgentWallet } from '@/types'
10
+ import type { ProviderType, ClaudeSkill, AgentWallet, AgentPackManifest, AgentRoutingStrategy, AgentRoutingTarget } from '@/types'
11
11
  import { WalletSection } from '@/components/wallets/wallet-section'
12
12
  import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
13
13
  import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
@@ -54,6 +54,24 @@ function parseDurationToSec(interval: string | number | null | undefined, interv
54
54
  return '' // default
55
55
  }
56
56
 
57
+ function formatIdentityList(value: string[] | null | undefined): string {
58
+ return Array.isArray(value) ? value.join('\n') : ''
59
+ }
60
+
61
+ function parseIdentityList(value: string): string[] {
62
+ const seen = new Set<string>()
63
+ return value
64
+ .split('\n')
65
+ .map((line) => line.replace(/\s+/g, ' ').trim())
66
+ .filter((line) => {
67
+ if (!line) return false
68
+ const key = line.toLowerCase()
69
+ if (seen.has(key)) return false
70
+ seen.add(key)
71
+ return true
72
+ })
73
+ }
74
+
57
75
  export function AgentSheet() {
58
76
  const open = useAppStore((s) => s.agentSheetOpen)
59
77
  const setOpen = useAppStore((s) => s.setAgentSheetOpen)
@@ -65,6 +83,8 @@ export function AgentSheet() {
65
83
  const loadProjects = useAppStore((s) => s.loadProjects)
66
84
  const providers = useAppStore((s) => s.providers)
67
85
  const loadProviders = useAppStore((s) => s.loadProviders)
86
+ const gatewayProfiles = useAppStore((s) => s.gatewayProfiles)
87
+ const loadGatewayProfiles = useAppStore((s) => s.loadGatewayProfiles)
68
88
  const credentials = useAppStore((s) => s.credentials)
69
89
  const loadCredentials = useAppStore((s) => s.loadCredentials)
70
90
  const appSettings = useAppStore((s) => s.appSettings)
@@ -92,7 +112,10 @@ export function AgentSheet() {
92
112
  const [model, setModel] = useState('')
93
113
  const [credentialId, setCredentialId] = useState<string | null>(null)
94
114
  const [apiEndpoint, setApiEndpoint] = useState<string | null>(null)
95
- const [isOrchestrator, setIsOrchestrator] = useState(false)
115
+ const [gatewayProfileId, setGatewayProfileId] = useState<string | null>(null)
116
+ const [routingStrategy, setRoutingStrategy] = useState<AgentRoutingStrategy>('single')
117
+ const [routingTargets, setRoutingTargets] = useState<AgentRoutingTarget[]>([])
118
+ const [platformAssignScope, setPlatformAssignScope] = useState<'self' | 'all'>('self')
96
119
  const [subAgentIds, setAgentAgentIds] = useState<string[]>([])
97
120
  const [tools, setTools] = useState<string[]>([])
98
121
  const [skills, setSkills] = useState<string[]>([])
@@ -111,12 +134,25 @@ export function AgentSheet() {
111
134
  const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
112
135
  const [uploading, setUploading] = useState(false)
113
136
  const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
137
+ const [memoryScopeMode, setMemoryScopeMode] = useState<'auto' | 'all' | 'global' | 'agent' | 'session' | 'project'>('auto')
138
+ const [memoryTierPreference, setMemoryTierPreference] = useState<'working' | 'durable' | 'archive' | 'blended'>('blended')
114
139
  const [autoRecovery, setAutoRecovery] = useState(false)
115
140
  const [voiceId, setVoiceId] = useState('')
116
141
  const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
117
142
  const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
118
143
  const [heartbeatModel, setHeartbeatModel] = useState('')
119
144
  const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
145
+ const [sessionResetMode, setSessionResetMode] = useState<'' | 'idle' | 'daily'>('')
146
+ const [sessionIdleTimeoutSec, setSessionIdleTimeoutSec] = useState('')
147
+ const [sessionMaxAgeSec, setSessionMaxAgeSec] = useState('')
148
+ const [sessionDailyResetAt, setSessionDailyResetAt] = useState('')
149
+ const [sessionResetTimezone, setSessionResetTimezone] = useState('')
150
+ const [identityPersonaLabel, setIdentityPersonaLabel] = useState('')
151
+ const [identitySelfSummary, setIdentitySelfSummary] = useState('')
152
+ const [identityRelationshipSummary, setIdentityRelationshipSummary] = useState('')
153
+ const [identityToneStyle, setIdentityToneStyle] = useState('')
154
+ const [identityBoundariesText, setIdentityBoundariesText] = useState('')
155
+ const [identityContinuityNotesText, setIdentityContinuityNotesText] = useState('')
120
156
  const [budgetEnabled, setBudgetEnabled] = useState(false)
121
157
  const [hourlyBudget, setHourlyBudget] = useState('')
122
158
  const [dailyBudget, setDailyBudget] = useState('')
@@ -153,6 +189,7 @@ export function AgentSheet() {
153
189
  const currentProvider = providers.find((p) => p.id === provider)
154
190
  const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
155
191
  const openclawCredentials = Object.values(credentials).filter((c) => c.provider === 'openclaw')
192
+ const openclawGatewayProfiles = gatewayProfiles.filter((item) => item.provider === 'openclaw')
156
193
  const editing = editingId ? agents[editingId] : null
157
194
  const hasNativeCapabilities = NATIVE_CAPABILITY_PROVIDER_IDS.has(provider)
158
195
 
@@ -164,6 +201,7 @@ export function AgentSheet() {
164
201
  useEffect(() => {
165
202
  if (open) {
166
203
  loadProviders()
204
+ loadGatewayProfiles()
167
205
  loadCredentials()
168
206
  loadSkills()
169
207
  loadProjects()
@@ -181,7 +219,10 @@ export function AgentSheet() {
181
219
  setModel(editing.model)
182
220
  setCredentialId(editing.credentialId || null)
183
221
  setApiEndpoint(editing.apiEndpoint || null)
184
- setIsOrchestrator(editing.isOrchestrator || false)
222
+ setGatewayProfileId(editing.gatewayProfileId || null)
223
+ setRoutingStrategy(editing.routingStrategy || 'single')
224
+ setRoutingTargets(editing.routingTargets || [])
225
+ setPlatformAssignScope(editing.platformAssignScope || 'self')
185
226
  setAgentAgentIds(editing.subAgentIds || [])
186
227
  setTools(editing.plugins || [])
187
228
  setSkills(editing.skills || [])
@@ -189,7 +230,6 @@ export function AgentSheet() {
189
230
  setMcpServerIds(editing.mcpServerIds || [])
190
231
  setMcpDisabledTools(editing.mcpDisabledTools || [])
191
232
  setFallbackCredentialIds(editing.fallbackCredentialIds || [])
192
- // platformAssignScope derived from isOrchestrator — no separate state
193
233
  setCapabilities(Array.isArray(editing.capabilities) ? editing.capabilities : [])
194
234
  setCapInput('')
195
235
  setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
@@ -198,12 +238,25 @@ export function AgentSheet() {
198
238
  setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
199
239
  setAvatarUrl(editing.avatarUrl || null)
200
240
  setThinkingLevel(editing.thinkingLevel || '')
241
+ setMemoryScopeMode(editing.memoryScopeMode || 'auto')
242
+ setMemoryTierPreference(editing.memoryTierPreference || 'blended')
201
243
  setAutoRecovery(editing.autoRecovery || false)
202
244
  setVoiceId(editing.elevenLabsVoiceId || '')
203
245
  setHeartbeatEnabled(editing.heartbeatEnabled || false)
204
246
  setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
205
247
  setHeartbeatModel(editing.heartbeatModel || '')
206
248
  setHeartbeatPrompt(editing.heartbeatPrompt || '')
249
+ setSessionResetMode(editing.sessionResetMode || '')
250
+ setSessionIdleTimeoutSec(editing.sessionIdleTimeoutSec != null ? String(editing.sessionIdleTimeoutSec) : '')
251
+ setSessionMaxAgeSec(editing.sessionMaxAgeSec != null ? String(editing.sessionMaxAgeSec) : '')
252
+ setSessionDailyResetAt(editing.sessionDailyResetAt || '')
253
+ setSessionResetTimezone(editing.sessionResetTimezone || '')
254
+ setIdentityPersonaLabel(editing.identityState?.personaLabel || '')
255
+ setIdentitySelfSummary(editing.identityState?.selfSummary || '')
256
+ setIdentityRelationshipSummary(editing.identityState?.relationshipSummary || '')
257
+ setIdentityToneStyle(editing.identityState?.toneStyle || '')
258
+ setIdentityBoundariesText(formatIdentityList(editing.identityState?.boundaries))
259
+ setIdentityContinuityNotesText(formatIdentityList(editing.identityState?.continuityNotes))
207
260
  setBudgetEnabled(
208
261
  (typeof editing.hourlyBudget === 'number' && editing.hourlyBudget > 0)
209
262
  || (typeof editing.dailyBudget === 'number' && editing.dailyBudget > 0)
@@ -233,7 +286,10 @@ export function AgentSheet() {
233
286
  setModel('')
234
287
  setCredentialId(null)
235
288
  setApiEndpoint(null)
236
- setIsOrchestrator(false)
289
+ setGatewayProfileId(null)
290
+ setRoutingStrategy('single')
291
+ setRoutingTargets([])
292
+ setPlatformAssignScope('self')
237
293
  setAgentAgentIds([])
238
294
  setTools([])
239
295
  setSkills([])
@@ -247,12 +303,25 @@ export function AgentSheet() {
247
303
  setProjectId(undefined)
248
304
  setAvatarSeed('')
249
305
  setThinkingLevel('')
306
+ setMemoryScopeMode('auto')
307
+ setMemoryTierPreference('blended')
250
308
  setAutoRecovery(false)
251
309
  setVoiceId('')
252
310
  setHeartbeatEnabled(false)
253
311
  setHeartbeatIntervalSec('')
254
312
  setHeartbeatModel('')
255
313
  setHeartbeatPrompt('')
314
+ setSessionResetMode('')
315
+ setSessionIdleTimeoutSec('')
316
+ setSessionMaxAgeSec('')
317
+ setSessionDailyResetAt('')
318
+ setSessionResetTimezone('')
319
+ setIdentityPersonaLabel('')
320
+ setIdentitySelfSummary('')
321
+ setIdentityRelationshipSummary('')
322
+ setIdentityToneStyle('')
323
+ setIdentityBoundariesText('')
324
+ setIdentityContinuityNotesText('')
256
325
  setBudgetEnabled(false)
257
326
  setHourlyBudget('')
258
327
  setDailyBudget('')
@@ -319,6 +388,45 @@ export function AgentSheet() {
319
388
  setEditingId(null)
320
389
  }
321
390
 
391
+ const applyGatewayProfileSelection = (nextGatewayProfileId: string | null) => {
392
+ setGatewayProfileId(nextGatewayProfileId)
393
+ const gateway = openclawGatewayProfiles.find((item) => item.id === nextGatewayProfileId)
394
+ if (!gateway) return
395
+ setProvider('openclaw')
396
+ setOpenclawEnabled(true)
397
+ setApiEndpoint(gateway.endpoint)
398
+ if (gateway.credentialId) setCredentialId(gateway.credentialId)
399
+ if (!model) setModel('default')
400
+ }
401
+
402
+ const updateRoutingTarget = (targetId: string, patch: Partial<AgentRoutingTarget>) => {
403
+ setRoutingTargets((current) => current.map((target) => (
404
+ target.id === targetId
405
+ ? { ...target, ...patch }
406
+ : target
407
+ )))
408
+ }
409
+
410
+ const removeRoutingTarget = (targetId: string) => {
411
+ setRoutingTargets((current) => current.filter((target) => target.id !== targetId))
412
+ }
413
+
414
+ const addRoutingTargetFromCurrent = () => {
415
+ const nextTarget: AgentRoutingTarget = {
416
+ id: crypto.randomUUID(),
417
+ label: routingTargets.length === 0 ? 'Primary route' : `Route ${routingTargets.length + 1}`,
418
+ role: routingTargets.length === 0 ? 'primary' : 'backup',
419
+ provider,
420
+ model,
421
+ credentialId,
422
+ fallbackCredentialIds,
423
+ apiEndpoint,
424
+ gatewayProfileId,
425
+ priority: routingTargets.length + 1,
426
+ }
427
+ setRoutingTargets((current) => [...current, nextTarget])
428
+ }
429
+
322
430
  const handleSave = async () => {
323
431
  // For any endpoint, just ensure bare host:port gets a protocol prepended
324
432
  let normalizedEndpoint = apiEndpoint
@@ -329,6 +437,22 @@ export function AgentSheet() {
329
437
  const parsedHourlyBudget = budgetEnabled && hourlyBudget ? Number(hourlyBudget) : null
330
438
  const parsedDailyBudget = budgetEnabled && dailyBudget ? Number(dailyBudget) : null
331
439
  const parsedMonthlyBudget = budgetEnabled && monthlyBudget ? Number(monthlyBudget) : null
440
+ const parsedSessionIdleTimeoutSec = sessionIdleTimeoutSec ? Number(sessionIdleTimeoutSec) : null
441
+ const parsedSessionMaxAgeSec = sessionMaxAgeSec ? Number(sessionMaxAgeSec) : null
442
+ const identityBoundaries = parseIdentityList(identityBoundariesText)
443
+ const identityContinuityNotes = parseIdentityList(identityContinuityNotesText)
444
+ const identityState = (() => {
445
+ const value = {
446
+ personaLabel: identityPersonaLabel.trim() || undefined,
447
+ selfSummary: identitySelfSummary.trim() || undefined,
448
+ relationshipSummary: identityRelationshipSummary.trim() || undefined,
449
+ toneStyle: identityToneStyle.trim() || undefined,
450
+ boundaries: identityBoundaries.length ? identityBoundaries : undefined,
451
+ continuityNotes: identityContinuityNotes.length ? identityContinuityNotes : undefined,
452
+ }
453
+ return Object.values(value).some((entry) => Array.isArray(entry) ? entry.length > 0 : Boolean(entry)) ? value : null
454
+ })()
455
+ const canDelegateToAgents = platformAssignScope === 'all'
332
456
  const data = {
333
457
  name: name.trim() || 'Unnamed Agent',
334
458
  description,
@@ -338,20 +462,27 @@ export function AgentSheet() {
338
462
  model,
339
463
  credentialId,
340
464
  apiEndpoint: normalizedEndpoint,
341
- isOrchestrator,
342
- subAgentIds: isOrchestrator ? subAgentIds : [],
465
+ gatewayProfileId,
466
+ routingStrategy,
467
+ routingTargets: routingTargets.map((target, index) => ({
468
+ ...target,
469
+ priority: typeof target.priority === 'number' ? target.priority : index + 1,
470
+ })),
471
+ subAgentIds: canDelegateToAgents ? subAgentIds : [],
343
472
  tools,
344
473
  skills,
345
474
  skillIds,
346
475
  mcpServerIds,
347
476
  mcpDisabledTools: mcpDisabledTools.length ? mcpDisabledTools : undefined,
348
477
  fallbackCredentialIds,
349
- platformAssignScope: (isOrchestrator ? 'all' : 'self') as 'all' | 'self',
478
+ platformAssignScope,
350
479
  capabilities,
351
480
  projectId: projectId || undefined,
352
481
  avatarSeed: avatarSeed.trim() || undefined,
353
482
  avatarUrl: avatarUrl || null,
354
483
  thinkingLevel: thinkingLevel || undefined,
484
+ memoryScopeMode,
485
+ memoryTierPreference,
355
486
  autoRecovery,
356
487
  elevenLabsVoiceId: voiceId.trim() || null,
357
488
  heartbeatEnabled,
@@ -359,6 +490,12 @@ export function AgentSheet() {
359
490
  heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
360
491
  heartbeatModel: heartbeatModel.trim() || null,
361
492
  heartbeatPrompt: heartbeatPrompt.trim() || null,
493
+ identityState,
494
+ sessionResetMode: sessionResetMode || null,
495
+ sessionIdleTimeoutSec: Number.isFinite(parsedSessionIdleTimeoutSec) && parsedSessionIdleTimeoutSec! >= 0 ? parsedSessionIdleTimeoutSec : null,
496
+ sessionMaxAgeSec: Number.isFinite(parsedSessionMaxAgeSec) && parsedSessionMaxAgeSec! >= 0 ? parsedSessionMaxAgeSec : null,
497
+ sessionDailyResetAt: sessionDailyResetAt.trim() || null,
498
+ sessionResetTimezone: sessionResetTimezone.trim() || null,
362
499
  hourlyBudget: parsedHourlyBudget && parsedHourlyBudget > 0 ? parsedHourlyBudget : null,
363
500
  dailyBudget: parsedDailyBudget && parsedDailyBudget > 0 ? parsedDailyBudget : null,
364
501
  monthlyBudget: parsedMonthlyBudget && parsedMonthlyBudget > 0 ? parsedMonthlyBudget : null,
@@ -389,15 +526,40 @@ export function AgentSheet() {
389
526
 
390
527
  const handleExport = () => {
391
528
  if (!editing) return
392
- const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...exportData } = editing
393
- const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' })
529
+ const pack: AgentPackManifest = {
530
+ schemaVersion: 1,
531
+ kind: 'swarmclaw-agent-pack',
532
+ name: `${editing.name} Pack`,
533
+ description: editing.description || undefined,
534
+ exportedAt: Date.now(),
535
+ recommendedProviders: [editing.provider],
536
+ agents: [{
537
+ id: editing.name.replace(/\s+/g, '-').toLowerCase(),
538
+ name: editing.name,
539
+ description: editing.description || undefined,
540
+ provider: editing.provider,
541
+ model: editing.model,
542
+ credentialId: editing.credentialId || null,
543
+ fallbackCredentialIds: editing.fallbackCredentialIds || [],
544
+ apiEndpoint: editing.apiEndpoint || null,
545
+ gatewayProfileId: editing.gatewayProfileId || null,
546
+ routingStrategy: editing.routingStrategy || null,
547
+ routingTargets: editing.routingTargets || [],
548
+ tools: editing.tools,
549
+ plugins: editing.plugins,
550
+ capabilities: editing.capabilities,
551
+ soul: editing.soul,
552
+ systemPrompt: editing.systemPrompt,
553
+ }],
554
+ }
555
+ const blob = new Blob([JSON.stringify(pack, null, 2)], { type: 'application/json' })
394
556
  const url = URL.createObjectURL(blob)
395
557
  const a = document.createElement('a')
396
558
  a.href = url
397
- a.download = `${editing.name.replace(/[^a-zA-Z0-9_-]/g, '_')}.agent.json`
559
+ a.download = `${editing.name.replace(/[^a-zA-Z0-9_-]/g, '_')}.agent-pack.json`
398
560
  a.click()
399
561
  URL.revokeObjectURL(url)
400
- toast.success('Agent exported')
562
+ toast.success('Agent pack exported')
401
563
  }
402
564
 
403
565
  const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -407,11 +569,15 @@ export function AgentSheet() {
407
569
  reader.onload = async (ev) => {
408
570
  try {
409
571
  const data = JSON.parse(ev.target?.result as string)
572
+ const importedAgent = data?.kind === 'swarmclaw-agent-pack'
573
+ ? data?.agents?.[0]
574
+ : data
575
+ if (!importedAgent || typeof importedAgent !== 'object') throw new Error('Invalid agent pack')
410
576
  // Strip IDs and timestamps
411
- const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...agentData } = data
577
+ const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...agentData } = importedAgent
412
578
  await createAgent({ ...agentData, name: agentData.name || 'Imported Agent' })
413
579
  await loadAgents()
414
- toast.success('Agent imported')
580
+ toast.success(data?.kind === 'swarmclaw-agent-pack' ? 'Agent pack imported' : 'Agent imported')
415
581
  onClose()
416
582
  } catch (err) {
417
583
  toast.error('Invalid agent JSON file')
@@ -473,7 +639,8 @@ export function AgentSheet() {
473
639
  setSaving(false)
474
640
  }
475
641
 
476
- const agentOptions = Object.values(agents).filter((p) => !p.isOrchestrator && p.id !== editingId)
642
+ const canDelegateToAgents = platformAssignScope === 'all'
643
+ const agentOptions = Object.values(agents).filter((p) => p.id !== editingId)
477
644
 
478
645
  const toggleAgent = (id: string) => {
479
646
  setAgentAgentIds((prev) =>
@@ -491,7 +658,7 @@ export function AgentSheet() {
491
658
  <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
492
659
  {editing ? 'Edit Agent' : 'New Agent'}
493
660
  </h2>
494
- <p className="text-[14px] text-text-3">Define an AI agent or orchestrator</p>
661
+ <p className="text-[14px] text-text-3">Define an AI agent and optional multi-agent delegation behavior</p>
495
662
  </div>
496
663
  <div className="flex items-center gap-3 mt-1.5">
497
664
  <label className="text-[11px] font-600 text-text-3 uppercase tracking-[0.08em]">OpenClaw</label>
@@ -510,6 +677,7 @@ export function AgentSheet() {
510
677
  setModel('')
511
678
  setApiEndpoint(null)
512
679
  setCredentialId(null)
680
+ setGatewayProfileId(null)
513
681
  setTestStatus('idle')
514
682
  setTestMessage('')
515
683
  setTestErrorCode(null)
@@ -699,6 +867,29 @@ export function AgentSheet() {
699
867
  <p className="text-[11px] text-text-3/70 mt-1.5">Controls reasoning depth. Anthropic models use extended thinking; OpenAI o-series uses reasoning_effort. Others get system prompt guidance.</p>
700
868
  </div>
701
869
 
870
+ <div className="mb-8">
871
+ <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
872
+ Memory Defaults <HintTip text="Controls where this agent should look first and which memory tier it should favor when writing or recalling context." />
873
+ </label>
874
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
875
+ <select value={memoryScopeMode} onChange={(e) => setMemoryScopeMode(e.target.value as typeof memoryScopeMode)} className={inputClass}>
876
+ <option value="auto">Scope: Auto</option>
877
+ <option value="all">Scope: All memories</option>
878
+ <option value="global">Scope: Global only</option>
879
+ <option value="agent">Scope: Agent memories</option>
880
+ <option value="session">Scope: Session memories</option>
881
+ <option value="project">Scope: Project memories</option>
882
+ </select>
883
+ <select value={memoryTierPreference} onChange={(e) => setMemoryTierPreference(e.target.value as typeof memoryTierPreference)} className={inputClass}>
884
+ <option value="blended">Tier: Blended</option>
885
+ <option value="working">Tier: Working</option>
886
+ <option value="durable">Tier: Durable</option>
887
+ <option value="archive">Tier: Archive</option>
888
+ </select>
889
+ </div>
890
+ <p className="text-[11px] text-text-3/70 mt-1.5">Use working for fast recent context, durable for facts/preferences, and archive for long-lived history.</p>
891
+ </div>
892
+
702
893
  {/* Auto-Recovery */}
703
894
  <div className="mb-8">
704
895
  <div className="flex items-center justify-between mb-1.5">
@@ -981,9 +1172,147 @@ export function AgentSheet() {
981
1172
  </div>
982
1173
  )}
983
1174
 
1175
+ <div className="mb-8">
1176
+ <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
1177
+ Identity Continuity <HintTip text="Seeds the agent's continuity state so session memory can preserve a stable persona and relationship context." />
1178
+ </label>
1179
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
1180
+ <input
1181
+ type="text"
1182
+ value={identityPersonaLabel}
1183
+ onChange={(e) => setIdentityPersonaLabel(e.target.value)}
1184
+ placeholder="Persona label"
1185
+ className={inputClass}
1186
+ style={{ fontFamily: 'inherit' }}
1187
+ />
1188
+ <input
1189
+ type="text"
1190
+ value={identityToneStyle}
1191
+ onChange={(e) => setIdentityToneStyle(e.target.value)}
1192
+ placeholder="Tone style"
1193
+ className={inputClass}
1194
+ style={{ fontFamily: 'inherit' }}
1195
+ />
1196
+ </div>
1197
+ <div className="grid grid-cols-1 gap-3">
1198
+ <textarea
1199
+ value={identitySelfSummary}
1200
+ onChange={(e) => setIdentitySelfSummary(e.target.value)}
1201
+ placeholder="How this agent should summarize itself across sessions."
1202
+ rows={3}
1203
+ className={`${inputClass} resize-y min-h-[84px]`}
1204
+ style={{ fontFamily: 'inherit' }}
1205
+ />
1206
+ <textarea
1207
+ value={identityRelationshipSummary}
1208
+ onChange={(e) => setIdentityRelationshipSummary(e.target.value)}
1209
+ placeholder="Relationship framing or standing context the agent should keep in mind."
1210
+ rows={3}
1211
+ className={`${inputClass} resize-y min-h-[84px]`}
1212
+ style={{ fontFamily: 'inherit' }}
1213
+ />
1214
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
1215
+ <textarea
1216
+ value={identityBoundariesText}
1217
+ onChange={(e) => setIdentityBoundariesText(e.target.value)}
1218
+ placeholder="Boundaries, one per line."
1219
+ rows={4}
1220
+ className={`${inputClass} resize-y min-h-[108px]`}
1221
+ style={{ fontFamily: 'inherit' }}
1222
+ />
1223
+ <textarea
1224
+ value={identityContinuityNotesText}
1225
+ onChange={(e) => setIdentityContinuityNotesText(e.target.value)}
1226
+ placeholder="Continuity notes, one per line."
1227
+ rows={4}
1228
+ className={`${inputClass} resize-y min-h-[108px]`}
1229
+ style={{ fontFamily: 'inherit' }}
1230
+ />
1231
+ </div>
1232
+ </div>
1233
+ <p className="text-[12px] text-text-3/60 mt-2 leading-[1.5]">
1234
+ Use one line per item. Boundaries are stable guardrails; continuity notes are recurring relationship or project context worth carrying across sessions.
1235
+ </p>
1236
+ </div>
1237
+
1238
+ <div className="mb-8">
1239
+ <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
1240
+ Session Reset Policy <HintTip text="Controls when this agent's sessions are considered stale and should be refreshed." />
1241
+ </label>
1242
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
1243
+ <div>
1244
+ <select
1245
+ value={sessionResetMode}
1246
+ onChange={(e) => setSessionResetMode(e.target.value as typeof sessionResetMode)}
1247
+ className={inputClass}
1248
+ style={{ fontFamily: 'inherit' }}
1249
+ >
1250
+ <option value="">Inherit global default</option>
1251
+ <option value="idle">Idle</option>
1252
+ <option value="daily">Daily</option>
1253
+ </select>
1254
+ </div>
1255
+ <div>
1256
+ <input
1257
+ type="number"
1258
+ min={0}
1259
+ value={sessionIdleTimeoutSec}
1260
+ onChange={(e) => setSessionIdleTimeoutSec(e.target.value)}
1261
+ placeholder="Idle timeout in seconds"
1262
+ className={inputClass}
1263
+ style={{ fontFamily: 'inherit' }}
1264
+ />
1265
+ </div>
1266
+ </div>
1267
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
1268
+ <input
1269
+ type="number"
1270
+ min={0}
1271
+ value={sessionMaxAgeSec}
1272
+ onChange={(e) => setSessionMaxAgeSec(e.target.value)}
1273
+ placeholder="Max age in seconds"
1274
+ className={inputClass}
1275
+ style={{ fontFamily: 'inherit' }}
1276
+ />
1277
+ <input
1278
+ type="text"
1279
+ value={sessionDailyResetAt}
1280
+ onChange={(e) => setSessionDailyResetAt(e.target.value)}
1281
+ placeholder="Daily reset time (HH:MM)"
1282
+ className={inputClass}
1283
+ style={{ fontFamily: 'inherit' }}
1284
+ />
1285
+ <input
1286
+ type="text"
1287
+ value={sessionResetTimezone}
1288
+ onChange={(e) => setSessionResetTimezone(e.target.value)}
1289
+ placeholder="Timezone (optional)"
1290
+ className={inputClass}
1291
+ style={{ fontFamily: 'inherit' }}
1292
+ />
1293
+ </div>
1294
+ </div>
1295
+
984
1296
  {/* OpenClaw Gateway Fields */}
985
1297
  {openclawEnabled && (
986
1298
  <div className="mb-8 space-y-5">
1299
+ {openclawGatewayProfiles.length > 0 && (
1300
+ <div>
1301
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">Gateway Profile</label>
1302
+ <select
1303
+ value={gatewayProfileId || ''}
1304
+ onChange={(e) => applyGatewayProfileSelection(e.target.value || null)}
1305
+ className={inputClass}
1306
+ >
1307
+ <option value="">Custom endpoint</option>
1308
+ {openclawGatewayProfiles.map((gateway) => (
1309
+ <option key={gateway.id} value={gateway.id}>
1310
+ {gateway.name}{gateway.isDefault ? ' (default)' : ''}
1311
+ </option>
1312
+ ))}
1313
+ </select>
1314
+ </div>
1315
+ )}
987
1316
  {/* Connection fields */}
988
1317
  <div className="space-y-4">
989
1318
  <div>
@@ -1178,13 +1507,14 @@ export function AgentSheet() {
1178
1507
  {!openclawEnabled && <div className="mb-8">
1179
1508
  <SectionLabel>Provider</SectionLabel>
1180
1509
  <div className="grid grid-cols-3 gap-3">
1181
- {providers.filter((p) => !isOrchestrator || p.id !== 'claude-cli').map((p) => {
1510
+ {providers.map((p) => {
1182
1511
  const isConnected = !p.requiresApiKey || Object.values(credentials).some((c) => c.provider === p.id)
1183
1512
  return (
1184
1513
  <button
1185
1514
  key={p.id}
1186
1515
  onClick={() => {
1187
1516
  setProvider(p.id)
1517
+ setGatewayProfileId(null)
1188
1518
  }}
1189
1519
  className={`relative py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
1190
1520
  active:scale-[0.97] text-[14px] font-600 border
@@ -1212,6 +1542,9 @@ export function AgentSheet() {
1212
1542
  onChange={setModel}
1213
1543
  models={currentProvider.models}
1214
1544
  defaultModels={currentProvider.defaultModels}
1545
+ credentialId={credentialId}
1546
+ apiEndpoint={apiEndpoint}
1547
+ supportsDiscovery={currentProvider.supportsModelDiscovery}
1215
1548
  className={`${inputClass} cursor-pointer`}
1216
1549
  />
1217
1550
  </div>
@@ -1365,6 +1698,108 @@ export function AgentSheet() {
1365
1698
  </div>
1366
1699
  )}
1367
1700
 
1701
+ <div className="mb-8">
1702
+ <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
1703
+ Model Routing <HintTip text="Route this agent through a provider/model pool instead of a single fixed model. The base provider remains the default when no route matches." />
1704
+ </label>
1705
+ <div className="flex items-center gap-3 mb-3">
1706
+ <select value={routingStrategy} onChange={(e) => setRoutingStrategy(e.target.value as AgentRoutingStrategy)} className={inputClass}>
1707
+ <option value="single">Single route</option>
1708
+ <option value="balanced">Balanced</option>
1709
+ <option value="economy">Economy</option>
1710
+ <option value="premium">Premium</option>
1711
+ <option value="reasoning">Reasoning</option>
1712
+ </select>
1713
+ <button
1714
+ type="button"
1715
+ onClick={addRoutingTargetFromCurrent}
1716
+ className="shrink-0 px-3 py-2.5 rounded-[10px] bg-accent-soft/50 text-accent-bright text-[12px] font-700 hover:bg-accent-soft transition-colors cursor-pointer border border-accent-bright/20"
1717
+ >
1718
+ + Add Current Route
1719
+ </button>
1720
+ </div>
1721
+ <div className="space-y-3">
1722
+ {routingTargets.map((target, index) => {
1723
+ const targetCredentials = Object.values(credentials).filter((item) => item.provider === target.provider)
1724
+ return (
1725
+ <div key={target.id} className="p-4 rounded-[12px] border border-white/[0.08] bg-white/[0.02] space-y-3">
1726
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
1727
+ <input
1728
+ value={target.label || ''}
1729
+ onChange={(e) => updateRoutingTarget(target.id, { label: e.target.value })}
1730
+ placeholder={`Route ${index + 1} label`}
1731
+ className={inputClass}
1732
+ />
1733
+ <select value={target.role || 'backup'} onChange={(e) => updateRoutingTarget(target.id, { role: e.target.value as AgentRoutingTarget['role'] })} className={inputClass}>
1734
+ <option value="primary">Primary</option>
1735
+ <option value="economy">Economy</option>
1736
+ <option value="premium">Premium</option>
1737
+ <option value="reasoning">Reasoning</option>
1738
+ <option value="backup">Backup</option>
1739
+ </select>
1740
+ </div>
1741
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
1742
+ <select value={target.provider} onChange={(e) => updateRoutingTarget(target.id, { provider: e.target.value as ProviderType, gatewayProfileId: e.target.value === 'openclaw' ? target.gatewayProfileId : null })} className={inputClass}>
1743
+ {providers.map((item) => (
1744
+ <option key={item.id} value={item.id}>{item.name}</option>
1745
+ ))}
1746
+ </select>
1747
+ <input
1748
+ value={target.model}
1749
+ onChange={(e) => updateRoutingTarget(target.id, { model: e.target.value })}
1750
+ placeholder="Model"
1751
+ className={inputClass}
1752
+ />
1753
+ </div>
1754
+ {target.provider === 'openclaw' && openclawGatewayProfiles.length > 0 && (
1755
+ <select
1756
+ value={target.gatewayProfileId || ''}
1757
+ onChange={(e) => {
1758
+ const nextId = e.target.value || null
1759
+ const gateway = openclawGatewayProfiles.find((item) => item.id === nextId)
1760
+ updateRoutingTarget(target.id, {
1761
+ gatewayProfileId: nextId,
1762
+ apiEndpoint: gateway?.endpoint || target.apiEndpoint || null,
1763
+ credentialId: gateway?.credentialId || target.credentialId || null,
1764
+ model: target.model || 'default',
1765
+ })
1766
+ }}
1767
+ className={inputClass}
1768
+ >
1769
+ <option value="">Custom OpenClaw endpoint</option>
1770
+ {openclawGatewayProfiles.map((gateway) => (
1771
+ <option key={gateway.id} value={gateway.id}>{gateway.name}</option>
1772
+ ))}
1773
+ </select>
1774
+ )}
1775
+ <div className="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3">
1776
+ <input
1777
+ value={target.apiEndpoint || ''}
1778
+ onChange={(e) => updateRoutingTarget(target.id, { apiEndpoint: e.target.value || null })}
1779
+ placeholder="Endpoint (optional)"
1780
+ className={`${inputClass} font-mono text-[14px]`}
1781
+ />
1782
+ <select value={target.credentialId || ''} onChange={(e) => updateRoutingTarget(target.id, { credentialId: e.target.value || null })} className={inputClass}>
1783
+ <option value="">No key</option>
1784
+ {targetCredentials.map((item) => (
1785
+ <option key={item.id} value={item.id}>{item.name}</option>
1786
+ ))}
1787
+ </select>
1788
+ </div>
1789
+ <div className="flex justify-end">
1790
+ <button type="button" onClick={() => removeRoutingTarget(target.id)} className="px-3 py-1.5 rounded-[8px] border border-red-400/20 bg-red-400/[0.06] text-[12px] font-700 text-red-300 hover:bg-red-400/[0.1] transition-all cursor-pointer">
1791
+ Remove Route
1792
+ </button>
1793
+ </div>
1794
+ </div>
1795
+ )
1796
+ })}
1797
+ </div>
1798
+ {routingTargets.length === 0 && (
1799
+ <p className="text-[11px] text-text-3/70 mt-2">No route pool yet. Add one if this agent should switch between cheaper, stronger, or gateway-specific models.</p>
1800
+ )}
1801
+ </div>
1802
+
1368
1803
  {/* Plugins — hidden for providers that manage capabilities outside LangGraph */}
1369
1804
  {!hasNativeCapabilities && (
1370
1805
  <div className="mb-8">
@@ -1584,15 +2019,13 @@ export function AgentSheet() {
1584
2019
  <label className="flex items-center gap-3 cursor-pointer">
1585
2020
  <div
1586
2021
  onClick={() => {
1587
- const next = !isOrchestrator
1588
- setIsOrchestrator(next)
1589
- if (next && provider === 'claude-cli') setProvider('anthropic')
2022
+ setPlatformAssignScope((current) => current === 'all' ? 'self' : 'all')
1590
2023
  }}
1591
2024
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer
1592
- ${isOrchestrator ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
2025
+ ${canDelegateToAgents ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
1593
2026
  >
1594
2027
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
1595
- ${isOrchestrator ? 'left-[22px]' : 'left-0.5'}`} />
2028
+ ${canDelegateToAgents ? 'left-[22px]' : 'left-0.5'}`} />
1596
2029
  </div>
1597
2030
  <span className="font-display text-[14px] font-600 text-text-2">Can Delegate to Other Agents</span>
1598
2031
  <span className="text-[12px] text-text-3">Route work to specialized agents and coordinate multi-agent tasks</span>
@@ -1600,7 +2033,7 @@ export function AgentSheet() {
1600
2033
  </div>
1601
2034
  )}
1602
2035
 
1603
- {provider !== 'openclaw' && isOrchestrator && agentOptions.length > 0 && (
2036
+ {provider !== 'openclaw' && canDelegateToAgents && agentOptions.length > 0 && (
1604
2037
  <div className="mb-8">
1605
2038
  <SectionLabel>Available Agents</SectionLabel>
1606
2039
  <AgentPickerList