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

364 lines
10 KiB
TypeScript

import type { AgentGUIAgentTarget } from "../../../types";
import type { AgentGUIConversationSummary } from "./agentGuiConversationTypes";
export const AGENT_GUI_PROVIDER_RAIL_PREFERENCES_STORAGE_KEY =
"agent-gui:provider-rail-preferences";
export const AGENT_GUI_PROVIDER_RAIL_PREFERENCES_EVENT =
"agent-gui:provider-rail-preferences-changed";
export interface AgentGUIProviderRailPreferences {
hiddenTargetIds: readonly string[];
order: readonly string[];
}
export interface AgentGUIProviderManagerDropPlacement {
overTargetId: string;
position: "before" | "after";
}
export interface AgentGUIManagedHomeTargetProjection {
agentTargets: readonly AgentGUIAgentTarget[];
selectedAgentTarget: AgentGUIAgentTarget | null;
}
const emptyAgentGUIProviderRailPreferences: AgentGUIProviderRailPreferences = {
hiddenTargetIds: [],
order: []
};
export function agentGUIProviderRailOrderStorageKey(
..._legacyWorkspaceId: readonly unknown[]
): string {
return AGENT_GUI_PROVIDER_RAIL_PREFERENCES_STORAGE_KEY;
}
export function parseAgentGUIProviderRailPreferences(
rawValue: string | null | undefined
): AgentGUIProviderRailPreferences {
if (!rawValue) {
return emptyAgentGUIProviderRailPreferences;
}
try {
const parsed = JSON.parse(rawValue) as unknown;
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return emptyAgentGUIProviderRailPreferences;
}
const candidate = parsed as {
hiddenTargetIds?: unknown;
order?: unknown;
version?: unknown;
};
if (candidate.version !== 1) {
return emptyAgentGUIProviderRailPreferences;
}
return {
hiddenTargetIds: Array.isArray(candidate.hiddenTargetIds)
? sanitizeAgentGUIProviderRailOrder(candidate.hiddenTargetIds)
: [],
order: Array.isArray(candidate.order)
? sanitizeAgentGUIProviderRailOrder(candidate.order)
: []
};
} catch {
return emptyAgentGUIProviderRailPreferences;
}
}
export function serializeAgentGUIProviderRailPreferences(
preferences: AgentGUIProviderRailPreferences
): string {
return JSON.stringify({
version: 1,
order: sanitizeAgentGUIProviderRailOrder(preferences.order),
hiddenTargetIds: sanitizeAgentGUIProviderRailOrder(
preferences.hiddenTargetIds
)
});
}
export function parseAgentGUIProviderRailOrder(
rawValue: string | null | undefined
): readonly string[] {
if (!rawValue) {
return [];
}
try {
const parsed = JSON.parse(rawValue);
if (!Array.isArray(parsed)) {
return [];
}
return sanitizeAgentGUIProviderRailOrder(parsed);
} catch {
return [];
}
}
export function serializeAgentGUIProviderRailOrder(
order: readonly string[]
): string {
return JSON.stringify(sanitizeAgentGUIProviderRailOrder(order));
}
export function sanitizeAgentGUIProviderRailOrder(
order: readonly unknown[]
): readonly string[] {
const seen = new Set<string>();
const sanitized: string[] = [];
for (const value of order) {
if (typeof value !== "string") {
continue;
}
const targetId = value.trim();
if (!targetId || seen.has(targetId)) {
continue;
}
seen.add(targetId);
sanitized.push(targetId);
}
return sanitized;
}
export function applyAgentGUIProviderRailOrder<
T extends Pick<AgentGUIAgentTarget, "targetId">
>(targets: readonly T[], order: readonly string[]): readonly T[] {
const knownOrder = sanitizeAgentGUIProviderRailOrder(order);
if (knownOrder.length === 0 || targets.length <= 1) {
return targets;
}
const indexByTargetId = new Map<string, number>();
knownOrder.forEach((targetId, index) => {
indexByTargetId.set(targetId, index);
});
return [...targets].sort((left, right) => {
const leftIndex = indexByTargetId.get(left.targetId);
const rightIndex = indexByTargetId.get(right.targetId);
if (leftIndex === undefined && rightIndex === undefined) {
return 0;
}
if (leftIndex === undefined) {
return 1;
}
if (rightIndex === undefined) {
return -1;
}
return leftIndex - rightIndex;
});
}
export function applyAgentGUIProviderRailVisibility<
T extends Pick<AgentGUIAgentTarget, "targetId">
>(targets: readonly T[], hiddenTargetIds: readonly string[]): readonly T[] {
const hidden = new Set(
normalizeAgentGUIProviderRailHiddenTargetIds(
targets.map((target) => target.targetId),
hiddenTargetIds
)
);
if (hidden.size === 0) {
return targets;
}
return targets.filter((target) => !hidden.has(target.targetId));
}
export function agentGUIRunningTargetIds(input: {
activeConversation?: AgentGUIConversationSummary | null;
agentTargets: readonly AgentGUIAgentTarget[];
conversations: readonly AgentGUIConversationSummary[];
}): readonly string[] {
const runningTargetIds = new Set<string>();
const seenConversationIds = new Set<string>();
for (const conversation of [
...input.conversations,
...(input.activeConversation ? [input.activeConversation] : [])
]) {
if (
seenConversationIds.has(conversation.id) ||
(conversation.status !== "working" && conversation.status !== "waiting")
) {
continue;
}
seenConversationIds.add(conversation.id);
const agentTargetId = conversation.agentTargetId?.trim() ?? "";
for (const target of input.agentTargets) {
const targetMatches = agentTargetId
? target.targetId === agentTargetId ||
target.agentTargetId === agentTargetId
: target.provider === conversation.provider;
if (targetMatches) {
runningTargetIds.add(target.targetId);
}
}
}
return [...runningTargetIds];
}
export function projectAgentGUIManagedHomeTargets(input: {
agentTargets: readonly AgentGUIAgentTarget[];
preferences: AgentGUIProviderRailPreferences;
selectedAgentTarget: AgentGUIAgentTarget | null;
}): AgentGUIManagedHomeTargetProjection {
const agentTargets = applyAgentGUIProviderRailVisibility(
applyAgentGUIProviderRailOrder(input.agentTargets, input.preferences.order),
input.preferences.hiddenTargetIds
);
const selectedAgentTarget = input.selectedAgentTarget;
if (!selectedAgentTarget) {
return { agentTargets, selectedAgentTarget: null };
}
const selectedTargetIds = new Set(
[selectedAgentTarget.targetId, selectedAgentTarget.agentTargetId]
.map((targetId) => targetId?.trim() ?? "")
.filter(Boolean)
);
if (
!input.preferences.hiddenTargetIds.some((targetId) =>
selectedTargetIds.has(targetId)
)
) {
return { agentTargets, selectedAgentTarget };
}
return {
agentTargets,
selectedAgentTarget:
agentTargets.find(
(target) =>
selectedTargetIds.has(target.targetId) ||
(target.agentTargetId
? selectedTargetIds.has(target.agentTargetId)
: false)
) ??
agentTargets.find((target) => target.disabled !== true) ??
agentTargets[0] ??
selectedAgentTarget
};
}
export function normalizeAgentGUIProviderRailHiddenTargetIds(
currentTargetIds: readonly string[],
hiddenTargetIds: readonly string[]
): readonly string[] {
const current = sanitizeAgentGUIProviderRailOrder(currentTargetIds);
const hidden = sanitizeAgentGUIProviderRailOrder(hiddenTargetIds);
if (
current.length === 0 ||
current.some((targetId) => !hidden.includes(targetId))
) {
return hiddenTargetIds;
}
return hidden.filter((targetId) => targetId !== current[0]);
}
export function reorderAgentGUIProviderRailOrder(input: {
currentTargetIds: readonly string[];
draggedTargetId: string;
dropPosition: "before" | "after";
overTargetId: string;
}): readonly string[] {
const currentTargetIds = sanitizeAgentGUIProviderRailOrder(
input.currentTargetIds
);
const draggedTargetId = input.draggedTargetId.trim();
const overTargetId = input.overTargetId.trim();
if (
!draggedTargetId ||
!overTargetId ||
draggedTargetId === overTargetId ||
!currentTargetIds.includes(draggedTargetId) ||
!currentTargetIds.includes(overTargetId)
) {
return currentTargetIds;
}
const withoutDragged = currentTargetIds.filter(
(targetId) => targetId !== draggedTargetId
);
const overIndex = withoutDragged.indexOf(overTargetId);
if (overIndex < 0) {
return currentTargetIds;
}
const insertIndex =
input.dropPosition === "after" ? overIndex + 1 : overIndex;
return [
...withoutDragged.slice(0, insertIndex),
draggedTargetId,
...withoutDragged.slice(insertIndex)
];
}
export function changeAgentGUIProviderManagerVisibility(input: {
currentTargetIds: readonly string[];
placement?: AgentGUIProviderManagerDropPlacement;
preferences: AgentGUIProviderRailPreferences;
runningTargetIds?: readonly string[];
targetId: string;
visible: boolean;
}): AgentGUIProviderRailPreferences {
const currentTargetIds = sanitizeAgentGUIProviderRailOrder(
input.currentTargetIds
);
const targetId = input.targetId.trim();
if (!targetId || !currentTargetIds.includes(targetId)) {
return input.preferences;
}
if (
!input.visible &&
sanitizeAgentGUIProviderRailOrder(input.runningTargetIds ?? []).includes(
targetId
)
) {
return input.preferences;
}
const hiddenTargetIds = new Set(
normalizeAgentGUIProviderRailHiddenTargetIds(
currentTargetIds,
input.preferences.hiddenTargetIds
)
);
const availableTargetCount = currentTargetIds.filter(
(candidateId) => !hiddenTargetIds.has(candidateId)
).length;
if (
!input.visible &&
!hiddenTargetIds.has(targetId) &&
availableTargetCount <= 1
) {
return input.preferences;
}
let order = input.preferences.order;
const fallbackTargetId = currentTargetIds
.filter(
(candidateId) =>
candidateId !== targetId &&
(input.visible
? !hiddenTargetIds.has(candidateId)
: hiddenTargetIds.has(candidateId))
)
.at(-1);
const placement =
input.placement ??
(fallbackTargetId
? { overTargetId: fallbackTargetId, position: "after" as const }
: null);
if (placement) {
order = reorderAgentGUIProviderRailOrder({
currentTargetIds,
draggedTargetId: targetId,
dropPosition: placement.position,
overTargetId: placement.overTargetId
});
}
if (input.visible) {
hiddenTargetIds.delete(targetId);
} else {
hiddenTargetIds.add(targetId);
}
return {
...input.preferences,
hiddenTargetIds: [...hiddenTargetIds],
order
};
}