@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.
- package/README.md +54 -23
- package/next.config.ts +1 -0
- package/package.json +4 -3
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/settings/route.ts +0 -2
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +21 -33
- package/src/cli/spec.js +19 -30
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +165 -131
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +1 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +11 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
- package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +1 -10
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -17
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -8
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +10 -0
- package/src/lib/server/storage.ts +18 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +2 -2
- package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -35
- package/src/lib/server/tool-planning.ts +11 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +1 -1
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +49 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +264 -0
- package/src/types/app-settings.ts +173 -0
- package/src/types/approval.ts +25 -0
- package/src/types/connector.ts +188 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +56 -0
- package/src/types/misc.ts +737 -0
- package/src/types/protocol.ts +420 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +180 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +215 -0
- package/src/types/skill.ts +157 -0
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +144 -0
- package/src/types/working-state.ts +204 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/sandbox.ts +0 -281
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- 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 [
|
|
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
|
-
{/*
|
|
1166
|
-
<CollapsibleSection title="
|
|
1167
|
-
<
|
|
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
|
-
// ───
|
|
1254
|
+
// ─── Execute Config Section ──────────────────────────────────────
|
|
1331
1255
|
|
|
1332
|
-
function
|
|
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 = {
|
|
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">
|
|
1362
|
-
<ToggleSwitch
|
|
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.
|
|
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
|
-
{
|
|
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">
|
|
1378
|
-
<
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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">
|
|
1403
|
+
<label className="text-[10px] text-text-3/50 block mb-1">Mode</label>
|
|
1387
1404
|
<select
|
|
1388
|
-
defaultValue={config.
|
|
1389
|
-
onChange={(e) => void update({
|
|
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="
|
|
1397
|
-
<
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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">
|
|
1423
|
-
<ToggleSwitch
|
|
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
|
|
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
|
|
291
|
-
|
|
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
|
)}
|