@swarmclawai/swarmclaw 1.2.6 → 1.2.9

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 (269) hide show
  1. package/README.md +54 -23
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/scripts/easy-setup.mjs +1 -1
  5. package/scripts/postinstall.mjs +1 -1
  6. package/skills/swarmclaw.md +115 -0
  7. package/skills/tools/browser.md +131 -0
  8. package/skills/tools/execute.md +98 -0
  9. package/skills/tools/files.md +98 -0
  10. package/skills/tools/memory.md +104 -0
  11. package/skills/tools/platform.md +144 -0
  12. package/skills/tools/skills.md +83 -0
  13. package/src/app/agents/[id]/page.tsx +1 -18
  14. package/src/app/api/agents/thread-route.test.ts +0 -1
  15. package/src/app/api/approvals/route.test.ts +6 -22
  16. package/src/app/api/chats/[id]/messages/route.ts +23 -19
  17. package/src/app/api/chats/messages-route.test.ts +105 -51
  18. package/src/app/api/connectors/route.ts +2 -2
  19. package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
  20. package/src/app/api/openclaw/deploy/route.ts +2 -0
  21. package/src/app/api/portability/export/route.ts +8 -0
  22. package/src/app/api/portability/import/route.test.ts +80 -0
  23. package/src/app/api/portability/import/route.ts +28 -0
  24. package/src/app/api/settings/route.ts +0 -2
  25. package/src/app/api/setup/doctor/route.ts +4 -4
  26. package/src/app/api/wallets/[id]/route.ts +15 -157
  27. package/src/app/api/wallets/generate/route.ts +22 -0
  28. package/src/app/api/wallets/route.test.ts +147 -0
  29. package/src/app/api/wallets/route.ts +13 -95
  30. package/src/app/autonomy/page.tsx +2 -57
  31. package/src/app/protocols/page.tsx +2 -21
  32. package/src/app/settings/page.tsx +0 -9
  33. package/src/app/wallets/page.tsx +105 -5
  34. package/src/cli/index.js +21 -33
  35. package/src/cli/spec.js +19 -30
  36. package/src/components/agents/agent-chat-list.tsx +23 -1
  37. package/src/components/agents/agent-sheet.tsx +2 -40
  38. package/src/components/agents/inspector-panel.tsx +165 -131
  39. package/src/components/chat/chat-area.tsx +38 -9
  40. package/src/components/chat/chat-card.tsx +0 -31
  41. package/src/components/chat/message-bubble.tsx +1 -108
  42. package/src/components/chat/message-list.tsx +33 -19
  43. package/src/components/connectors/connector-sheet.tsx +25 -1
  44. package/src/components/gateways/gateway-sheet.tsx +5 -2
  45. package/src/components/layout/sidebar-rail.tsx +6 -10
  46. package/src/components/projects/project-detail.tsx +3 -35
  47. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  48. package/src/components/projects/tabs/work-tab.tsx +7 -77
  49. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  50. package/src/components/shared/connector-platform-icon.tsx +1 -0
  51. package/src/components/tasks/task-card.tsx +4 -34
  52. package/src/components/tasks/task-sheet.tsx +6 -36
  53. package/src/components/wallets/wallet-list.tsx +150 -0
  54. package/src/lib/agent-execute-defaults.test.ts +24 -0
  55. package/src/lib/agent-execute-defaults.ts +62 -0
  56. package/src/lib/app/navigation.test.ts +0 -13
  57. package/src/lib/app/navigation.ts +2 -7
  58. package/src/lib/app/view-constants.ts +14 -19
  59. package/src/lib/chat/queued-message-queue.test.ts +134 -1
  60. package/src/lib/chat/queued-message-queue.ts +77 -2
  61. package/src/lib/server/agents/agent-service.ts +5 -0
  62. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  63. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  64. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  65. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  66. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  67. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  68. package/src/lib/server/approval-match.ts +0 -85
  69. package/src/lib/server/approvals.test.ts +6 -6
  70. package/src/lib/server/approvals.ts +0 -6
  71. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  72. package/src/lib/server/builtin-extensions.ts +1 -2
  73. package/src/lib/server/capability-router.test.ts +0 -2
  74. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
  75. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
  76. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  77. package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
  78. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  79. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  80. package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
  81. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
  82. package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
  83. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  84. package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
  85. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  86. package/src/lib/server/chat-execution/message-classifier.ts +11 -16
  87. package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
  88. package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
  89. package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
  90. package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
  91. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  92. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  93. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  94. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
  95. package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
  96. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  97. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  98. package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
  99. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  100. package/src/lib/server/chats/chat-session-service.ts +3 -5
  101. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  102. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  103. package/src/lib/server/connectors/connector-service.ts +39 -9
  104. package/src/lib/server/connectors/discord.ts +2 -2
  105. package/src/lib/server/connectors/matrix.ts +3 -2
  106. package/src/lib/server/connectors/signal.ts +5 -4
  107. package/src/lib/server/connectors/slack.ts +10 -9
  108. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  109. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  110. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  111. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  112. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  113. package/src/lib/server/connectors/swarmdock.ts +255 -0
  114. package/src/lib/server/connectors/teams.ts +3 -2
  115. package/src/lib/server/connectors/telegram.ts +4 -4
  116. package/src/lib/server/connectors/whatsapp.ts +2 -2
  117. package/src/lib/server/daemon/controller.ts +7 -0
  118. package/src/lib/server/execution-brief.test.ts +2 -25
  119. package/src/lib/server/execution-brief.ts +12 -35
  120. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  121. package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
  122. package/src/lib/server/messages/message-repository.test.ts +70 -0
  123. package/src/lib/server/messages/message-repository.ts +11 -6
  124. package/src/lib/server/openclaw/deploy.ts +32 -2
  125. package/src/lib/server/persistence/storage-context.ts +0 -5
  126. package/src/lib/server/plugins-advanced.test.ts +1 -2
  127. package/src/lib/server/portability/export.ts +109 -0
  128. package/src/lib/server/portability/import.ts +159 -0
  129. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  130. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  131. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  132. package/src/lib/server/protocols/protocol-service.ts +0 -1
  133. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  134. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  135. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  136. package/src/lib/server/protocols/protocol-types.ts +0 -2
  137. package/src/lib/server/provider-health.ts +1 -10
  138. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  139. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  140. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  141. package/src/lib/server/runtime/process-manager.ts +13 -9
  142. package/src/lib/server/runtime/queue/core.ts +11 -33
  143. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  144. package/src/lib/server/runtime/scheduler.ts +0 -13
  145. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  146. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  147. package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
  148. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  149. package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
  150. package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
  151. package/src/lib/server/sandbox/session-runtime.ts +40 -28
  152. package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
  153. package/src/lib/server/session-tools/context.ts +1 -1
  154. package/src/lib/server/session-tools/credential-env.ts +109 -0
  155. package/src/lib/server/session-tools/crud.ts +3 -17
  156. package/src/lib/server/session-tools/delegate.ts +0 -4
  157. package/src/lib/server/session-tools/edit_file.ts +3 -2
  158. package/src/lib/server/session-tools/execute.test.ts +58 -0
  159. package/src/lib/server/session-tools/execute.ts +334 -0
  160. package/src/lib/server/session-tools/files-tool.ts +635 -0
  161. package/src/lib/server/session-tools/index.ts +14 -8
  162. package/src/lib/server/session-tools/memory-tool.ts +242 -0
  163. package/src/lib/server/session-tools/memory.ts +1 -1
  164. package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
  165. package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
  166. package/src/lib/server/session-tools/platform-tool.ts +617 -0
  167. package/src/lib/server/session-tools/session-info.ts +3 -2
  168. package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
  169. package/src/lib/server/session-tools/shell.ts +7 -122
  170. package/src/lib/server/session-tools/skills-tool.ts +396 -0
  171. package/src/lib/server/session-tools/team-context.ts +0 -3
  172. package/src/lib/server/session-tools/web.ts +2 -2
  173. package/src/lib/server/storage-normalization.ts +10 -0
  174. package/src/lib/server/storage.ts +18 -45
  175. package/src/lib/server/tasks/task-checkout.ts +59 -0
  176. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  177. package/src/lib/server/tasks/task-route-service.ts +4 -26
  178. package/src/lib/server/tasks/task-service.ts +0 -7
  179. package/src/lib/server/tool-aliases.ts +2 -2
  180. package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
  181. package/src/lib/server/tool-capability-policy.test.ts +2 -1
  182. package/src/lib/server/tool-capability-policy.ts +60 -35
  183. package/src/lib/server/tool-planning.ts +11 -12
  184. package/src/lib/server/universal-tool-access.ts +0 -1
  185. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  186. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  187. package/src/lib/server/wallets/wallet-service.ts +119 -0
  188. package/src/lib/server/working-state/extraction.ts +8 -42
  189. package/src/lib/server/working-state/normalization.ts +10 -103
  190. package/src/lib/server/working-state/service.ts +12 -21
  191. package/src/lib/setup-defaults.ts +5 -0
  192. package/src/lib/strip-internal-metadata.test.ts +1 -1
  193. package/src/lib/strip-internal-metadata.ts +1 -1
  194. package/src/lib/tool-definitions.ts +1 -1
  195. package/src/lib/validation/schemas.test.ts +16 -0
  196. package/src/lib/validation/schemas.ts +49 -2
  197. package/src/stores/slices/data-slice.ts +5 -1
  198. package/src/stores/slices/ui-slice.ts +0 -4
  199. package/src/stores/use-chat-store.test.ts +231 -0
  200. package/src/stores/use-chat-store.ts +62 -13
  201. package/src/types/agent.ts +264 -0
  202. package/src/types/app-settings.ts +173 -0
  203. package/src/types/approval.ts +25 -0
  204. package/src/types/connector.ts +188 -0
  205. package/src/types/extension.ts +386 -0
  206. package/src/types/index.ts +16 -3555
  207. package/src/types/message.ts +56 -0
  208. package/src/types/misc.ts +737 -0
  209. package/src/types/protocol.ts +420 -0
  210. package/src/types/provider.ts +52 -0
  211. package/src/types/run.ts +180 -0
  212. package/src/types/schedule.ts +59 -0
  213. package/src/types/session.ts +215 -0
  214. package/src/types/skill.ts +157 -0
  215. package/src/types/swarmdock.ts +29 -0
  216. package/src/types/task.ts +144 -0
  217. package/src/types/working-state.ts +204 -0
  218. package/src/views/settings/section-heartbeat.tsx +2 -2
  219. package/src/views/settings/section-runtime-loop.tsx +0 -14
  220. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  221. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  222. package/src/app/api/missions/[id]/events/route.ts +0 -14
  223. package/src/app/api/missions/[id]/route.ts +0 -10
  224. package/src/app/api/missions/route.test.ts +0 -244
  225. package/src/app/api/missions/route.ts +0 -57
  226. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  227. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  228. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  229. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  230. package/src/app/missions/[id]/page.tsx +0 -3
  231. package/src/app/missions/page.tsx +0 -685
  232. package/src/components/canvas/canvas-panel.tsx +0 -267
  233. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  234. package/src/components/wallets/wallet-panel.tsx +0 -1010
  235. package/src/components/wallets/wallet-section.tsx +0 -260
  236. package/src/features/missions/queries.ts +0 -23
  237. package/src/lib/canvas-content.test.ts +0 -360
  238. package/src/lib/canvas-content.ts +0 -198
  239. package/src/lib/server/canvas-content.test.ts +0 -32
  240. package/src/lib/server/canvas-content.ts +0 -6
  241. package/src/lib/server/ethereum.ts +0 -591
  242. package/src/lib/server/evm-swap.ts +0 -476
  243. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  244. package/src/lib/server/missions/mission-intent.ts +0 -569
  245. package/src/lib/server/missions/mission-repository.ts +0 -74
  246. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  247. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  248. package/src/lib/server/missions/mission-service/context.ts +0 -4
  249. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  250. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  251. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  252. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  253. package/src/lib/server/missions/mission-service.test.ts +0 -888
  254. package/src/lib/server/missions/mission-service.ts +0 -6
  255. package/src/lib/server/session-tools/canvas.ts +0 -105
  256. package/src/lib/server/session-tools/sandbox.ts +0 -281
  257. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  258. package/src/lib/server/session-tools/wallet.ts +0 -1287
  259. package/src/lib/server/solana.ts +0 -327
  260. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  261. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  262. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  263. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  264. package/src/lib/server/wallet/wallet-service.ts +0 -225
  265. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  266. package/src/lib/wallet/wallet-transactions.ts +0 -43
  267. package/src/lib/wallet/wallet.test.ts +0 -333
  268. package/src/lib/wallet/wallet.ts +0 -183
  269. package/src/views/settings/section-wallets.tsx +0 -35
