File size: 2,662 Bytes
adb91eb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | import { useI18n } from '../i18n'
import type { OrchestrateResult } from '../api/ai'
type StepRole = 'advisory' | 'authoritative' | 'user' | 'notExecuted' | 'executed'
interface Step {
key: string
labelKey:
| 'trace.context' | 'trace.risk' | 'trace.policy' | 'trace.auth'
| 'trace.rail' | 'trace.userAction' | 'trace.paymentCore'
role: StepRole
summary: string
}
function roleBadge(role: StepRole): { key: string; tone: string } {
switch (role) {
case 'advisory': return { key: 'shadow.advisory', tone: 'tone-info' }
case 'authoritative': return { key: 'authoritative.badge', tone: 'tone-ok' }
case 'user': return { key: 'trace.userRequired', tone: 'tone-warn' }
case 'executed': return { key: 'trace.executed', tone: 'tone-ok' }
default: return { key: 'trace.notExecuted', tone: 'tone-bad' }
}
}
/** Safe, read-only sequence view. Never renders capabilities, signatures, keys or envelopes. */
export function AgentTrace({ result }: { result: OrchestrateResult }) {
const { t } = useI18n()
const executed = result.agent_state === 'initiated'
const steps: Step[] = [
{ key: 'context', labelKey: 'trace.context', role: 'advisory',
summary: result.risk ? '' : t('shadow.noChange') },
{ key: 'risk', labelKey: 'trace.risk', role: 'advisory',
summary: result.risk ? result.risk.band : 'model_unavailable' },
{ key: 'policy', labelKey: 'trace.policy', role: 'authoritative', summary: result.decision },
{ key: 'auth', labelKey: 'trace.auth', role: 'advisory',
summary: result.auth_recommendation?.recommended_method ?? '' },
{ key: 'rail', labelKey: 'trace.rail', role: 'advisory',
summary: (result.rails?.ranked ?? []).join(', ') },
{ key: 'user', labelKey: 'trace.userAction', role: 'user',
summary: result.decision === 'step_up' ? t('trace.userRequired') : '' },
{ key: 'core', labelKey: 'trace.paymentCore',
role: executed ? 'executed' : 'notExecuted',
summary: result.payment?.id ?? t('trace.notExecuted') },
]
return (
<div className="agent-trace">
<h3>{t('trace.title')}</h3>
<ol className="trace-list">
{steps.map((s) => {
const b = roleBadge(s.role)
return (
<li key={s.key} className={`trace-step role-${s.role}`}>
<div className="trace-head">
<span className="trace-label">{t(s.labelKey)}</span>
<span className={`badge ${b.tone}`}>{t(b.key as never)}</span>
</div>
{s.summary && <span className="trace-summary">{s.summary}</span>}
</li>
)
})}
</ol>
</div>
)
}
|