fix(connection-status): return full capabilities payload in dev (#285)
The vite dev server intercepted /api/connection-status with a slim
inline shortcut handler that returned only {ok, mode, backend} \u2014
silently overriding the real route at src/routes/api/connection-status.ts
which returns the full ConnectionStatus payload.
Downstream feature gates (useFeatureCapability, useFeatureAvailable)
read .capabilities from the response. With the slim body, every
capability evaluated to undefined, so dev users got UI states that
looked like 'feature not available' even when the gateway was healthy
and exposing the API.
Fix: drop the inline shortcut entirely. The real route file already
caches via ensureGatewayProbed() (PROBE_TTL_MS), so the cost in dev
is negligible and the payload is correct everywhere.
Closes #285. Also drops the now-unused dashboard URL / token / cache
locals in vite.config.ts that were only used by the deleted handler;
the per-PR work that introduced them (#288, #289) was the
right diagnosis but the wrong fix \u2014 the real route was already
doing it correctly.
This commit is contained in:
136
vite.config.ts
136
vite.config.ts
@@ -85,36 +85,9 @@ async function isClaudeAgentHealthy(port = 8642): Promise<boolean> {
|
||||
const config = defineConfig(({ mode, command }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const claudeApiUrl = env.CLAUDE_API_URL?.trim() || 'http://127.0.0.1:8642'
|
||||
// Dashboard URL — where /api/sessions actually lives. The Hermes Agent
|
||||
// (port 8642) does not serve /api/sessions; the Hermes Dashboard does
|
||||
// (default port 9119). The Vite dev-server health probe used to hit
|
||||
// /api/sessions on the agent URL, producing a 404 every 15 seconds in
|
||||
// setups where the dashboard runs separately. See #276.
|
||||
const claudeDashboardUrl =
|
||||
env.HERMES_DASHBOARD_URL?.trim() ||
|
||||
env.CLAUDE_DASHBOARD_URL?.trim() ||
|
||||
'http://127.0.0.1:9119'
|
||||
const claudeApiToken =
|
||||
env.HERMES_API_TOKEN?.trim() || env.CLAUDE_API_TOKEN?.trim() || ''
|
||||
const probeHeaders: Record<string, string> = claudeApiToken
|
||||
? { Authorization: `Bearer ${claudeApiToken}` }
|
||||
: {}
|
||||
|
||||
// Cache the most recent successful /api/connection-status result so we
|
||||
// don't hammer the agent + dashboard with three fetches every 15s when
|
||||
// the answer hasn't changed. Negative results are cached for a much
|
||||
// shorter window so transient outages recover quickly.
|
||||
type ConnectionStatusBody = {
|
||||
ok: boolean
|
||||
mode: 'enhanced' | 'portable' | 'disconnected'
|
||||
backend: string
|
||||
}
|
||||
let connectionStatusCache: {
|
||||
expiresAt: number
|
||||
body: ConnectionStatusBody
|
||||
} | null = null
|
||||
const CONNECTION_STATUS_CACHE_OK_MS = 60_000
|
||||
const CONNECTION_STATUS_CACHE_FAIL_MS = 5_000
|
||||
// /api/connection-status is handled by the real route file at
|
||||
// src/routes/api/connection-status.ts; the dev server no longer
|
||||
// intercepts that path with a slim shortcut. See #285.
|
||||
|
||||
// Hermes Agent auto-start state
|
||||
let claudeAgentChild: ChildProcess | null = null
|
||||
@@ -595,102 +568,13 @@ const config = defineConfig(({ mode, command }) => {
|
||||
return
|
||||
}
|
||||
|
||||
// Portable-aware health check — returns ok if any chat backend is available
|
||||
if (
|
||||
req.method === 'GET' &&
|
||||
requestPath === '/api/connection-status'
|
||||
) {
|
||||
const sendCached = (body: ConnectionStatusBody) => {
|
||||
res.statusCode = body.ok ? 200 : 502
|
||||
res.setHeader('content-type', 'application/json')
|
||||
res.end(JSON.stringify(body))
|
||||
}
|
||||
|
||||
if (
|
||||
connectionStatusCache &&
|
||||
connectionStatusCache.expiresAt > Date.now()
|
||||
) {
|
||||
sendCached(connectionStatusCache.body)
|
||||
return
|
||||
}
|
||||
|
||||
const cacheAndSend = (
|
||||
body: ConnectionStatusBody,
|
||||
ttlMs: number,
|
||||
) => {
|
||||
connectionStatusCache = {
|
||||
expiresAt: Date.now() + ttlMs,
|
||||
body,
|
||||
}
|
||||
sendCached(body)
|
||||
}
|
||||
|
||||
try {
|
||||
// Probe the agent for /v1/models and the dashboard for
|
||||
// /api/sessions in parallel. /api/sessions lives on the
|
||||
// dashboard, not the agent, so probing it on the agent URL
|
||||
// (the previous behaviour) produced a 404 every 15s when
|
||||
// the dashboard ran on a separate port. See #276.
|
||||
const [modelsRes, sessionsRes] = await Promise.all([
|
||||
fetch(`${claudeApiUrl}/v1/models`, {
|
||||
headers: probeHeaders,
|
||||
signal: AbortSignal.timeout(3000),
|
||||
}).catch(() => null),
|
||||
fetch(`${claudeDashboardUrl}/api/sessions?limit=1`, {
|
||||
headers: probeHeaders,
|
||||
signal: AbortSignal.timeout(3000),
|
||||
}).catch(() => null),
|
||||
])
|
||||
const hasModels = modelsRes?.ok ?? false
|
||||
const hasSessions = sessionsRes?.ok ?? false
|
||||
if (hasModels && hasSessions) {
|
||||
cacheAndSend(
|
||||
{
|
||||
ok: true,
|
||||
mode: 'enhanced',
|
||||
backend: claudeApiUrl,
|
||||
},
|
||||
CONNECTION_STATUS_CACHE_OK_MS,
|
||||
)
|
||||
return
|
||||
}
|
||||
if (hasModels) {
|
||||
cacheAndSend(
|
||||
{
|
||||
ok: true,
|
||||
mode: 'portable',
|
||||
backend: claudeApiUrl,
|
||||
},
|
||||
CONNECTION_STATUS_CACHE_OK_MS,
|
||||
)
|
||||
return
|
||||
}
|
||||
// Fall back to /health for full Hermes backends
|
||||
const healthRes = await fetch(`${claudeApiUrl}/health`, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
})
|
||||
cacheAndSend(
|
||||
{
|
||||
ok: healthRes.ok,
|
||||
mode: 'enhanced',
|
||||
backend: claudeApiUrl,
|
||||
},
|
||||
healthRes.ok
|
||||
? CONNECTION_STATUS_CACHE_OK_MS
|
||||
: CONNECTION_STATUS_CACHE_FAIL_MS,
|
||||
)
|
||||
} catch {
|
||||
cacheAndSend(
|
||||
{
|
||||
ok: false,
|
||||
mode: 'disconnected',
|
||||
backend: claudeApiUrl,
|
||||
},
|
||||
CONNECTION_STATUS_CACHE_FAIL_MS,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
// /api/connection-status is handled by the real route file at
|
||||
// src/routes/api/connection-status.ts — it returns the full
|
||||
// ConnectionStatus payload including capabilities and chatMode
|
||||
// that downstream feature gates depend on. Earlier versions
|
||||
// had an inline shortcut handler here that returned a slim
|
||||
// body ({ok, mode, backend}) which silently broke things like
|
||||
// useFeatureCapability/useFeatureAvailable in dev mode. See #285.
|
||||
|
||||
if (
|
||||
req.method !== 'POST' ||
|
||||
|
||||
Reference in New Issue
Block a user