Fixes silent runtime fallback visibility (dashboard never read wasConfigured or session:runtime-resolved) and threads the real FallbackReason (not_found vs factory_error) through resolveRuntime()/logRuntimeFallback instead of hardcoding "not_found" for every fallback. - packages/engine/src/runtime-resolution.ts: resolvePluginRuntime() now returns a tagged miss result distinguishing not_found from factory_error; resolveRuntime() threads the real reason through and returns it as ResolvedRuntime.fallbackReason - packages/engine/src/agent-session-helpers.ts: includes fallbackReason in the session:runtime-resolved audit event metadata - packages/dashboard/src/routes/register-task-workflow-routes.ts: new GET /api/tasks/:id/runtime-fallback endpoint - packages/dashboard/app/hooks/useRuntimeFallbackStatus.ts + packages/dashboard/app/components/RuntimeFallbackBadge.tsx: new polling hook + badge/toast component wired into TaskCard, ActiveAgentsPanel, and AgentsView Ref: Fusion task FUX-022, investigations/FUX-017-hermes-runtime-fallback.md recommendation #1
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
/**
|
|
* RuntimeFallbackBadge (FUX-022)
|
|
*
|
|
* Renders a visible badge on an agent/task card when the most recent
|
|
* `session:runtime-resolved` audit event for that task shows
|
|
* `wasConfigured: false` alongside a non-empty configured `runtimeHint` —
|
|
* i.e. the configured runtime (e.g. "hermes") could not be resolved and the
|
|
* session silently fell back to the default `pi` runtime. Also fires a toast
|
|
* via the shared ToastProvider the first time this fallback state is newly
|
|
* observed for a session (not re-fired on every poll/re-render).
|
|
*
|
|
* Renders null (no leftover placeholder) for every other state: no event
|
|
* yet, wasConfigured true, or wasConfigured false with a blank hint.
|
|
*/
|
|
import { memo, useEffect } from "react";
|
|
import { AlertTriangle } from "lucide-react";
|
|
import { useRuntimeFallbackStatus } from "../hooks/useRuntimeFallbackStatus";
|
|
import { useToast } from "../hooks/useToast";
|
|
|
|
interface RuntimeFallbackBadgeProps {
|
|
taskId?: string;
|
|
/** Gate polling to visible cards only (e.g. pass the card's own isInViewport state). */
|
|
isInViewport: boolean;
|
|
projectId?: string;
|
|
}
|
|
|
|
function RuntimeFallbackBadgeComponent({ taskId, isInViewport, projectId }: RuntimeFallbackBadgeProps) {
|
|
const { addToast } = useToast();
|
|
const status = useRuntimeFallbackStatus(taskId, isInViewport, projectId);
|
|
|
|
useEffect(() => {
|
|
if (status.shouldToastNow && status.message) {
|
|
addToast(status.message, "warning");
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- addToast identity is stable per ToastProvider instance
|
|
}, [status.shouldToastNow, status.message]);
|
|
|
|
if (!status.showBadge || !status.message) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<span
|
|
className="card-status-badge card-runtime-fallback-badge"
|
|
title={status.message}
|
|
data-testid="runtime-fallback-badge"
|
|
data-runtime-hint={status.runtimeHint ?? undefined}
|
|
data-runtime-fallback-reason={status.reason ?? undefined}
|
|
>
|
|
<AlertTriangle size={10} aria-hidden="true" />
|
|
<span>{status.message}</span>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export const RuntimeFallbackBadge = memo(RuntimeFallbackBadgeComponent);
|
|
RuntimeFallbackBadge.displayName = "RuntimeFallbackBadge";
|