feat: OpenClaw WebSocket integration with workspace file preview

- Migrate OpenClaw from HTTP (port 8004) to WebSocket (port 18789)
- Add workspace file list and content preview handlers
- Add OpenClawStatus component with agent/skills view
- Add OpenClawView panel in trader interface
- Add Zustand store for OpenClaw state management
- Fix gateway logging noise (yfinance, websockets)
- Fix RunWorkspaceManager.get_agent_asset_dir attribute error
- Handle missing workspace files gracefully in preview

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-27 11:08:15 +08:00
parent 9bcc4221a4
commit 6ecc224427
20 changed files with 5691 additions and 6 deletions

View File

@@ -0,0 +1,227 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export const useOpenClawStore = create(
persist(
(set) => ({
// Raw data
openclawStatus: null,
openclawSessions: [],
openclawSessionDetail: null,
openclawSessionHistory: [],
openclawCronJobs: [],
openclawApprovals: [],
// Loading states
isStatusLoading: false,
isSessionsLoading: false,
isSessionDetailLoading: false,
isCronLoading: false,
isApprovalsLoading: false,
// Error states
statusError: null,
sessionsError: null,
sessionDetailError: null,
cronError: null,
approvalsError: null,
// Agents state
agents: [],
agentsLoading: false,
agentsError: null,
agentsPresence: {},
// Skills state
skills: [],
skillsLoading: false,
skillsError: null,
// Models state
models: [],
modelsLoading: false,
modelsError: null,
// Hooks state
hooks: [],
hooksLoading: false,
hooksError: null,
// Plugins state
plugins: [],
pluginsLoading: false,
pluginsError: null,
// Secrets audit state
secretsAudit: null,
secretsAuditLoading: false,
secretsAuditError: null,
// Security audit state
securityAudit: null,
securityAuditLoading: false,
securityAuditError: null,
// Daemon status state
daemonStatus: null,
daemonStatusLoading: false,
daemonStatusError: null,
// Pairing state
pairing: null,
pairingLoading: false,
pairingError: null,
// QR code state
qrCode: null,
qrCodeLoading: false,
qrCodeError: null,
// Update status state
updateStatus: null,
updateStatusLoading: false,
updateStatusError: null,
// Models aliases state
modelsAliases: null,
modelsAliasesLoading: false,
modelsAliasesError: null,
// Models fallbacks state
modelsFallbacks: [],
modelsFallbacksLoading: false,
modelsFallbacksError: null,
// Models image fallbacks state
modelsImageFallbacks: [],
modelsImageFallbacksLoading: false,
modelsImageFallbacksError: null,
// Skill update state
skillUpdate: null,
skillUpdateLoading: false,
skillUpdateError: null,
// Workspace files state (per agent, keyed by workspace path)
workspaceFiles: {},
workspaceFilesLoading: false,
workspaceFilesError: null,
// Workspace file content (keyed by "agentId:filename")
workspaceFileContent: {},
// Selected session key for detail/history drill-down
selectedSessionKey: null,
// WebSocket client ref (set by App.jsx on connection)
clientRef: null,
setClientRef: (ref) => set({ clientRef: ref }),
// Setters
setOpenclawStatus: (data) => set({ openclawStatus: data, statusError: null }),
setOpenclawSessions: (data) => set({ openclawSessions: data?.sessions || [], sessionsError: null }),
setOpenclawSessionDetail: (data) => set({ openclawSessionDetail: data?.session || null, sessionDetailError: null }),
setOpenclawSessionHistory: (data) => set({ openclawSessionHistory: data?.history || [], sessionDetailError: null }),
setOpenclawCronJobs: (data) => set({ openclawCronJobs: data?.cron || [], cronError: null }),
setOpenclawApprovals: (data) => set({ openclawApprovals: data?.approvals || [], approvalsError: null }),
setSelectedSessionKey: (key) => set({ selectedSessionKey: key }),
setStatusLoading: (v) => set({ isStatusLoading: v }),
setSessionsLoading: (v) => set({ isSessionsLoading: v }),
setSessionDetailLoading: (v) => set({ isSessionDetailLoading: v }),
setCronLoading: (v) => set({ isCronLoading: v }),
setApprovalsLoading: (v) => set({ isApprovalsLoading: v }),
setStatusError: (e) => set({ statusError: e }),
setSessionsError: (e) => set({ sessionsError: e }),
setSessionDetailError: (e) => set({ sessionDetailError: e }),
setCronError: (e) => set({ cronError: e }),
setApprovalsError: (e) => set({ approvalsError: e }),
setAgents: (agents) => set({ agents }),
setAgentsLoading: (loading) => set({ agentsLoading: loading }),
setAgentsError: (error) => set({ agentsError: error }),
setAgentsPresence: (presence) => set({ agentsPresence: presence }),
setSkills: (skills) => set({ skills }),
setSkillsLoading: (loading) => set({ skillsLoading: loading }),
setSkillsError: (error) => set({ skillsError: error }),
setModels: (models) => set({ models }),
setModelsLoading: (loading) => set({ modelsLoading: loading }),
setModelsError: (error) => set({ modelsError: error }),
setHooks: (hooks) => set({ hooks }),
setHooksLoading: (loading) => set({ hooksLoading: loading }),
setHooksError: (error) => set({ hooksError: error }),
setPlugins: (plugins) => set({ plugins }),
setPluginsLoading: (loading) => set({ pluginsLoading: loading }),
setPluginsError: (error) => set({ pluginsError: error }),
setSecretsAudit: (data) => set({ secretsAudit: data }),
setSecretsAuditLoading: (loading) => set({ secretsAuditLoading: loading }),
setSecretsAuditError: (error) => set({ secretsAuditError: error }),
setSecurityAudit: (data) => set({ securityAudit: data }),
setSecurityAuditLoading: (loading) => set({ securityAuditLoading: loading }),
setSecurityAuditError: (error) => set({ securityAuditError: error }),
setDaemonStatus: (data) => set({ daemonStatus: data }),
setDaemonStatusLoading: (loading) => set({ daemonStatusLoading: loading }),
setDaemonStatusError: (error) => set({ daemonStatusError: error }),
setPairing: (data) => set({ pairing: data }),
setPairingLoading: (loading) => set({ pairingLoading: loading }),
setPairingError: (error) => set({ pairingError: error }),
setQrCode: (data) => set({ qrCode: data }),
setQrCodeLoading: (loading) => set({ qrCodeLoading: loading }),
setQrCodeError: (error) => set({ qrCodeError: error }),
setUpdateStatus: (data) => set({ updateStatus: data }),
setUpdateStatusLoading: (loading) => set({ updateStatusLoading: loading }),
setUpdateStatusError: (error) => set({ updateStatusError: error }),
setModelsAliases: (data) => set({ modelsAliases: data }),
setModelsAliasesLoading: (loading) => set({ modelsAliasesLoading: loading }),
setModelsAliasesError: (error) => set({ modelsAliasesError: error }),
setModelsFallbacks: (data) => set({ modelsFallbacks: data }),
setModelsFallbacksLoading: (loading) => set({ modelsFallbacksLoading: loading }),
setModelsFallbacksError: (error) => set({ modelsFallbacksError: error }),
setModelsImageFallbacks: (data) => set({ modelsImageFallbacks: data }),
setModelsImageFallbacksLoading: (loading) => set({ modelsImageFallbacksLoading: loading }),
setModelsImageFallbacksError: (error) => set({ modelsImageFallbacksError: error }),
setSkillUpdate: (data) => set({ skillUpdate: data }),
setSkillUpdateLoading: (loading) => set({ skillUpdateLoading: loading }),
setSkillUpdateError: (error) => set({ skillUpdateError: error }),
setWorkspaceFiles: (workspace, data) => set((state) => ({
workspaceFiles: { ...state.workspaceFiles, [workspace]: data },
})),
setWorkspaceFilesLoading: (loading) => set({ workspaceFilesLoading: loading }),
setWorkspaceFilesError: (error) => set({ workspaceFilesError: error }),
setWorkspaceFileContent: (key, content) => set((state) => ({
workspaceFileContent: { ...state.workspaceFileContent, [key]: content },
})),
}),
{
name: "openclaw-store",
// Skip persisting ephemeral UI state
partialize: (state) => ({
// Persist only data, not loading/error/UI states
openclawStatus: state.openclawStatus,
openclawSessions: state.openclawSessions,
openclawCronJobs: state.openclawCronJobs,
openclawApprovals: state.openclawApprovals,
agents: state.agents,
agentsPresence: state.agentsPresence,
skills: state.skills,
models: state.models,
hooks: state.hooks,
plugins: state.plugins,
secretsAudit: state.secretsAudit,
securityAudit: state.securityAudit,
daemonStatus: state.daemonStatus,
pairing: state.pairing,
qrCode: state.qrCode,
updateStatus: state.updateStatus,
modelsAliases: state.modelsAliases,
modelsFallbacks: state.modelsFallbacks,
modelsImageFallbacks: state.modelsImageFallbacks,
skillUpdate: state.skillUpdate,
}),
}
)
);