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

396 lines
11 KiB
TypeScript

export const agentToolPanelIds = [
"files",
"terminal",
"browser",
"tasks",
"apps",
"messages",
"side"
] as const;
export type AgentToolPanelId = (typeof agentToolPanelIds)[number];
export interface AgentToolPanelDefinition {
canAdd?: boolean;
id: AgentToolPanelId;
label: string;
}
export interface AgentToolTab {
id: string;
panel: AgentToolPanelId;
resourceId?: string;
}
const supportedPanelIds = new Set<string>(agentToolPanelIds);
export function isAgentToolPanelId(value: string): value is AgentToolPanelId {
return supportedPanelIds.has(value);
}
export function filterAgentToolPanels(
panels: readonly { canAdd?: boolean; id: string; label: string }[]
): AgentToolPanelDefinition[] {
const seen = new Set<AgentToolPanelId>();
const filtered: AgentToolPanelDefinition[] = [];
for (const panel of panels) {
if (!isAgentToolPanelId(panel.id) || seen.has(panel.id)) {
continue;
}
seen.add(panel.id);
filtered.push({
id: panel.id,
label: panel.label,
...(panel.canAdd === false ? { canAdd: false } : {})
});
}
return filtered;
}
export function filterAgentToolAddablePanels(
panels: readonly AgentToolPanelDefinition[]
): AgentToolPanelDefinition[] {
return panels.filter((panel) => panel.canAdd !== false);
}
const adjacentPanelDefaultWidth = 720;
export const agentToolEmptySidebarWidth = Math.round(
adjacentPanelDefaultWidth * 0.6
);
export const agentToolMainMinWidth = 280;
export const agentToolPanelDefaultWidthById: Record<AgentToolPanelId, number> =
{
apps: adjacentPanelDefaultWidth,
browser: adjacentPanelDefaultWidth,
files: adjacentPanelDefaultWidth,
messages: adjacentPanelDefaultWidth,
side: 440,
tasks: adjacentPanelDefaultWidth,
terminal: adjacentPanelDefaultWidth
};
export const agentToolPanelMinWidthById: Record<AgentToolPanelId, number> = {
apps: 420,
browser: 420,
files: 480,
messages: 320,
side: 360,
tasks: 420,
terminal: 420
};
export const agentToolPanelMaxWidthById: Record<AgentToolPanelId, number> = {
apps: 1_200,
browser: 1_200,
files: Number.MAX_SAFE_INTEGER,
messages: 1_200,
side: 600,
tasks: 1_200,
terminal: 1_200
};
export interface AgentToolSidebarState {
activePanel: AgentToolPanelId | null;
activeTabId: string | null;
mountedTabs: AgentToolTab[];
}
export type AgentToolSidebarAction =
| {
panel: AgentToolPanelId;
resourceId?: string;
tabId: string;
type: "open-panel" | "add-panel" | "ensure-panel";
}
| { tabId: string; type: "activate-tab" | "close-tab" }
| { type: "close" };
export function createAgentToolSidebarState(
initial?: Partial<AgentToolSidebarState>
): AgentToolSidebarState {
const activePanel = initial?.activePanel ?? null;
const mountedTabs = initial?.mountedTabs ?? [];
const activeTabId =
initial?.activeTabId ??
(activePanel === null
? null
: (findLastTab(mountedTabs, activePanel)?.id ?? null));
return {
activePanel: activeTabId === null ? null : activePanel,
activeTabId,
mountedTabs
};
}
export function reduceAgentToolSidebarState(
state: AgentToolSidebarState,
action: AgentToolSidebarAction
): AgentToolSidebarState {
switch (action.type) {
case "close":
return state.activeTabId === null
? state
: { ...state, activePanel: null, activeTabId: null };
case "close-tab": {
const closingIndex = state.mountedTabs.findIndex(
(tab) => tab.id === action.tabId
);
if (closingIndex < 0) {
return state;
}
const mountedTabs = state.mountedTabs.filter(
(tab) => tab.id !== action.tabId
);
if (state.activeTabId !== action.tabId) {
return { ...state, mountedTabs };
}
const nextTab =
mountedTabs[Math.max(0, closingIndex - 1)] ?? mountedTabs[0] ?? null;
return {
activePanel: nextTab?.panel ?? null,
activeTabId: nextTab?.id ?? null,
mountedTabs
};
}
case "activate-tab": {
const tab = state.mountedTabs.find(
(candidate) => candidate.id === action.tabId
);
return tab
? { ...state, activePanel: tab.panel, activeTabId: tab.id }
: state;
}
case "add-panel":
return addTab(state, action);
case "ensure-panel": {
const existing = findLastTab(
state.mountedTabs,
action.panel,
action.resourceId
);
return existing ? state : addTab(state, action, { activate: false });
}
case "open-panel": {
const existing = findLastTab(
state.mountedTabs,
action.panel,
action.resourceId
);
return existing
? { ...state, activePanel: existing.panel, activeTabId: existing.id }
: addTab(state, action);
}
}
}
function addTab(
state: AgentToolSidebarState,
tab: {
panel: AgentToolPanelId;
resourceId?: string;
tabId: string;
},
options: { activate?: boolean } = {}
): AgentToolSidebarState {
const nextTab: AgentToolTab = {
id: tab.tabId,
panel: tab.panel,
...(tab.resourceId ? { resourceId: tab.resourceId } : {})
};
const activate = options.activate !== false;
return {
activePanel: activate ? nextTab.panel : state.activePanel,
activeTabId: activate ? nextTab.id : state.activeTabId,
mountedTabs: state.mountedTabs.some(
(candidate) => candidate.id === nextTab.id
)
? state.mountedTabs
: [...state.mountedTabs, nextTab]
};
}
function findLastTab(
tabs: readonly AgentToolTab[],
panel: AgentToolPanelId,
resourceId?: string
): AgentToolTab | null {
for (let index = tabs.length - 1; index >= 0; index -= 1) {
const tab = tabs[index];
if (tab?.panel === panel && (tab.resourceId ?? undefined) === resourceId) {
return tab;
}
}
return null;
}
export function resolveAgentToolPanelExpansionReset(input: {
expandedPanel: AgentToolPanelId | null;
nextPanel: AgentToolPanelId | null;
widthBeforeExpansion?: number;
}): { panel: AgentToolPanelId; width: number } | null {
if (input.expandedPanel === null || input.expandedPanel === input.nextPanel) {
return null;
}
return {
panel: input.expandedPanel,
width:
typeof input.widthBeforeExpansion === "number" &&
Number.isFinite(input.widthBeforeExpansion)
? input.widthBeforeExpansion
: agentToolPanelDefaultWidthById[input.expandedPanel]
};
}
export function resolveAgentToolPanelExpansionTransfer(input: {
expandedPanel: AgentToolPanelId | null;
nextPanel: AgentToolPanelId | null;
nextPanelWidth: number;
widthBeforeExpansion?: number;
}): {
expandedPanel: AgentToolPanelId;
nextPanelWidthBeforeExpansion: number;
previousPanel: AgentToolPanelId;
previousPanelWidth: number;
} | null {
const reset = resolveAgentToolPanelExpansionReset(input);
if (!reset || input.nextPanel === null) {
return null;
}
return {
expandedPanel: input.nextPanel,
nextPanelWidthBeforeExpansion:
Number.isFinite(input.nextPanelWidth) && input.nextPanelWidth > 0
? input.nextPanelWidth
: agentToolPanelDefaultWidthById[input.nextPanel],
previousPanel: reset.panel,
previousPanelWidth: reset.width
};
}
export function resolveAgentToolPanelMaxWidth(
panel: AgentToolPanelId,
viewportWidth: number,
allowFullWidth = false,
mainContentMinWidth = agentToolMainMinWidth
): number {
const resolvedMainMin = Number.isFinite(mainContentMinWidth)
? Math.max(0, mainContentMinWidth)
: agentToolMainMinWidth;
const resolvedViewport = Number.isFinite(viewportWidth)
? viewportWidth
: resolvedMainMin + agentToolPanelMinWidthById[panel];
return Math.max(
0,
Math.min(
allowFullWidth
? Number.MAX_SAFE_INTEGER
: agentToolPanelMaxWidthById[panel],
resolvedViewport - resolvedMainMin
)
);
}
export function clampAgentToolPanelWidth(input: {
allowFullWidth?: boolean;
mainContentMinWidth?: number;
panel: AgentToolPanelId;
viewportWidth: number;
width: number;
}): number {
const maxWidth = resolveAgentToolPanelMaxWidth(
input.panel,
input.viewportWidth,
input.allowFullWidth,
input.mainContentMinWidth
);
const minWidth = Math.min(agentToolPanelMinWidthById[input.panel], maxWidth);
const width = Number.isFinite(input.width)
? input.width
: agentToolPanelDefaultWidthById[input.panel];
return Math.round(Math.min(maxWidth, Math.max(minWidth, width)));
}
export function resolveAgentToolSidebarWidth(input: {
allowFullWidth?: boolean;
baselineViewportWidth: number;
mainContentMinWidth?: number;
panel: AgentToolPanelId;
preferredWidth: number;
viewportWidth: number;
}): number {
const baseline = Number.isFinite(input.baselineViewportWidth)
? Math.max(0, input.baselineViewportWidth)
: input.viewportWidth;
return clampAgentToolPanelWidth({
allowFullWidth: input.allowFullWidth,
mainContentMinWidth: input.mainContentMinWidth,
panel: input.panel,
viewportWidth: input.viewportWidth,
width: Math.max(input.preferredWidth, input.viewportWidth - baseline)
});
}
export function resolveAgentToolSidebarLayoutWidth(input: {
baselineContainerWidth: number;
panelWidth: number;
containerWidth: number;
}): number {
return Math.round(Math.max(0, input.panelWidth));
}
export function resolveAgentToolPanelPreferredWidth(input: {
isExpanded: boolean;
manuallyResizedWidth?: number | null;
panelWidth: number;
}): number {
return !input.isExpanded &&
typeof input.manuallyResizedWidth === "number" &&
Number.isFinite(input.manuallyResizedWidth)
? input.manuallyResizedWidth
: input.panelWidth;
}
export function shouldResizeAgentToolContainer(input: {
currentWidth: number;
lastResize?: { actualWidth: number; requestedWidth: number } | null;
requestedWidth: number;
}): boolean {
return (
input.currentWidth !== input.requestedWidth &&
!(
input.lastResize?.requestedWidth === input.requestedWidth &&
input.lastResize.actualWidth === input.currentWidth
)
);
}
export function shouldAutoCollapseAgentToolSidebar(input: {
containerWidth: number;
mainContentMinWidth: number;
sidebarWidth: number;
}): boolean {
if (
!Number.isFinite(input.containerWidth) ||
input.containerWidth <= 0 ||
!Number.isFinite(input.mainContentMinWidth) ||
!Number.isFinite(input.sidebarWidth)
) {
return false;
}
return (
input.containerWidth <
Math.max(0, input.mainContentMinWidth) + Math.max(0, input.sidebarWidth)
);
}
export function formatAgentToolReminderCount(
value: number | null | undefined
): string | null {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
return null;
}
const count = Math.floor(value);
return count > 99 ? "99+" : String(count);
}