@@ -6,7 +6,6 @@ import type { Agent, MemoryEntry, Session } from '@/types'
6
6
  import { useAppStore } from '@/stores/use-app-store'
7
7
  import { useChatStore } from '@/stores/use-chat-store'
8
8
  import { api } from '@/lib/app/api-client'
9
- import { dedup } from '@/lib/shared-utils'
10
9
  import { AgentAvatar } from './agent-avatar'
11
10
  import { AgentFilesEditor } from './agent-files-editor'
12
11
  import { OpenClawSkillsPanel } from './openclaw-skills-panel'
@@ -16,6 +15,7 @@ import { SandboxEnvPanel } from './sandbox-env-panel'
16
15
  import { CronJobForm } from './cron-job-form'
17
16
  import { toast } from 'sonner'
18
17
  import { StatusDot } from '@/components/ui/status-dot'
18
+ import { normalizeAgentExecuteConfig } from '@/lib/agent-execute-defaults'
19
19
  import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
20
20
  import { getEnabledToolIds, getEnabledExtensionIds, getEnabledCapabilityIds } from '@/lib/capability-selection'
21
21
  import { searchMemory } from '@/lib/memory'
@@ -204,25 +204,6 @@ function ToggleSwitch({ on, onChange, disabled }: { on: boolean; onChange: () =>
204
204
  )
205
205
  }
