Audit Console Phase 1: tool_call_trace emission from mcplocal router,
session_bind/rbac_decision event kinds, GET /audit/sessions endpoint,
full Ink TUI with session sidebar, event timeline, and detail view
(mcpctl console --audit).
System prompts: move 6 hardcoded LLM prompts to mcpctl-system project
with extensible ResourceRuleRegistry validation framework, template
variable enforcement ({{maxTokens}}, {{pageCount}}), and delete-resets-
to-default behavior. All consumers fetch via SystemPromptFetcher with
hardcoded fallbacks.
CLI: -p shorthand for --project across get/create/delete/config commands,
console auto-scroll improvements, shell completions regenerated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
/**
|
|
* Unified timeline — renders all events (interactive, observed)
|
|
* with a lane-colored gutter, windowed rendering, and auto-scroll.
|
|
*/
|
|
|
|
import { Box, Text } from 'ink';
|
|
import type { TimelineEvent, EventLane } from '../unified-types.js';
|
|
import { formatTime, formatEventSummary, trunc } from '../format-event.js';
|
|
|
|
const LANE_COLORS: Record<EventLane, string> = {
|
|
interactive: 'green',
|
|
observed: 'yellow',
|
|
};
|
|
|
|
const LANE_MARKERS: Record<EventLane, string> = {
|
|
interactive: '\u2502',
|
|
observed: '\u2502',
|
|
};
|
|
|
|
interface TimelineProps {
|
|
events: TimelineEvent[];
|
|
height: number;
|
|
focusedIdx: number; // -1 = auto-scroll to bottom
|
|
showProject: boolean;
|
|
}
|
|
|
|
export function Timeline({ events, height, focusedIdx, showProject }: TimelineProps) {
|
|
const maxVisible = Math.max(1, height - 2); // header + spacing
|
|
let startIdx: number;
|
|
if (focusedIdx >= 0) {
|
|
startIdx = Math.max(0, Math.min(focusedIdx - Math.floor(maxVisible / 2), events.length - maxVisible));
|
|
} else {
|
|
startIdx = Math.max(0, events.length - maxVisible);
|
|
}
|
|
const visible = events.slice(startIdx, startIdx + maxVisible);
|
|
|
|
return (
|
|
<Box flexDirection="column" flexGrow={1} paddingLeft={1}>
|
|
<Text bold>
|
|
Timeline <Text dimColor>({events.length} events{focusedIdx >= 0 ? ` \u00B7 #${focusedIdx + 1}` : ' \u00B7 following'})</Text>
|
|
</Text>
|
|
{visible.length === 0 && (
|
|
<Box marginTop={1}>
|
|
<Text dimColor>{' waiting for traffic\u2026'}</Text>
|
|
</Box>
|
|
)}
|
|
{visible.map((event, vi) => {
|
|
const absIdx = startIdx + vi;
|
|
const isFocused = absIdx === focusedIdx;
|
|
const { arrow, color, label, detail, detailColor } = formatEventSummary(
|
|
event.eventType,
|
|
event.method,
|
|
event.body,
|
|
event.upstreamName,
|
|
event.durationMs,
|
|
);
|
|
const isLifecycle = event.eventType === 'session_created' || event.eventType === 'session_closed';
|
|
const laneColor = LANE_COLORS[event.lane];
|
|
const laneMarker = LANE_MARKERS[event.lane];
|
|
const focusMarker = isFocused ? '\u25B8' : ' ';
|
|
const hasCorrelation = event.correlationId !== undefined;
|
|
|
|
if (isLifecycle) {
|
|
return (
|
|
<Text key={event.id} wrap="truncate">
|
|
<Text color={laneColor}>{laneMarker}</Text>
|
|
<Text color={isFocused ? 'cyan' : undefined}>{focusMarker}</Text>
|
|
<Text dimColor>{formatTime(event.timestamp)} </Text>
|
|
<Text color={color} bold>{arrow} {label}</Text>
|
|
{showProject && <Text color="gray"> [{trunc(event.projectName, 12)}]</Text>}
|
|
<Text dimColor> {event.sessionId.slice(0, 8)}</Text>
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
const isUpstream = event.eventType.startsWith('upstream_');
|
|
|
|
return (
|
|
<Text key={event.id} wrap="truncate">
|
|
<Text color={laneColor}>{laneMarker}</Text>
|
|
<Text color={isFocused ? 'cyan' : undefined}>{focusMarker}</Text>
|
|
<Text dimColor>{formatTime(event.timestamp)} </Text>
|
|
{showProject && <Text color="gray">[{trunc(event.projectName, 12)}] </Text>}
|
|
<Text color={color}>{arrow} </Text>
|
|
<Text bold={!isUpstream} color={color}>{label}</Text>
|
|
{detail ? (
|
|
<Text color={detailColor} dimColor={!detailColor}> {detail}</Text>
|
|
) : null}
|
|
{hasCorrelation && <Text dimColor>{' \u26D3'}</Text>}
|
|
</Text>
|
|
);
|
|
})}
|
|
</Box>
|
|
);
|
|
}
|