Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

225 lines
7.0 KiB
TypeScript

function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
// Keep aligned with the issue manager task-list rail dimensions.
export const AGENT_GUI_CONVERSATION_RAIL_DEFAULT_WIDTH_PX = 280;
export const AGENT_GUI_CONVERSATION_RAIL_MIN_WIDTH_PX = 248;
export const AGENT_GUI_CONVERSATION_RAIL_MAX_WIDTH_PX = 520;
export const AGENT_GUI_DETAIL_MIN_WIDTH_PX = 220;
export const AGENT_GUI_HOME_COMPOSER_MIN_WIDTH_PX = 320;
export const AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX = 10;
export const AGENT_GUI_PROVIDER_RAIL_WIDTH_PX = 52;
export const AGENT_GUI_COLLAPSED_MIN_WIDTH_PX = 460;
export const AGENT_GUI_STANDALONE_MIDDLE_CONTENT_MIN_WIDTH_PX =
AGENT_GUI_COLLAPSED_MIN_WIDTH_PX - AGENT_GUI_PROVIDER_RAIL_WIDTH_PX;
export const AGENT_GUI_EXPANDED_MIN_WIDTH_PX =
AGENT_GUI_CONVERSATION_RAIL_MIN_WIDTH_PX +
AGENT_GUI_DETAIL_MIN_WIDTH_PX +
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX;
// One package-owned responsive threshold applies to every Agent GUI surface:
// 248px conversation rail + 320px composer + 10px resize handle + 52px
// provider rail = 630px. Hosts project container width and persisted user
// preference only; they do not replace this layout policy.
export const AGENT_GUI_AUTO_COLLAPSE_WIDTH_PX =
AGENT_GUI_CONVERSATION_RAIL_MIN_WIDTH_PX +
AGENT_GUI_HOME_COMPOSER_MIN_WIDTH_PX +
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX +
AGENT_GUI_PROVIDER_RAIL_WIDTH_PX;
export const AGENT_GUI_EXPANDED_TARGET_WIDTH_PX = 800;
export interface AgentGUIConversationRailPresentation {
conversationRailWidthPx: number;
isAutoCollapsed: boolean;
isCollapsed: boolean;
}
export type AgentGUIConversationRailAutoCollapseMode =
| "default"
| "preserve-middle-content";
export interface AgentGUIExpandedWindowFrameInput {
position: { x: number; y: number };
width: number;
height: number;
desktopSize: { width: number; height: number };
conversationRailWidthPx: number | null | undefined;
}
export function shouldAutoCollapseAgentGUIConversationRail(
containerWidthPx: number,
options?: {
mode?: AgentGUIConversationRailAutoCollapseMode;
conversationRailWidthPx?: number | null;
}
): boolean {
if (
options?.mode === "preserve-middle-content" &&
Number.isFinite(containerWidthPx) &&
containerWidthPx > 0
) {
return (
containerWidthPx <
resolveStandaloneAgentGUIViewportMinimumWidthPx({
conversationRailCollapsed: false,
conversationRailWidthPx: options.conversationRailWidthPx
})
);
}
return (
Number.isFinite(containerWidthPx) &&
containerWidthPx > 0 &&
containerWidthPx <= AGENT_GUI_AUTO_COLLAPSE_WIDTH_PX
);
}
export function resolveAgentGUIConversationRailPresentation(input: {
autoCollapseMode?: AgentGUIConversationRailAutoCollapseMode;
containerWidthPx: number;
conversationRailCollapsed?: boolean | null;
conversationRailWidthPx?: number | null;
}): AgentGUIConversationRailPresentation {
const isAutoCollapsed = shouldAutoCollapseAgentGUIConversationRail(
input.containerWidthPx,
{
mode: input.autoCollapseMode,
conversationRailWidthPx: input.conversationRailWidthPx
}
);
return {
conversationRailWidthPx: clampAgentGUIConversationRailWidthPx(
input.conversationRailWidthPx,
input.containerWidthPx
),
isAutoCollapsed,
isCollapsed: input.conversationRailCollapsed === true || isAutoCollapsed
};
}
export function resolveStandaloneAgentGUIViewportMinimumWidthPx(input: {
conversationRailCollapsed?: boolean | null;
conversationRailWidthPx?: number | null;
}): number {
if (input.conversationRailCollapsed === true) {
return AGENT_GUI_COLLAPSED_MIN_WIDTH_PX;
}
return (
AGENT_GUI_PROVIDER_RAIL_WIDTH_PX +
resolvePreferredConversationRailWidthPx(input.conversationRailWidthPx) +
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX +
AGENT_GUI_STANDALONE_MIDDLE_CONTENT_MIN_WIDTH_PX
);
}
export function resolveAgentGUIConversationRailMaxWidthPx(
containerWidthPx: number
): number {
const safeContainerWidth =
Number.isFinite(containerWidthPx) && containerWidthPx > 0
? containerWidthPx
: AGENT_GUI_CONVERSATION_RAIL_DEFAULT_WIDTH_PX +
AGENT_GUI_DETAIL_MIN_WIDTH_PX +
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX;
return Math.max(
AGENT_GUI_CONVERSATION_RAIL_MIN_WIDTH_PX,
Math.min(
AGENT_GUI_CONVERSATION_RAIL_MAX_WIDTH_PX,
safeContainerWidth -
AGENT_GUI_DETAIL_MIN_WIDTH_PX -
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX
)
);
}
export function clampAgentGUIConversationRailWidthPx(
widthPx: number | null | undefined,
containerWidthPx: number
): number {
return clamp(
resolvePreferredConversationRailWidthPx(widthPx),
AGENT_GUI_CONVERSATION_RAIL_MIN_WIDTH_PX,
resolveAgentGUIConversationRailMaxWidthPx(containerWidthPx)
);
}
function resolvePreferredConversationRailWidthPx(
widthPx: number | null | undefined
): number {
const preferredWidthPx =
typeof widthPx === "number" && Number.isFinite(widthPx)
? Math.round(widthPx)
: AGENT_GUI_CONVERSATION_RAIL_DEFAULT_WIDTH_PX;
return clamp(
preferredWidthPx,
AGENT_GUI_CONVERSATION_RAIL_MIN_WIDTH_PX,
AGENT_GUI_CONVERSATION_RAIL_MAX_WIDTH_PX
);
}
export function resolveNextAgentGUIConversationRailWidthPx(input: {
currentWidthPx: number | null | undefined;
requestedWidthPx: number;
containerWidthPx: number;
}): number | null {
const nextWidthPx = clampAgentGUIConversationRailWidthPx(
input.requestedWidthPx,
input.containerWidthPx
);
if (input.currentWidthPx === nextWidthPx) {
return input.currentWidthPx;
}
if (input.currentWidthPx === null || input.currentWidthPx === undefined) {
const defaultWidthPx = clampAgentGUIConversationRailWidthPx(
null,
input.containerWidthPx
);
if (nextWidthPx === defaultWidthPx) {
return null;
}
}
return nextWidthPx;
}
export function resolveAgentGUIExpandedWindowFrame(
input: AgentGUIExpandedWindowFrameInput
): {
position: { x: number; y: number };
size: { width: number; height: number };
} {
const desiredRailWidthPx = clampAgentGUIConversationRailWidthPx(
input.conversationRailWidthPx,
AGENT_GUI_CONVERSATION_RAIL_MAX_WIDTH_PX +
AGENT_GUI_DETAIL_MIN_WIDTH_PX +
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX
);
const desiredWidthPx = Math.max(
Math.round(input.width),
desiredRailWidthPx +
AGENT_GUI_DETAIL_MIN_WIDTH_PX +
AGENT_GUI_RAIL_RESIZE_HANDLE_WIDTH_PX,
AGENT_GUI_EXPANDED_MIN_WIDTH_PX,
AGENT_GUI_EXPANDED_TARGET_WIDTH_PX
);
const desktopWidthPx =
Number.isFinite(input.desktopSize.width) && input.desktopSize.width > 0
? Math.round(input.desktopSize.width)
: desiredWidthPx;
const nextWidthPx = Math.min(desiredWidthPx, desktopWidthPx);
const maxX = Math.max(0, desktopWidthPx - nextWidthPx);
const nextX = clamp(Math.round(input.position.x), 0, maxX);
return {
position: {
x: nextX,
y: input.position.y
},
size: {
width: nextWidthPx,
height: input.height
}
};
}