feat(M4-007,M4-008,M4-012): wire routing engine into ChatGateway, add /model override and transparency
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed

M4-012: In ChatGateway.handleMessage(), call routingEngine.resolve() before
creating a new agent session when no explicit provider/model is supplied.
The routing decision's provider/model are passed to AgentService.createSession().

M4-007: Add sticky per-session /model override via ChatGateway.setModelOverride().
When active, the routing engine is bypassed entirely. CommandExecutorService
handleModel() is updated to set/clear the override. /model clear resets to
automatic routing. /model with no args shows the current override or usage hint.

M4-008: Include routingDecision in the session:info socket event.
Add RoutingDecisionInfo to SessionInfoPayload in @mosaic/types.
useSocket() tracks the routing decision in state and the BottomBar TUI component
displays a "Routed: <model> (<reason>)" line when a routing decision is present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 19:48:06 -05:00
parent f22ce0096b
commit 7bf2ad1f92
6 changed files with 116 additions and 4 deletions

View File

@@ -403,6 +403,7 @@ export function TuiApp({
providerName={socket.providerName}
thinkingLevel={socket.thinkingLevel}
conversationId={socket.conversationId}
routingDecision={socket.routingDecision}
/>
</Box>
);

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Box, Text } from 'ink';
import type { RoutingDecisionInfo } from '@mosaic/types';
import type { TokenUsage } from '../hooks/use-socket.js';
import type { GitInfo } from '../hooks/use-git-info.js';
@@ -12,6 +13,8 @@ export interface BottomBarProps {
providerName: string | null;
thinkingLevel: string;
conversationId: string | undefined;
/** Routing decision info for transparency display (M4-008) */
routingDecision?: RoutingDecisionInfo | null;
}
function formatTokens(n: number): string {
@@ -38,6 +41,7 @@ export function BottomBar({
providerName,
thinkingLevel,
conversationId,
routingDecision,
}: BottomBarProps) {
const gatewayStatus = connected ? 'Connected' : connecting ? 'Connecting…' : 'Disconnected';
const gatewayColor = connected ? 'green' : connecting ? 'yellow' : 'red';
@@ -120,6 +124,15 @@ export function BottomBar({
</Text>
</Box>
</Box>
{/* Line 4: routing transparency (M4-008) — only shown when a routing decision is available */}
{routingDecision && (
<Box>
<Text dimColor>
Routed: {routingDecision.model} ({routingDecision.reason})
</Text>
</Box>
)}
</Box>
);
}

View File

@@ -14,6 +14,7 @@ import type {
CommandManifestPayload,
SlashCommandResultPayload,
SystemReloadPayload,
RoutingDecisionInfo,
} from '@mosaic/types';
import { commandRegistry } from '../commands/index.js';
@@ -66,6 +67,8 @@ export interface UseSocketReturn {
providerName: string | null;
thinkingLevel: string;
availableThinkingLevels: string[];
/** Last routing decision received from the gateway (M4-008) */
routingDecision: RoutingDecisionInfo | null;
sendMessage: (content: string) => void;
addSystemMessage: (content: string) => void;
setThinkingLevel: (level: string) => void;
@@ -109,6 +112,7 @@ export function useSocket(opts: UseSocketOptions): UseSocketReturn {
const [providerName, setProviderName] = useState<string | null>(null);
const [thinkingLevel, setThinkingLevelState] = useState<string>('off');
const [availableThinkingLevels, setAvailableThinkingLevels] = useState<string[]>([]);
const [routingDecision, setRoutingDecision] = useState<RoutingDecisionInfo | null>(null);
const [connectionError, setConnectionError] = useState<string | null>(null);
const socketRef = useRef<TypedSocket | null>(null);
@@ -154,6 +158,10 @@ export function useSocket(opts: UseSocketOptions): UseSocketReturn {
setModelName(data.modelId);
setThinkingLevelState(data.thinkingLevel);
setAvailableThinkingLevels(data.availableThinkingLevels);
// Update routing decision if provided (M4-008)
if (data.routingDecision) {
setRoutingDecision(data.routingDecision);
}
});
socket.on('agent:start', () => {
@@ -319,6 +327,7 @@ export function useSocket(opts: UseSocketOptions): UseSocketReturn {
providerName,
thinkingLevel,
availableThinkingLevels,
routingDecision,
sendMessage,
addSystemMessage,
setThinkingLevel,