206
206
 
207
- // --- Wallet helpers (extracted from chat-header) ---
208
-
209
- function getAgentWalletIds(agent: { walletIds?: string[]; walletId?: string | null } | null | undefined): string[] {
210
- const ids = Array.isArray(agent?.walletIds)
211
- ? agent.walletIds.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
212
- : []
213
- const legacy = typeof agent?.walletId === 'string' && agent.walletId.trim()
214
- ? [agent.walletId.trim()]
215
- : []
216
- return dedup([...ids, ...legacy])
217
- }
218
-
219
- function getAgentActiveWalletId(agent: { activeWalletId?: string | null; walletIds?: string[]; walletId?: string | null } | null | undefined): string | null {
220
- const walletIds = getAgentWalletIds(agent)
221
- if (typeof agent?.activeWalletId === 'string' && walletIds.includes(agent.activeWalletId)) return agent.activeWalletId
222
- if (typeof agent?.walletId === 'string' && walletIds.includes(agent.walletId)) return agent.walletId
223
- return walletIds[0] || null
224
- }
225
-
226
207
  // --- Main component ---
227
208
 
228
209
  export function InspectorPanel({ agent, session, onEditAgent, onDuplicateAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: Props) {
@@ -344,7 +325,6 @@ function DashboardTab({ agent, session }: { agent: Agent; session: Session }) {
344
325
  <IdentityCard agent={agent} />
345
326
  {agent.provider === 'openclaw' && <OpenClawActionsSection agent={agent} />}
346
327
  <HeartbeatSection agent={agent} session={session} />
347
- <WalletSection agent={agent} />
348
328
  <ToolsSection agent={agent} session={session} />
349
329
  <AudioSection />
350
330
  <MemorySection agentId={agent.id} />
@@ -612,68 +592,6 @@ function HeartbeatSection({ agent, session }: { agent: Agent; session: Session }
612
592
  )
613
593
  }
614
594
 
615
- // ─── Wallet Section ──────────────────────────────────────────────
616
-
617
- function WalletSection({ agent }: { agent: Agent }) {
618
- const setWalletPanelAgentId = useAppStore((s) => s.setWalletPanelAgentId)
619
- const navigateTo = useNavigate()
620
- const agentWalletIds = useMemo(() => getAgentWalletIds(agent), [agent])
621
- const activeWalletId = useMemo(() => getAgentActiveWalletId(agent), [agent])
622
- const [walletBalance, setWalletBalance] = useState<{ formatted: string; symbol: string; assets?: number } | null>(null)
623
-
624
- useEffect(() => {
625
- if (!activeWalletId) return
626
- let cancelled = false
627
- api<{ balanceFormatted?: string; balanceSymbol?: string; portfolioSummary?: { nonZeroAssets?: number } }>('GET', `/wallets/${activeWalletId}?cached=1`)
628
- .then((data) => {
629
- if (cancelled) return
630
- if (data.balanceFormatted && data.balanceSymbol) {
631
- setWalletBalance({
632
- formatted: data.balanceFormatted,
633
- symbol: data.balanceSymbol,
634
- assets: typeof data.portfolioSummary?.nonZeroAssets === 'number' ? data.portfolioSummary.nonZeroAssets : undefined,
635
- })
636
- }
637
- })
638
- .catch(() => {})
639
- return () => { cancelled = true }
640
- }, [activeWalletId])
641
-
642
- if (agentWalletIds.length === 0) return null
643
-
644
- const handleClick = () => {
645
- setWalletPanelAgentId(agent.id)
646
- navigateTo('wallets')
647
- }
648
-
649
- return (
650
- <div className={panelCardClass('p-4')}>
651
- <SectionLabel>Wallet</SectionLabel>
652
- <button
653
- type="button"
654
- onClick={handleClick}
655
- className="flex items-center gap-2 w-full bg-transparent border-none cursor-pointer text-left group"
656
- >
657
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/50 shrink-0">
658
- <rect x="2" y="6" width="20" height="14" rx="2" />
659
- <path d="M22 10H18a2 2 0 0 0 0 4h4" />
660
- </svg>
661
- {walletBalance ? (
662
- <span className="text-[13px] text-text-2 font-600 group-hover:text-accent-bright transition-colors">
663
- {walletBalance.formatted} {walletBalance.symbol}
664
- {walletBalance.assets && walletBalance.assets > 1 ? ` +${walletBalance.assets - 1} assets` : ''}
665
- </span>
666
- ) : (
667
- <span className="text-[13px] text-text-3/50 group-hover:text-text-3 transition-colors">View wallet</span>
668
- )}
669
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="ml-auto text-text-3/30 group-hover:text-text-3/60 transition-colors shrink-0">
670
- <polyline points="9 18 15 12 9 6" />
671
- </svg>
672
- </button>
673
- </div>
674
- )
675
- }
676
-
677
595
  // ─── Tools Section ───────────────────────────────────────────────
678
596
 
679
597
  function ToolsSection({ agent, session }: { agent: Agent; session: Session }) {
@@ -1139,7 +1057,8 @@ function ConfigTab({ agent }: { agent: Agent }) {
1139
1057
  const isOpenClaw = agent.provider === 'openclaw'
1140
1058
  const schedules = useAppStore((s) => s.schedules)
1141
1059
  const agentSchedules = Object.values(schedules).filter((s) => s.agentId === agent.id)
1142
- const [sandboxOpen, setSandboxOpen] = useState(false)
1060
+ const [executeOpen, setExecuteOpen] = useState(false)
1061
+ const [browserSandboxOpen, setBrowserSandboxOpen] = useState(false)
1143
1062
  const [openclawOpen, setOpenclawOpen] = useState(false)
1144
1063
  const [detailsOpen, setDetailsOpen] = useState(false)
1145
1064
 
@@ -1162,9 +1081,14 @@ function ConfigTab({ agent }: { agent: Agent }) {
1162
1081
  {/* Automations section */}
1163
1082
  <AutomationsSection schedules={agentSchedules} agent={agent} />
1164
1083
 
1165
- {/* Sandbox (collapsible) */}
1166
- <CollapsibleSection title="Sandbox" open={sandboxOpen} onToggle={() => setSandboxOpen((v) => !v)}>
1167
- <SandboxConfigSection agent={agent} />
1084
+ {/* Execute (collapsible) */}
1085
+ <CollapsibleSection title="Execute" open={executeOpen} onToggle={() => setExecuteOpen((v) => !v)}>
1086
+ <ExecuteToolConfigSection agent={agent} />
1087
+ </CollapsibleSection>
1088
+
1089
+ {/* Browser sandbox (collapsible) */}
1090
+ <CollapsibleSection title="Browser Sandbox" open={browserSandboxOpen} onToggle={() => setBrowserSandboxOpen((v) => !v)}>
1091
+ <BrowserSandboxSection agent={agent} />
1168
1092
  </CollapsibleSection>
1169
1093
 
1170
1094
  {/* OpenClaw settings (collapsible, OpenClaw only) */}
@@ -1327,13 +1251,85 @@ function AutomationsSection({ schedules, agent }: { schedules: Array<{ id: strin
1327
1251
  )
1328
1252
  }
1329
1253
 
1330
- // ─── Sandbox Config Section ──────────────────────────────────────
1254
+ // ─── Execute Config Section ──────────────────────────────────────
1331
1255
 
1332
- function SandboxConfigSection({ agent }: { agent: Agent }) {
1256
+ function ExecuteToolConfigSection({ agent }: { agent: Agent }) {
1257
+ const loadAgents = useAppStore((s) => s.loadAgents)
1258
+ const [saving, setSaving] = useState(false)
1259
+ const config = normalizeAgentExecuteConfig(agent.executeConfig)
1260
+
1261
+ const update = useCallback(async (patch: Partial<NonNullable<typeof agent.executeConfig>>) => {
1262
+ setSaving(true)
1263
+ try {
1264
+ const next = {
1265
+ ...config,
1266
+ ...patch,
1267
+ network: {
1268
+ ...(config.network || {}),
1269
+ ...((patch.network as Record<string, unknown> | undefined) || {}),
1270
+ },
1271
+ }
1272
+ await api('PUT', `/agents/${agent.id}`, { executeConfig: next })
1273
+ await loadAgents()
1274
+ } catch (err: unknown) {
1275
+ toast.error(err instanceof Error ? err.message : 'Failed to update execute config')
1276
+ } finally {
1277
+ setSaving(false)
1278
+ }
1279
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1280
+ }, [agent.id, config])
1281
+
1282
+ return (
1283
+ <div className="pt-3 flex flex-col gap-3">
1284
+ <div className="text-[11px] text-text-3/60">
1285
+ `execute` uses just-bash in sandbox mode by default. Host mode is explicit and required for persistent writes.
1286
+ </div>
1287
+ <div>
1288
+ <label className="text-[10px] text-text-3/50 block mb-1">Backend</label>
1289
+ <select
1290
+ value={config.backend || 'sandbox'}
1291
+ onChange={(e) => void update({ backend: e.target.value as 'sandbox' | 'host' })}
1292
+ disabled={saving}
1293
+ className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
1294
+ >
1295
+ <option value="sandbox">sandbox (just-bash)</option>
1296
+ <option value="host">host (real bash)</option>
1297
+ </select>
1298
+ </div>
1299
+ <div className="flex items-center justify-between">
1300
+ <span className="text-[11px] text-text-3/60">Allow network in sandbox mode</span>
1301
+ <ToggleSwitch
1302
+ on={config.network?.enabled !== false}
1303
+ onChange={() => void update({ network: { ...(config.network || {}), enabled: config.network?.enabled === false } })}
1304
+ disabled={saving}
1305
+ />
1306
+ </div>
1307
+ <div>
1308
+ <label className="text-[10px] text-text-3/50 block mb-1">Timeout (seconds)</label>
1309
+ <input
1310
+ type="number"
1311
+ defaultValue={config.timeout || 30}
1312
+ min={1}
1313
+ max={300}
1314
+ onBlur={(e) => void update({ timeout: Math.max(1, Math.min(300, Number(e.target.value) || 30)) })}
1315
+ className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
1316
+ />
1317
+ </div>
1318
+ <div className="text-[11px] text-text-3/50">
1319
+ `shell` remains the host command/process tool. Use `execute` for sandboxed one-shot scripts.
1320
+ </div>
1321
+ </div>
1322
+ )
1323
+ }
1324
+
1325
+ // ─── Browser Sandbox Section ─────────────────────────────────────
1326
+
1327
+ function BrowserSandboxSection({ agent }: { agent: Agent }) {
1333
1328
  const loadAgents = useAppStore((s) => s.loadAgents)
1334
1329
  const [saving, setSaving] = useState(false)
1335
1330
  const [dockerAvailable, setDockerAvailable] = useState<boolean | null>(null)
1336
1331
  const config = normalizeAgentSandboxConfig(agent.sandboxConfig)
1332
+ const browserEnabled = config.enabled && config.browser?.enabled !== false
1337
1333
 
1338
1334
  useEffect(() => {
1339
1335
  api<{ docker?: { available: boolean; version?: string | null } }>('GET', '/setup/doctor')
@@ -1344,7 +1340,16 @@ function SandboxConfigSection({ agent }: { agent: Agent }) {
1344
1340
  const update = useCallback(async (patch: Partial<NonNullable<typeof agent.sandboxConfig>>) => {
1345
1341
  setSaving(true)
1346
1342
  try {
1347
- const next = { ...config, ...patch }
1343
+ const next = {
1344
+ ...config,
1345
+ ...patch,
1346
+ browser: patch.browser === null
1347
+ ? null
1348
+ : {
1349
+ ...(config.browser || {}),
1350
+ ...((patch.browser as Record<string, unknown> | undefined) || {}),
1351
+ },
1352
+ }
1348
1353
  await api('PUT', `/agents/${agent.id}`, { sandboxConfig: next })
1349
1354
  await loadAgents()
1350
1355
  } catch (err: unknown) {
@@ -1358,69 +1363,98 @@ function SandboxConfigSection({ agent }: { agent: Agent }) {
1358
1363
  return (
1359
1364
  <div className="pt-3">
1360
1365
  <div className="flex items-center justify-between mb-3">
1361
- <span className="text-[12px] text-text-2">Prefer Docker sandboxes</span>
1362
- <ToggleSwitch on={config.enabled} onChange={() => void update({ enabled: !config.enabled })} disabled={saving} />
1366
+ <span className="text-[12px] text-text-2">Use Docker browser sandbox</span>
1367
+ <ToggleSwitch
1368
+ on={browserEnabled}
1369
+ onChange={() => void update({
1370
+ enabled: !browserEnabled,
1371
+ browser: {
1372
+ ...(config.browser || {}),
1373
+ enabled: !browserEnabled,
1374
+ },
1375
+ })}
1376
+ disabled={saving}
1377
+ />
1363
1378
  </div>
1364
1379
  {dockerAvailable === false && (
1365
1380
  <div className="text-[11px] text-amber-400/80 bg-amber-400/[0.06] rounded-[8px] px-2.5 py-2 mb-3 border border-amber-400/10">
1366
- Docker is not detected. SwarmClaw will fall back to host execution.
1381
+ Docker is not detected. Browser automation will use the host Playwright runtime.
1367
1382
  </div>
1368
1383
  )}
1369
1384
  {dockerAvailable === true && (
1370
1385
  <div className="text-[11px] text-emerald-400/70 mb-3 flex items-center gap-1.5">
1371
- <StatusDot status="online" size="sm" /> Docker available
1386
+ <StatusDot status="online" size="sm" /> Docker available for browser sandboxing
1372
1387
  </div>
1373
1388
  )}
1374
- {config.enabled && (
1389
+ {browserEnabled && (
1375
1390
  <div className="flex flex-col gap-2.5 mt-1">
1376
1391
  <div>
1377
- <label className="text-[10px] text-text-3/50 block mb-1">Image</label>
1378
- <input
1379
- type="text"
1380
- defaultValue={config.image || 'node:22-slim'}
1381
- onBlur={(e) => void update({ image: e.target.value.trim() || 'node:22-slim' })}
1382
- className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
1383
- />
1392
+ <label className="text-[10px] text-text-3/50 block mb-1">Scope</label>
1393
+ <select
1394
+ defaultValue={config.scope || 'session'}
1395
+ onChange={(e) => void update({ scope: e.target.value as 'session' | 'agent' })}
1396
+ className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
1397
+ >
1398
+ <option value="session">session</option>
1399
+ <option value="agent">agent</option>
1400
+ </select>
1384
1401
  </div>
1385
1402
  <div>
1386
- <label className="text-[10px] text-text-3/50 block mb-1">Network</label>
1403
+ <label className="text-[10px] text-text-3/50 block mb-1">Mode</label>
1387
1404
  <select
1388
- defaultValue={config.network || 'none'}
1389
- onChange={(e) => void update({ network: e.target.value as 'none' | 'bridge' })}
1405
+ defaultValue={config.mode === 'non-main' ? 'non-main' : 'all'}
1406
+ onChange={(e) => void update({ mode: e.target.value as 'all' | 'non-main' })}
1407
+ className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
1408
+ >
1409
+ <option value="all">all sessions</option>
1410
+ <option value="non-main">non-main sessions only</option>
1411
+ </select>
1412
+ </div>
1413
+ <div>
1414
+ <label className="text-[10px] text-text-3/50 block mb-1">Workspace access</label>
1415
+ <select
1416
+ defaultValue={config.workspaceAccess || 'rw'}
1417
+ onChange={(e) => void update({ workspaceAccess: e.target.value as 'ro' | 'rw' })}
1418
+ className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
1419
+ >
1420
+ <option value="rw">read/write</option>
1421
+ <option value="ro">read-only</option>
1422
+ </select>
1423
+ </div>
1424
+ <div>
1425
+ <label className="text-[10px] text-text-3/50 block mb-1">Browser network</label>
1426
+ <select
1427
+ defaultValue={config.browser?.network || 'bridge'}
1428
+ onChange={(e) => void update({ browser: { ...(config.browser || {}), network: e.target.value as 'none' | 'bridge' } })}
1390
1429
  className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text outline-none cursor-pointer focus:border-accent-bright/30"
1391
1430
  >
1392
1431
  <option value="none">none (isolated)</option>
1393
1432
  <option value="bridge">bridge (internet access)</option>
1394
1433
  </select>
1395
1434
  </div>
1396
- <div className="grid grid-cols-2 gap-2">
1397
- <div>
1398
- <label className="text-[10px] text-text-3/50 block mb-1">Memory (MB)</label>
1399
- <input
1400
- type="number"
1401
- defaultValue={config.memoryMb || 512}
1402
- min={64}
1403
- max={8192}
1404
- onBlur={(e) => void update({ memoryMb: Math.max(64, Math.min(8192, Number(e.target.value) || 512)) })}
1405
- className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
1406
- />
1407
- </div>
1408
- <div>
1409
- <label className="text-[10px] text-text-3/50 block mb-1">CPUs</label>
1410
- <input
1411
- type="number"
1412
- defaultValue={config.cpus || 1.0}
1413
- min={0.25}
1414
- max={8}
1415
- step={0.25}
1416
- onBlur={(e) => void update({ cpus: Math.max(0.25, Math.min(8, Number(e.target.value) || 1)) })}
1417
- className="w-full rounded-[8px] border border-white/[0.06] bg-black/[0.14] px-2.5 py-1.5 text-[12px] text-text font-mono outline-none focus:border-accent-bright/30"
1418
- />
1419
- </div>
1435
+ <div className="flex items-center justify-between">
1436
+ <span className="text-[11px] text-text-3/60">Headless browser</span>
1437
+ <ToggleSwitch
1438
+ on={config.browser?.headless !== false}
1439
+ onChange={() => void update({ browser: { ...(config.browser || {}), headless: config.browser?.headless === false } })}
1440
+ disabled={saving}
1441
+ />
1420
1442
  </div>
1421
1443
  <div className="flex items-center justify-between">
1422
- <span className="text-[11px] text-text-3/60">Read-only root filesystem</span>
1423
- <ToggleSwitch on={config.readonlyRoot ?? false} onChange={() => void update({ readonlyRoot: !config.readonlyRoot })} disabled={saving} />
1444
+ <span className="text-[11px] text-text-3/60">Enable noVNC observer</span>
1445
+ <ToggleSwitch
1446
+ on={config.browser?.enableNoVnc !== false}
1447
+ onChange={() => void update({ browser: { ...(config.browser || {}), enableNoVnc: config.browser?.enableNoVnc === false } })}
1448
+ disabled={saving}
1449
+ />
1450
+ </div>
1451
+ <div className="flex items-center justify-between">
1452
+ <span className="text-[11px] text-text-3/60">Mount uploads into sandbox browser</span>
1453
+ <ToggleSwitch
1454
+ on={config.browser?.mountUploads !== false}
1455
+ onChange={() => void update({ browser: { ...(config.browser || {}), mountUploads: config.browser?.mountUploads === false } })}
1456
+ disabled={saving}
1457
+ />
1424
1458
  </div>
1425
1459
  </div>
1426
1460
  )}
@@ -57,6 +57,7 @@ export function ChatArea() {
57
57
  const refreshSession = useAppStore((s) => s.refreshSession)
58
58
  const appSettings = useAppStore((s) => s.appSettings)
59
59
  const messages = useChatStore((s) => s.messages)
60
+ const messageStartIndex = useChatStore((s) => s.messageStartIndex)
60
61
  const setMessages = useChatStore((s) => s.setMessages)
61
62
  const streaming = useChatStore((s) => s.streaming)
62
63
  const streamingSessionId = useChatStore((s) => s.streamingSessionId)
@@ -179,15 +180,15 @@ export function ChatArea() {
179
180
  const preserveLocalStream = chatState.streaming && chatState.streamingSessionId === requestedSessionId
180
181
  // Clear stale messages immediately so the skeleton loader shows instead of
181
182
  // the previous chat's messages flashing briefly during the fetch.
182
- if (!preserveLocalStream) setMessages([])
183
+ if (!preserveLocalStream) setMessages([], { startIndex: 0, totalMessages: 0 })
183
184
  setMessagesLoading(true)
184
185
  if (!preserveLocalStream) {
185
186
  useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', assistantRenderId: null, toolEvents: [] })
186
187
  }
187
188
  fetchMessagesPaginated(requestedSessionId, 100).then((data) => {
188
189
  if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
189
- setMessages(data.messages)
190
- useChatStore.setState({ hasMoreMessages: data.hasMore, totalMessages: data.total })
190
+ setMessages(data.messages, { startIndex: data.startIndex, totalMessages: data.total })
191
+ useChatStore.setState({ hasMoreMessages: data.hasMore })
191
192
  }).catch((err) => {
192
193
  if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
193
194
  console.error('Failed to load messages:', err)
@@ -197,6 +198,12 @@ export function ChatArea() {
197
198
  fallbackSession?.messages?.length
198
199
  ? fallbackSession.messages
199
200
  : (fallbackLastMessage ? [fallbackLastMessage] : []),
201
+ {
202
+ startIndex: 0,
203
+ totalMessages: fallbackSession?.messages?.length
204
+ ? fallbackSession.messages.length
205
+ : (fallbackLastMessage ? 1 : 0),
206
+ },
200
207
  )
201
208
  }).finally(() => {
202
209
  if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
@@ -268,6 +275,8 @@ export function ChatArea() {
268
275
  const shouldPollMessages = !!sessionId && (isServerActive || isOngoingMonitored)
269
276
  const messagesRef = useRef(messages)
270
277
  messagesRef.current = messages
278
+ const messageStartIndexRef = useRef(messageStartIndex)
279
+ messageStartIndexRef.current = messageStartIndex
271
280
  const isServerActiveRef = useRef(isServerActive)
272
281
  isServerActiveRef.current = isServerActive
273
282
  const ttsEnabledRef = useRef(ttsEnabled)
@@ -287,8 +296,9 @@ export function ChatArea() {
287
296
  if (currentChatState.streaming && currentChatState.streamingSessionId === sessionId && currentChatState.streamSource === 'local') return
288
297
  const previous = messagesRef.current
289
298
  if (messagesDiffer(msgs, previous)) {
290
- const newMsgs = msgs.length > previous.length ? msgs.slice(previous.length) : []
291
- setMessages(msgs)
299
+ const previousEndIndex = messageStartIndexRef.current + previous.length
300
+ const newMsgs = msgs.length > previousEndIndex ? msgs.slice(previousEndIndex) : []
301
+ setMessages(msgs, { startIndex: 0, totalMessages: msgs.length })
292
302
  if (ttsEnabledRef.current && typeof document !== 'undefined' && document.visibilityState === 'visible') {
293
303
  const latestAssistant = [...newMsgs].reverse().find((m) => {
294
304
  if (m.role !== 'assistant') return false
@@ -305,15 +315,34 @@ export function ChatArea() {
305
315
  // eslint-disable-next-line react-hooks/exhaustive-deps
306
316
  }, [sessionId])
307
317
 
318
+ // Targeted message fetch that bypasses the streaming guard — used by
319
+ // refreshQueue to ensure persisted messages appear before sending queue
320
+ // items are cleaned up by timeout.
321
+ const syncMessagesForQueue = useCallback(async () => {
322
+ if (!sessionId) return
323
+ try {
324
+ const msgs = await fetchMessages(sessionId)
325
+ if (messagesDiffer(msgs, messagesRef.current)) {
326
+ setMessages(msgs, { startIndex: 0, totalMessages: msgs.length })
327
+ }
328
+ } catch (err) { console.error('Failed to sync messages for queue:', err) }
329
+ }, [sessionId, setMessages])
330
+
308
331
  const refreshQueue = useCallback(async () => {
309
332
  if (!sessionId) return
310
333
  try {
311
334
  await loadQueuedMessages(sessionId)
335
+ // If there are "sending" queue items, fetch messages so persisted
336
+ // versions appear before the queue item gets cleaned up.
337
+ const chatState = useChatStore.getState()
338
+ const hasSendingItems = chatState.queuedMessages.some(
339
+ (item) => item.sessionId === sessionId && item.sending,
340
+ )
341
+ if (hasSendingItems) void syncMessagesForQueue()
312
342
  // Bridge the gap between "queue item disappears" and "isServerActive propagates".
313
343
  // If the server picked up a queued run, immediately show the thinking indicator
314
344
  // so users don't see a blank gap waiting for loadSessions to propagate.
315
345
  const refreshedSession = useAppStore.getState().sessions[sessionId]
316
- const chatState = useChatStore.getState()
317
346
  if (
318
347
  refreshedSession?.currentRunId
319
348
  && !chatState.streaming
@@ -324,7 +353,7 @@ export function ChatArea() {
324
353
  } catch (err) {
325
354
  console.error('Failed to refresh queue:', err)
326
355
  }
327
- }, [loadQueuedMessages, sessionId, startServerStreamingPlaceholder])
356
+ }, [loadQueuedMessages, syncMessagesForQueue, sessionId, startServerStreamingPlaceholder])
328
357
 
329
358
  // Subscribe to WS messages for this session — always subscribe when session exists,
330
359
  // only enable fallback polling when actively needed
@@ -369,7 +398,7 @@ export function ChatArea() {
369
398
  && (state.streamingSessionId === sessionId || state.streamingSessionId == null)
370
399
  ) {
371
400
  // Server finished — clear all streaming state and fetch final messages
372
- fetchMessages(sessionId).then(setMessages).catch(() => {})
401
+ fetchMessages(sessionId).then((msgs) => setMessages(msgs, { startIndex: 0, totalMessages: msgs.length })).catch(() => {})
373
402
  markSessionLocallyIdle(sessionId)
374
403
  useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', displayText: '', assistantRenderId: null, streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
375
404
  }
@@ -404,7 +433,7 @@ export function ChatArea() {
404
433
  setConfirmClear(false)
405
434
  if (!sessionId) return
406
435
  await clearMessages(sessionId)
407
- setMessages([])
436
+ setMessages([], { startIndex: 0, totalMessages: 0 })
408
437
  await refreshSession(sessionId)
409
438
  }, [refreshSession, sessionId, setMessages])
410
439
 
@@ -15,7 +15,6 @@ import { AgentAvatar } from '@/components/agents/agent-avatar'
15
15
  import { timeAgoShort } from '@/lib/time-format'
16
16
  import { toast } from 'sonner'
17
17
  import { getEnabledCapabilityIds } from '@/lib/capability-selection'
18
- import { getMissionPath } from '@/lib/app/navigation'
19
18
 
20
19
  function shortPath(p: string): string {
21
20
  return (p || '').replace(/^\/Users\/\w+/, '~')
@@ -80,7 +79,6 @@ export function ChatCard({ session, active, onClick }: Props) {
80
79
  : session.name
81
80
  const connector = getSessionConnector(session, connectors)
82
81
  const queuedCount = Math.max(session.queuedCount ?? 0, optimisticQueuedCount)
83
- const mission = session.missionSummary || null
84
82
  const loopIsOngoing = appSettings.loopMode === 'ongoing'
85
83
  const explicitOptIn = session.heartbeatEnabled === true || agent?.heartbeatEnabled === true
86
84
  const intervalRaw = session.heartbeatIntervalSec ?? agent?.heartbeatIntervalSec ?? appSettings.heartbeatIntervalSec ?? DEFAULT_HEARTBEAT_INTERVAL_SEC
@@ -199,35 +197,6 @@ export function ChatCard({ session, active, onClick }: Props) {
199
197
  <span className="w-1.5 h-1.5 rounded-full bg-amber-400" />
200
198
  {queuedCount} queued {queuedCount === 1 ? 'message' : 'messages'} waiting
201
199
  </div>
202
- ) : mission ? (
203
- <div className="mt-1 flex items-center gap-2">
204
- <div className={`min-w-0 truncate text-[13px] leading-relaxed flex items-center gap-1.5 ${
205
- mission.status === 'waiting' || mission.status === 'failed' || mission.status === 'cancelled'
206
- ? 'text-amber-300/75'
207
- : mission.status === 'completed'
208
- ? 'text-emerald-300/75'
209
- : 'text-sky-300/75'
210
- }`}>
211
- <span className={`w-1.5 h-1.5 rounded-full ${
212
- mission.status === 'waiting' || mission.status === 'failed' || mission.status === 'cancelled'
213
- ? 'bg-amber-400'
214
- : mission.status === 'completed'
215
- ? 'bg-emerald-400'
216
- : 'bg-sky-400'
217
- }`} />
218
- <span className="truncate">{mission.waitingReason || mission.currentStep || mission.objective}</span>
219
- </div>
220
- <button
221
- type="button"
222
- onClick={(event) => {
223
- event.stopPropagation()
224
- router.push(getMissionPath(mission.id))
225
- }}
226
- className="shrink-0 rounded-[8px] border border-white/[0.08] px-2 py-1 text-[10px] font-700 uppercase tracking-[0.08em] text-text-2 transition-colors hover:bg-white/[0.05]"
227
- >
228
- Mission
229
- </button>
230
- </div>
231
200
  ) : (
232
201
  <div className="text-[13px] text-text-2/50 truncate mt-1 leading-relaxed">{preview}</div>
233
202
  )}