mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-12 20:57:42 +02:00
[aidan] ui: tool calling desc/naming
This commit is contained in:
@@ -63,6 +63,7 @@ import { ContextPath } from '@/app/components/editor/DirectoryBrowser';
|
||||
import { setGlowingBrowserCards, fadeGlowingBrowserCards, clearGlowingBrowserCards, removeCard } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { setCardSidecar, commitDraft, updateWorkflowCard } from '@/shared/state/workflowsSlice';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import { parseMcpToolName, getMcpInputSummary } from '@/shared/mcpToolMeta';
|
||||
|
||||
const CONTEXT_WINDOWS: Record<string, number> = {
|
||||
'opus-4-8': 1_000_000,
|
||||
@@ -1285,7 +1286,9 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
const c = p.call.content;
|
||||
const tool = typeof c === 'object' ? c.tool || '' : '';
|
||||
const input = typeof c === 'object' ? c.input : '';
|
||||
const summary = typeof input === 'string' ? input.slice(0, 120) : JSON.stringify(input).slice(0, 120);
|
||||
const mcp = parseMcpToolName(tool);
|
||||
const friendly = mcp.isMcp ? getMcpInputSummary(input, mcp.action, mcp.serverSlug) : '';
|
||||
const summary = friendly || (typeof input === 'string' ? input.slice(0, 120) : JSON.stringify(input).slice(0, 120));
|
||||
return { tool, input_summary: summary };
|
||||
});
|
||||
dispatch(generateGroupMeta({ sessionId: id, groupId: group.id, toolCalls }));
|
||||
@@ -1297,7 +1300,9 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
const c = p.call.content;
|
||||
const tool = typeof c === 'object' ? c.tool || '' : '';
|
||||
const input = typeof c === 'object' ? c.input : '';
|
||||
const summary = typeof input === 'string' ? input.slice(0, 120) : JSON.stringify(input).slice(0, 120);
|
||||
const mcp = parseMcpToolName(tool);
|
||||
const friendly = mcp.isMcp ? getMcpInputSummary(input, mcp.action, mcp.serverSlug) : '';
|
||||
const summary = friendly || (typeof input === 'string' ? input.slice(0, 120) : JSON.stringify(input).slice(0, 120));
|
||||
return { tool, input_summary: summary };
|
||||
});
|
||||
const resultsSummary = group.pairs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AgentMessage } from '@/shared/state/agentsSlice';
|
||||
import { prettyPath, prettyUrl, quoteQuery, bashCommandDetail } from './toolLabels';
|
||||
import { parseMcpToolName, getMcpInputSummary, getGmailHeader } from '@/shared/mcpToolMeta';
|
||||
import { parseMcpToolName, getMcpInputSummary, getGmailHeader, getWorkflowToolInputDisplay } from '@/shared/mcpToolMeta';
|
||||
|
||||
export function getToolData(call: AgentMessage) {
|
||||
const content = typeof call.content === 'object' ? call.content : {};
|
||||
@@ -19,7 +19,7 @@ export function isBashTool(name: string) {
|
||||
export function getInputSummary(toolName: string, input: any): string {
|
||||
try {
|
||||
const mcp = parseMcpToolName(toolName);
|
||||
if (mcp.isMcp) return getMcpInputSummary(input);
|
||||
if (mcp.isMcp) return getMcpInputSummary(input, mcp.action, mcp.serverSlug);
|
||||
|
||||
const n = toolName.toLowerCase();
|
||||
if (isBashTool(toolName)) {
|
||||
@@ -61,7 +61,10 @@ function formatMcpInputDisplay(input: any): string {
|
||||
export function formatInputDisplay(toolName: string, input: any): string {
|
||||
try {
|
||||
const mcp = parseMcpToolName(toolName);
|
||||
if (mcp.isMcp) return formatMcpInputDisplay(input);
|
||||
if (mcp.isMcp) {
|
||||
const workflowDisplay = getWorkflowToolInputDisplay(input, mcp.action, mcp.serverSlug);
|
||||
return workflowDisplay || formatMcpInputDisplay(input);
|
||||
}
|
||||
|
||||
const n = toolName.toLowerCase();
|
||||
if (isBashTool(toolName)) return input.command || '';
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ApprovalRequest } from '@/shared/state/agentsSlice';
|
||||
import { useAppSelector } from '@/shared/hooks';
|
||||
import { ToolDefinition } from '@/shared/state/toolsSlice';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import { getMcpInputSummary as getSharedMcpInputSummary, getWorkflowToolInputDisplay, getWorkflowToolLabel } from '@/shared/mcpToolMeta';
|
||||
|
||||
interface IntegrationMeta {
|
||||
label: string;
|
||||
@@ -96,6 +97,9 @@ export function useMcpToolMeta(parsed: ParsedTool): McpToolMeta {
|
||||
);
|
||||
|
||||
if (!toolDef) {
|
||||
if (parsed.serverSlug === 'openswarm-schedule') {
|
||||
return { integration: null, description: '', serverLabel: 'Workflows' };
|
||||
}
|
||||
return { integration: null, description: '', serverLabel: parsed.serverSlug };
|
||||
}
|
||||
|
||||
@@ -110,6 +114,10 @@ export function useMcpToolMeta(parsed: ParsedTool): McpToolMeta {
|
||||
function getMcpInputSummary(actionName: string, toolInput: Record<string, any>): string {
|
||||
const lower = actionName.toLowerCase();
|
||||
|
||||
if (getWorkflowToolLabel(actionName)) {
|
||||
return getSharedMcpInputSummary(toolInput, actionName, 'openswarm-schedule');
|
||||
}
|
||||
|
||||
if (lower.includes('gmail') || lower.includes('email') || lower.includes('mail')) {
|
||||
const query = toolInput.query || toolInput.search_query || toolInput.q || '';
|
||||
const to = toolInput.to || toolInput.recipient || '';
|
||||
@@ -569,7 +577,12 @@ const GenericApprovalBar: React.FC<Props> = ({ request, onApprove, onDeny }) =>
|
||||
const meta = useMcpToolMeta(parsed);
|
||||
|
||||
const accentColor = meta.integration?.color || c.status.warning;
|
||||
const displayName = getWorkflowToolLabel(parsed.actionName) || parsed.displayName;
|
||||
const summary = parsed.isMcp ? getMcpInputSummary(parsed.actionName, request.tool_input) : '';
|
||||
const workflowDetails = parsed.isMcp
|
||||
? getWorkflowToolInputDisplay(request.tool_input, parsed.actionName, parsed.serverSlug)
|
||||
: '';
|
||||
const detailText = workflowDetails || JSON.stringify(request.tool_input, null, 2);
|
||||
const isSensitive = !!request.sensitive_pattern;
|
||||
|
||||
if (!parsed.isMcp) {
|
||||
@@ -744,7 +757,7 @@ const GenericApprovalBar: React.FC<Props> = ({ request, onApprove, onDeny }) =>
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '0.9rem' }}>
|
||||
{parsed.displayName}
|
||||
{displayName}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={meta.serverLabel || parsed.serverSlug}
|
||||
@@ -794,7 +807,6 @@ const GenericApprovalBar: React.FC<Props> = ({ request, onApprove, onDeny }) =>
|
||||
sx={{
|
||||
color: c.text.secondary,
|
||||
fontSize: '0.82rem',
|
||||
fontFamily: c.font.mono,
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
overflow: 'hidden',
|
||||
@@ -812,7 +824,7 @@ const GenericApprovalBar: React.FC<Props> = ({ request, onApprove, onDeny }) =>
|
||||
<Collapse in={detailsExpanded || !summary}>
|
||||
<Box sx={{ mt: summary ? 0.75 : 0 }}>
|
||||
<CodeBlock tokens={c}>
|
||||
{JSON.stringify(request.tool_input, null, 2)}
|
||||
{detailText}
|
||||
</CodeBlock>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { GoogleServiceIcon } from '../mcp-cards/GoogleServiceIcon';
|
||||
import { ElapsedTimer, formatElapsed } from '../parsing/toolBubbleChrome';
|
||||
import { useTermColors } from '../parsing/toolColorize';
|
||||
import { ParsedResult } from '../parsing/toolResultParsing';
|
||||
import { McpToolInfo, getMcpShortAction } from '@/shared/mcpToolMeta';
|
||||
import { McpToolInfo, getMcpShortAction, getMcpInputSummary, getWorkflowToolLabel } from '@/shared/mcpToolMeta';
|
||||
import { McpResultCard } from '../mcp-cards/McpResultCard';
|
||||
|
||||
interface CompactMcpBubbleProps {
|
||||
@@ -47,12 +47,17 @@ export const CompactMcpBubble: React.FC<CompactMcpBubbleProps> = ({
|
||||
const c = useClaudeTokens();
|
||||
const tc = useTermColors();
|
||||
|
||||
const shortAction = mcpInfo.isMcp ? getMcpShortAction(mcpInfo) : toolName;
|
||||
const workflowLabel = mcpInfo.isMcp ? getWorkflowToolLabel(mcpInfo.action) : null;
|
||||
const shortAction = workflowLabel || (mcpInfo.isMcp ? getMcpShortAction(mcpInfo) : toolName);
|
||||
const mcpVerbLabel = (() => {
|
||||
if (workflowLabel) return workflowLabel;
|
||||
const lbl = getToolLabel(toolName, call.id);
|
||||
return result && !isDenied ? lbl.past : lbl.present;
|
||||
})();
|
||||
const serviceLabel = mcpInfo.isMcp ? mcpVerbLabel : shortAction;
|
||||
const inputSummary = mcpInfo.isMcp ? getMcpInputSummary(input, mcpInfo.action, mcpInfo.serverSlug) : '';
|
||||
const visibleSummary = resultSummary || inputSummary;
|
||||
const canToggleDetails = !!visibleSummary;
|
||||
const ServiceIcon = mcpInfo.isMcp && mcpInfo.service
|
||||
? <GoogleServiceIcon service={mcpInfo.service} size={14} />
|
||||
: null;
|
||||
@@ -60,16 +65,16 @@ export const CompactMcpBubble: React.FC<CompactMcpBubbleProps> = ({
|
||||
return (
|
||||
<Box {...selectAttrs} sx={{ my: 0 }}>
|
||||
<Box
|
||||
onClick={toggle}
|
||||
onClick={canToggleDetails ? toggle : undefined}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: showBody ? 'flex-start' : 'center',
|
||||
alignItems: showBody && canToggleDetails ? 'flex-start' : 'center',
|
||||
gap: 0.75,
|
||||
px: 1.5,
|
||||
py: 0.6,
|
||||
cursor: 'pointer',
|
||||
borderBottom: showBody ? `1px solid ${c.border.subtle}` : 'none',
|
||||
'&:hover': { bgcolor: 'rgba(0,0,0,0.02)' },
|
||||
cursor: canToggleDetails ? 'pointer' : 'default',
|
||||
borderBottom: showBody && canToggleDetails ? `1px solid ${c.border.subtle}` : 'none',
|
||||
'&:hover': canToggleDetails ? { bgcolor: 'rgba(0,0,0,0.02)' } : undefined,
|
||||
}}
|
||||
>
|
||||
{ServiceIcon}
|
||||
@@ -83,22 +88,22 @@ export const CompactMcpBubble: React.FC<CompactMcpBubbleProps> = ({
|
||||
>
|
||||
{serviceLabel}
|
||||
</Typography>
|
||||
{resultSummary && !isError && (
|
||||
{visibleSummary && !isError && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: c.text.secondary,
|
||||
fontSize: '0.74rem',
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
...(showBody
|
||||
...(showBody && canToggleDetails
|
||||
? { whiteSpace: 'normal', wordBreak: 'break-word' }
|
||||
: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }),
|
||||
}}
|
||||
>
|
||||
{resultSummary}
|
||||
{visibleSummary}
|
||||
</Typography>
|
||||
)}
|
||||
{!resultSummary && !showTimer && <Box sx={{ flex: 1 }} />}
|
||||
{!visibleSummary && !showTimer && <Box sx={{ flex: 1 }} />}
|
||||
{showTimer && (
|
||||
<>
|
||||
<Box sx={{ flex: 1 }} />
|
||||
@@ -123,12 +128,14 @@ export const CompactMcpBubble: React.FC<CompactMcpBubbleProps> = ({
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{canToggleDetails && (
|
||||
<IconButton size="small" sx={{ color: c.text.tertiary, p: 0.15, flexShrink: 0 }}>
|
||||
{showBody ? <ExpandLessIcon sx={{ fontSize: 16 }} /> : <ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Collapse in={showBody}>
|
||||
<Collapse in={showBody && canToggleDetails}>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: tc.TERM_BG,
|
||||
|
||||
@@ -61,6 +61,7 @@ export const DefaultToolBubble: React.FC<DefaultToolBubbleProps> = ({
|
||||
// already on screen. mcpCompact rows opt out (the group's row-fade handles them).
|
||||
const reveal = useMountReveal();
|
||||
const enterStyle = (!mcpCompact && !suppressReveal) ? reveal : {};
|
||||
const canToggleDetails = !!inputSummary && !isStreaming;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -89,16 +90,16 @@ export const DefaultToolBubble: React.FC<DefaultToolBubbleProps> = ({
|
||||
} as any}
|
||||
>
|
||||
<Box
|
||||
onClick={toggle}
|
||||
onClick={canToggleDetails ? toggle : undefined}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.75,
|
||||
px: 1.5,
|
||||
py: mcpCompact ? 0.6 : 0.75,
|
||||
cursor: isStreaming ? 'default' : 'pointer',
|
||||
borderBottom: mcpCompact && showBody ? `1px solid ${c.border.subtle}` : 'none',
|
||||
'&:hover': isStreaming ? {} : { bgcolor: 'rgba(0,0,0,0.02)' },
|
||||
cursor: canToggleDetails ? 'pointer' : 'default',
|
||||
borderBottom: mcpCompact && showBody && canToggleDetails ? `1px solid ${c.border.subtle}` : 'none',
|
||||
'&:hover': canToggleDetails ? { bgcolor: 'rgba(0,0,0,0.02)' } : {},
|
||||
}}
|
||||
>
|
||||
{mcpInfo.isMcp && mcpInfo.service
|
||||
@@ -186,7 +187,7 @@ export const DefaultToolBubble: React.FC<DefaultToolBubbleProps> = ({
|
||||
)}
|
||||
{showTimer && <ElapsedTimer startTime={call.timestamp} />}
|
||||
|
||||
{!isStreaming && (
|
||||
{canToggleDetails && (
|
||||
<IconButton size="small" sx={{ color: c.text.tertiary, p: mcpCompact ? 0.15 : 0.25, flexShrink: 0 }}>
|
||||
{showBody ? (
|
||||
<ExpandLessIcon sx={{ fontSize: mcpCompact ? 16 : 18 }} />
|
||||
@@ -197,7 +198,7 @@ export const DefaultToolBubble: React.FC<DefaultToolBubbleProps> = ({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Collapse in={showBody}>
|
||||
<Collapse in={showBody && canToggleDetails}>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: tc.TERM_BG,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AgentMessage, ToolGroupMeta } from '@/shared/state/agentsSlice';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import { useMountReveal } from './useMountReveal';
|
||||
import { sanitizeSvgString } from '@/shared/sanitizeSvg';
|
||||
import { parseMcpToolName, getWorkflowToolLabel } from '@/shared/mcpToolMeta';
|
||||
import ToolCallBubble, { ToolPair } from './ToolCallBubble';
|
||||
|
||||
export interface ToolGroup {
|
||||
@@ -83,13 +84,21 @@ const ToolGroupBubble: React.FC<Props> = React.memo(({ group, isSessionRunning =
|
||||
).length;
|
||||
const allDone = pendingCount === 0 || !isSessionRunning;
|
||||
|
||||
const displayName = meta?.name || group.label;
|
||||
const hasSvg = !!meta?.svg;
|
||||
|
||||
const toolNames = group.pairs.map((p) => {
|
||||
const c2 = typeof p.call.content === 'object' ? p.call.content : {};
|
||||
return c2.tool || 'unknown';
|
||||
});
|
||||
const workflowGroupLabel = (() => {
|
||||
if (group.mcpServer !== 'openswarm-schedule') return null;
|
||||
const parsedLabels = Array.from(new Set(toolNames.map((name) => {
|
||||
const parsed = parseMcpToolName(name);
|
||||
return parsed.isMcp ? getWorkflowToolLabel(parsed.action) : null;
|
||||
}).filter(Boolean))) as string[];
|
||||
return parsedLabels.length === 1 ? parsedLabels[0] : 'Workflow actions';
|
||||
})();
|
||||
const displayName = workflowGroupLabel || meta?.name || group.label;
|
||||
const hasSvg = !!meta?.svg && !workflowGroupLabel;
|
||||
const canToggleGroup = group.pairs.length > 1;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -116,15 +125,15 @@ const ToolGroupBubble: React.FC<Props> = React.memo(({ group, isSessionRunning =
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
onClick={canToggleGroup ? () => setExpanded(!expanded) : undefined}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.75,
|
||||
px: 1.5,
|
||||
py: 0.7,
|
||||
cursor: 'pointer',
|
||||
'&:hover': { bgcolor: 'rgba(0,0,0,0.02)' },
|
||||
cursor: canToggleGroup ? 'pointer' : 'default',
|
||||
'&:hover': canToggleGroup ? { bgcolor: 'rgba(0,0,0,0.02)' } : undefined,
|
||||
}}
|
||||
>
|
||||
{!meta ? (
|
||||
@@ -171,9 +180,11 @@ const ToolGroupBubble: React.FC<Props> = React.memo(({ group, isSessionRunning =
|
||||
{completedCount}/{group.callCount}
|
||||
</Typography>
|
||||
)}
|
||||
{canToggleGroup && (
|
||||
<IconButton size="small" sx={{ color: c.text.tertiary, p: 0.15 }}>
|
||||
{expanded ? <ExpandLessIcon sx={{ fontSize: 16 }} /> : <ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Collapse in={expanded}>
|
||||
|
||||
@@ -26,8 +26,149 @@ export function parseMcpToolName(rawName: string): McpToolInfo {
|
||||
return { isMcp: true, serverSlug, action, service, displayName: display };
|
||||
}
|
||||
|
||||
export function getMcpInputSummary(input: any): string {
|
||||
function formatTime(hour: unknown, minute: unknown): string {
|
||||
const h = typeof hour === 'number' ? hour : Number(hour);
|
||||
const m = typeof minute === 'number' ? minute : Number(minute || 0);
|
||||
if (!Number.isFinite(h)) return '';
|
||||
const h12 = ((h + 11) % 12) + 1;
|
||||
const suffix = h < 12 ? 'am' : 'pm';
|
||||
return Number.isFinite(m) && m > 0 ? `${h12}:${String(m).padStart(2, '0')}${suffix}` : `${h12}${suffix}`;
|
||||
}
|
||||
|
||||
function weekdayLabel(day: number): string {
|
||||
return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][day] || '';
|
||||
}
|
||||
|
||||
function compactWorkflowSchedule(input: any): string {
|
||||
if (!input || typeof input !== 'object') return '';
|
||||
const enabled = input.schedule_enabled ?? input.enabled;
|
||||
if (enabled === false) return 'Turn schedule off';
|
||||
|
||||
const unit = input.repeat_unit || input.unit;
|
||||
const every = Math.max(1, Number(input.repeat_every || 1));
|
||||
const time = formatTime(input.hour, input.minute);
|
||||
const days = Array.isArray(input.on_days) ? input.on_days.filter((d: any) => Number.isInteger(d) && d >= 0 && d <= 6) : [];
|
||||
|
||||
if (unit === 'minute') return `Run every ${Math.max(15, Number(input.repeat_every || 15))} minutes`;
|
||||
if (unit === 'hour') {
|
||||
const minute = Number(input.minute || 0);
|
||||
const suffix = minute > 0 ? ` at :${String(minute).padStart(2, '0')}` : '';
|
||||
return every === 1 ? `Run every hour${suffix}` : `Run every ${every} hours${suffix}`;
|
||||
}
|
||||
if (unit === 'day') return `Run ${every === 1 ? 'daily' : `every ${every} days`}${time ? ` at ${time}` : ''}`;
|
||||
if (unit === 'month') return `Run ${every === 1 ? 'monthly' : `every ${every} months`}${time ? ` at ${time}` : ''}`;
|
||||
if (unit === 'week') {
|
||||
const dayText = days.length === 1
|
||||
? weekdayLabel(days[0])
|
||||
: days.length === 5 && [1, 2, 3, 4, 5].every((d) => days.includes(d))
|
||||
? 'weekdays'
|
||||
: days.length > 1
|
||||
? days.map(weekdayLabel).filter(Boolean).join(', ')
|
||||
: '';
|
||||
if (!dayText) return time ? `Choose weekly days at ${time}` : 'Choose weekly days';
|
||||
return `Run ${every === 1 ? `every ${dayText}` : `every ${every} weeks on ${dayText}`}${time ? ` at ${time}` : ''}`;
|
||||
}
|
||||
if (input.title) return `Update "${input.title}"`;
|
||||
return '';
|
||||
}
|
||||
|
||||
function firstText(input: any, keys: string[]): string {
|
||||
for (const key of keys) {
|
||||
const value = input?.[key];
|
||||
if (typeof value === 'string' && value.trim()) return value.trim();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function stepNumber(input: any): string {
|
||||
const raw = input?.step_idx ?? input?.step_index ?? input?.index;
|
||||
const idx = typeof raw === 'number' ? raw : Number(raw);
|
||||
return Number.isInteger(idx) && idx >= 0 ? String(idx + 1) : '';
|
||||
}
|
||||
|
||||
function compactWorkflowAction(input: any, action: string): string {
|
||||
const lower = action.toLowerCase();
|
||||
const step = stepNumber(input);
|
||||
const text = firstText(input, ['new_text', 'text', 'prompt', 'description']);
|
||||
const label = firstText(input, ['new_label', 'label', 'title', 'name']);
|
||||
const preview = text || label;
|
||||
const shortPreview = preview.length > 80 ? preview.slice(0, 77) + '...' : preview;
|
||||
|
||||
if (lower === 'addworkflowstep') return shortPreview ? `Add step: ${shortPreview}` : 'Add a workflow step';
|
||||
if (lower === 'editworkflowstep') return step ? `Edit step ${step}` : 'Edit workflow step';
|
||||
if (lower === 'deleteworkflowstep') return step ? `Delete step ${step}` : 'Delete workflow step';
|
||||
if (lower === 'runworkflow') return 'Run this workflow now';
|
||||
if (lower === 'testworkflow') return 'Test this workflow';
|
||||
if (lower === 'readtesttranscript') return 'Read latest test results';
|
||||
if (lower === 'deletescheduledworkflow') return 'Delete this workflow';
|
||||
if (lower === 'pauseallworkflows') return 'Pause all scheduled workflows';
|
||||
if (lower === 'resumeallworkflows') return 'Resume scheduled workflows';
|
||||
if (lower === 'listworkflows') return 'Show workflows';
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getWorkflowToolLabel(action: string): string | null {
|
||||
const lower = action.toLowerCase();
|
||||
if (lower === 'scheduleworkflow') return 'Schedule workflow';
|
||||
if (lower === 'updatescheduledworkflow') return 'Update schedule';
|
||||
if (lower === 'deletescheduledworkflow') return 'Delete workflow';
|
||||
if (lower === 'pauseallworkflows') return 'Pause workflows';
|
||||
if (lower === 'resumeallworkflows') return 'Resume workflows';
|
||||
if (lower === 'runworkflow') return 'Run workflow';
|
||||
if (lower === 'editworkflowstep') return 'Edit workflow step';
|
||||
if (lower === 'addworkflowstep') return 'Add workflow step';
|
||||
if (lower === 'deleteworkflowstep') return 'Delete workflow step';
|
||||
if (lower === 'testworkflow') return 'Test workflow';
|
||||
if (lower === 'readtesttranscript') return 'Read test results';
|
||||
if (lower === 'listworkflows') return 'List workflows';
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getWorkflowToolInputDisplay(input: any, action?: string, serverSlug?: string): string {
|
||||
if (!input || typeof input !== 'object') return '';
|
||||
const isWorkflowTool = serverSlug === 'openswarm-schedule' || (action && getWorkflowToolLabel(action));
|
||||
if (!isWorkflowTool || !action) return '';
|
||||
|
||||
const lower = action.toLowerCase();
|
||||
const lines: string[] = [];
|
||||
const schedule = compactWorkflowSchedule(input);
|
||||
const step = stepNumber(input);
|
||||
const text = firstText(input, ['new_text', 'text', 'prompt', 'description']);
|
||||
const label = firstText(input, ['new_label', 'label', 'title', 'name']);
|
||||
|
||||
if (lower === 'scheduleworkflow' || lower === 'updatescheduledworkflow') {
|
||||
if (schedule) lines.push(`Schedule: ${schedule}`);
|
||||
if (label) lines.push(`Name: ${label}`);
|
||||
return lines.join('\n') || getWorkflowToolLabel(action) || 'Workflow action';
|
||||
}
|
||||
|
||||
if (lower === 'addworkflowstep') {
|
||||
if (text) lines.push(`Step: ${text}`);
|
||||
if (label && label !== text) lines.push(`Label: ${label}`);
|
||||
return lines.join('\n') || compactWorkflowAction(input, action);
|
||||
}
|
||||
|
||||
if (lower === 'editworkflowstep') {
|
||||
if (step) lines.push(`Step: ${step}`);
|
||||
if (text) lines.push(`Prompt: ${text}`);
|
||||
if (label && label !== text) lines.push(`Label: ${label}`);
|
||||
return lines.join('\n') || compactWorkflowAction(input, action);
|
||||
}
|
||||
|
||||
return compactWorkflowAction(input, action) || getWorkflowToolLabel(action) || 'Workflow action';
|
||||
}
|
||||
|
||||
export function getMcpInputSummary(input: any, action?: string, serverSlug?: string): string {
|
||||
if (!input || typeof input !== 'object') return '';
|
||||
if (serverSlug === 'openswarm-schedule' || (action && getWorkflowToolLabel(action))) {
|
||||
const workflowSummary = compactWorkflowSchedule(input);
|
||||
if (workflowSummary) return workflowSummary;
|
||||
if (action) {
|
||||
const actionSummary = compactWorkflowAction(input, action);
|
||||
if (actionSummary) return actionSummary;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
const keys = Object.keys(input);
|
||||
if (keys.length === 0) return '';
|
||||
if (keys.length === 1) {
|
||||
|
||||
Reference in New Issue
Block a user