diff --git a/Dockerfile b/Dockerfile index a9a85a3..b5e7c55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,5 +29,5 @@ EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD curl -f http://localhost:3000/api/status || exit 1 + CMD curl -f http://localhost:3000/login || exit 1 CMD ["node", "server.js"] diff --git a/src/app/api/auth/google/route.ts b/src/app/api/auth/google/route.ts index c574057..a683074 100644 --- a/src/app/api/auth/google/route.ts +++ b/src/app/api/auth/google/route.ts @@ -92,8 +92,11 @@ export async function POST(request: Request) { }, }) + const isSecureRequest = request.headers.get('x-forwarded-proto') === 'https' + || new URL(request.url).protocol === 'https:' + response.cookies.set('mc-session', token, { - ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000) }), + ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000), isSecureRequest }), }) return response diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 2da7472..57a5274 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -41,8 +41,11 @@ export async function POST(request: Request) { }, }) + const isSecureRequest = request.headers.get('x-forwarded-proto') === 'https' + || new URL(request.url).protocol === 'https:' + response.cookies.set('mc-session', token, { - ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000) }), + ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000), isSecureRequest }), }) return response diff --git a/src/components/dashboard/agent-network.tsx b/src/components/dashboard/agent-network.tsx index 13ee8a9..7628ac7 100644 --- a/src/components/dashboard/agent-network.tsx +++ b/src/components/dashboard/agent-network.tsx @@ -86,7 +86,7 @@ function AgentNode({ data }: { data: any }) {
- {agent.model.split('/').pop()} + {(typeof agent.model === 'string' ? agent.model : '').split('/').pop() || 'unknown'}
{agent.session && ( diff --git a/src/lib/session-cookie.ts b/src/lib/session-cookie.ts index 5363668..508e432 100644 --- a/src/lib/session-cookie.ts +++ b/src/lib/session-cookie.ts @@ -9,9 +9,11 @@ function envFlag(name: string): boolean | undefined { return undefined } -export function getMcSessionCookieOptions(input: { maxAgeSeconds: number }): Partial { +export function getMcSessionCookieOptions(input: { maxAgeSeconds: number; isSecureRequest?: boolean }): Partial { const secureEnv = envFlag('MC_COOKIE_SECURE') - const secure = secureEnv ?? process.env.NODE_ENV === 'production' + // Explicit env wins. Otherwise auto-detect: only set secure if request came over HTTPS. + // Falls back to NODE_ENV=production when no request hint is available. + const secure = secureEnv ?? input.isSecureRequest ?? process.env.NODE_ENV === 'production' // Strict is safest for this app (same-site UI + API), but allow override for edge cases. const sameSiteRaw = (process.env.MC_COOKIE_SAMESITE || 'strict').toLowerCase() diff --git a/src/lib/sessions.ts b/src/lib/sessions.ts index 91165ba..8983e6e 100644 --- a/src/lib/sessions.ts +++ b/src/lib/sessions.ts @@ -62,7 +62,7 @@ export function getAllGatewaySessions(activeWithinMs = 60 * 60 * 1000): GatewayS updatedAt, chatType: s.chatType || 'unknown', channel: s.deliveryContext?.channel || s.lastChannel || s.channel || '', - model: s.model || '', + model: typeof s.model === 'object' && s.model?.primary ? String(s.model.primary) : String(s.model || ''), totalTokens: s.totalTokens || 0, inputTokens: s.inputTokens || 0, outputTokens: s.outputTokens || 0, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 57a6d77..67f9498 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -59,6 +59,13 @@ export function getStatusBadgeColor(status: AgentStatus['status']): string { } } +/** Normalize model field — OpenClaw 2026.3.x may send {primary: "model-name"} instead of a string */ +export function normalizeModel(model: unknown): string { + if (typeof model === 'string') return model + if (model && typeof model === 'object' && 'primary' in model) return String((model as any).primary) + return '' +} + export function sessionToAgent(session: Session): Agent { const getStatusFromSession = (session: Session): AgentStatus['status'] => { if (session.age === 'just now' || session.age.includes('m ago')) return 'active' diff --git a/src/lib/websocket.ts b/src/lib/websocket.ts index 53b86d1..7acb8fa 100644 --- a/src/lib/websocket.ts +++ b/src/lib/websocket.ts @@ -2,6 +2,7 @@ import { useCallback, useRef, useEffect } from 'react' import { useMissionControl } from '@/store' +import { normalizeModel } from '@/lib/utils' // Gateway protocol version (v3 required by OpenClaw 2026.x) const PROTOCOL_VERSION = 3 @@ -172,7 +173,7 @@ export function useWebSocket() { key: session.key || '', kind: session.kind || 'unknown', age: session.age || '', - model: session.model || '', + model: normalizeModel(session.model), tokens: session.tokens || '', flags: session.flags || [], active: session.active || false, @@ -219,7 +220,7 @@ export function useWebSocket() { // Handle various gateway events if (message.data?.type === 'token_usage') { addTokenUsage({ - model: message.data.model, + model: normalizeModel(message.data.model), sessionId: message.data.sessionId, date: new Date().toISOString(), inputTokens: message.data.inputTokens || 0, @@ -291,7 +292,7 @@ export function useWebSocket() { key: session.key || '', kind: session.kind || 'unknown', age: formatAge(session.updatedAt), - model: session.model || '', + model: normalizeModel(session.model), tokens: `${session.totalTokens || 0}/${session.contextTokens || 35000}`, flags: [], active: isActive(session.updatedAt),