mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-21 12:58:07 -05:00
Compare commits
16 Commits
v0.5.65
...
feat/tools
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78f818f7cd | ||
|
|
cd1c5315d6 | ||
|
|
601f58cec9 | ||
|
|
9fc6378f17 | ||
|
|
63d109de3a | ||
|
|
c9239b55ef | ||
|
|
233a3ee0b4 | ||
|
|
c3634c2e38 | ||
|
|
51ed4f506d | ||
|
|
59578dd140 | ||
|
|
dcaae1df7c | ||
|
|
c5d3405c7a | ||
|
|
0ac6fec0a5 | ||
|
|
75450afb11 | ||
|
|
dbee20e9e5 | ||
|
|
ecf39c5a54 |
@@ -234,7 +234,7 @@ function ProgressBar({
|
||||
{segments.map((segment, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='absolute h-full opacity-70'
|
||||
className='absolute h-full'
|
||||
style={{
|
||||
left: `${segment.startPercent}%`,
|
||||
width: `${segment.widthPercent}%`,
|
||||
|
||||
@@ -257,7 +257,7 @@ export const LogDetails = memo(function LogDetails({
|
||||
Version
|
||||
</span>
|
||||
<div className='flex w-0 flex-1 justify-end'>
|
||||
<span className='max-w-full truncate rounded-[6px] bg-[#bbf7d0] px-[9px] py-[2px] font-medium text-[#15803d] text-[12px] dark:bg-[#14291B] dark:text-[#86EFAC]'>
|
||||
<span className='max-w-full truncate rounded-[6px] bg-[#14291B] px-[9px] py-[2px] font-medium text-[#86EFAC] text-[12px]'>
|
||||
{log.deploymentVersionName || `v${log.deploymentVersion}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,6 @@ import { DatePicker } from '@/components/emcn/components/date-picker/date-picker
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { hasActiveFilters } from '@/lib/logs/filters'
|
||||
import { getTriggerOptions } from '@/lib/logs/get-trigger-options'
|
||||
import { type LogStatus, STATUS_CONFIG } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
import { useFilterStore } from '@/stores/logs/filters/store'
|
||||
@@ -212,12 +211,12 @@ export function LogsToolbar({
|
||||
}, [level])
|
||||
|
||||
const statusOptions: ComboboxOption[] = useMemo(
|
||||
() =>
|
||||
(Object.keys(STATUS_CONFIG) as LogStatus[]).map((status) => ({
|
||||
value: status,
|
||||
label: STATUS_CONFIG[status].label,
|
||||
icon: getColorIcon(STATUS_CONFIG[status].color),
|
||||
})),
|
||||
() => [
|
||||
{ value: 'error', label: 'Error', icon: getColorIcon('var(--text-error)') },
|
||||
{ value: 'info', label: 'Info', icon: getColorIcon('var(--terminal-status-info-color)') },
|
||||
{ value: 'running', label: 'Running', icon: getColorIcon('#22c55e') },
|
||||
{ value: 'pending', label: 'Pending', icon: getColorIcon('#f59e0b') },
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
@@ -243,8 +242,12 @@ export function LogsToolbar({
|
||||
|
||||
const selectedStatusColor = useMemo(() => {
|
||||
if (selectedStatuses.length !== 1) return null
|
||||
const status = selectedStatuses[0] as LogStatus
|
||||
return STATUS_CONFIG[status]?.color ?? null
|
||||
const status = selectedStatuses[0]
|
||||
if (status === 'error') return 'var(--text-error)'
|
||||
if (status === 'info') return 'var(--terminal-status-info-color)'
|
||||
if (status === 'running') return '#22c55e'
|
||||
if (status === 'pending') return '#f59e0b'
|
||||
return null
|
||||
}, [selectedStatuses])
|
||||
|
||||
const workflowOptions: ComboboxOption[] = useMemo(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
|
||||
|
||||
/** Column configuration for logs table - shared between header and rows */
|
||||
export const LOG_COLUMNS = {
|
||||
date: { width: 'w-[8%]', minWidth: 'min-w-[70px]', label: 'Date' },
|
||||
time: { width: 'w-[12%]', minWidth: 'min-w-[90px]', label: 'Time' },
|
||||
@@ -15,8 +16,10 @@ export const LOG_COLUMNS = {
|
||||
duration: { width: 'w-[20%]', minWidth: 'min-w-[100px]', label: 'Duration' },
|
||||
} as const
|
||||
|
||||
/** Type-safe column key derived from LOG_COLUMNS */
|
||||
export type LogColumnKey = keyof typeof LOG_COLUMNS
|
||||
|
||||
/** Ordered list of column keys for rendering table headers */
|
||||
export const LOG_COLUMN_ORDER: readonly LogColumnKey[] = [
|
||||
'date',
|
||||
'time',
|
||||
@@ -27,6 +30,7 @@ export const LOG_COLUMN_ORDER: readonly LogColumnKey[] = [
|
||||
'duration',
|
||||
] as const
|
||||
|
||||
/** Possible execution status values for workflow logs */
|
||||
export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled'
|
||||
|
||||
/**
|
||||
@@ -49,28 +53,30 @@ export function getDisplayStatus(status: string | null | undefined): LogStatus {
|
||||
}
|
||||
}
|
||||
|
||||
export const STATUS_CONFIG: Record<
|
||||
/** Configuration mapping log status to Badge variant and display label */
|
||||
const STATUS_VARIANT_MAP: Record<
|
||||
LogStatus,
|
||||
{ variant: React.ComponentProps<typeof Badge>['variant']; label: string; color: string }
|
||||
{ variant: React.ComponentProps<typeof Badge>['variant']; label: string }
|
||||
> = {
|
||||
error: { variant: 'red', label: 'Error', color: 'var(--text-error)' },
|
||||
pending: { variant: 'amber', label: 'Pending', color: '#f59e0b' },
|
||||
running: { variant: 'green', label: 'Running', color: '#22c55e' },
|
||||
cancelled: { variant: 'orange', label: 'Cancelled', color: '#f97316' },
|
||||
info: { variant: 'gray', label: 'Info', color: 'var(--terminal-status-info-color)' },
|
||||
error: { variant: 'red', label: 'Error' },
|
||||
pending: { variant: 'amber', label: 'Pending' },
|
||||
running: { variant: 'green', label: 'Running' },
|
||||
cancelled: { variant: 'gray', label: 'Cancelled' },
|
||||
info: { variant: 'gray', label: 'Info' },
|
||||
}
|
||||
|
||||
/** Configuration mapping core trigger types to Badge color variants */
|
||||
const TRIGGER_VARIANT_MAP: Record<string, React.ComponentProps<typeof Badge>['variant']> = {
|
||||
manual: 'gray-secondary',
|
||||
api: 'blue',
|
||||
schedule: 'green',
|
||||
chat: 'purple',
|
||||
webhook: 'orange',
|
||||
mcp: 'cyan',
|
||||
a2a: 'teal',
|
||||
}
|
||||
|
||||
interface StatusBadgeProps {
|
||||
/** The execution status to display */
|
||||
status: LogStatus
|
||||
}
|
||||
|
||||
@@ -80,13 +86,14 @@ interface StatusBadgeProps {
|
||||
* @returns A Badge with dot indicator and status label
|
||||
*/
|
||||
export const StatusBadge = React.memo(({ status }: StatusBadgeProps) => {
|
||||
const config = STATUS_CONFIG[status]
|
||||
const config = STATUS_VARIANT_MAP[status]
|
||||
return React.createElement(Badge, { variant: config.variant, dot: true }, config.label)
|
||||
})
|
||||
|
||||
StatusBadge.displayName = 'StatusBadge'
|
||||
|
||||
interface TriggerBadgeProps {
|
||||
/** The trigger type identifier (e.g., 'manual', 'api', or integration block type) */
|
||||
trigger: string
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export const ActionBar = memo(
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
{!isStartBlock && !isResponseBlock && (
|
||||
{!isStartBlock && !isResponseBlock && !isSubflowBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
@@ -213,29 +213,6 @@ export const ActionBar = memo(
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
{isSubflowBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (!disabled) {
|
||||
collaborativeBatchToggleBlockEnabled([blockId])
|
||||
}
|
||||
}}
|
||||
className={ACTION_BUTTON_STYLES}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isEnabled ? <Circle className={ICON_SIZE} /> : <CircleOff className={ICON_SIZE} />}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface CanvasMenuProps {
|
||||
onOpenLogs: () => void
|
||||
onToggleVariables: () => void
|
||||
onToggleChat: () => void
|
||||
onInvite: () => void
|
||||
isVariablesOpen?: boolean
|
||||
isChatOpen?: boolean
|
||||
hasClipboard?: boolean
|
||||
@@ -54,12 +55,15 @@ export function CanvasMenu({
|
||||
onOpenLogs,
|
||||
onToggleVariables,
|
||||
onToggleChat,
|
||||
onInvite,
|
||||
isVariablesOpen = false,
|
||||
isChatOpen = false,
|
||||
hasClipboard = false,
|
||||
disableEdit = false,
|
||||
disableAdmin = false,
|
||||
canUndo = false,
|
||||
canRedo = false,
|
||||
isInvitationsDisabled = false,
|
||||
}: CanvasMenuProps) {
|
||||
return (
|
||||
<Popover
|
||||
@@ -175,6 +179,22 @@ export function CanvasMenu({
|
||||
>
|
||||
{isChatOpen ? 'Close Chat' : 'Open Chat'}
|
||||
</PopoverItem>
|
||||
|
||||
{/* Admin action - hidden when invitations are disabled */}
|
||||
{!isInvitationsDisabled && (
|
||||
<>
|
||||
<PopoverDivider />
|
||||
<PopoverItem
|
||||
disabled={disableAdmin}
|
||||
onClick={() => {
|
||||
onInvite()
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
Invite to Workspace
|
||||
</PopoverItem>
|
||||
</>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
|
||||
@@ -886,16 +886,17 @@ export function Chat() {
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
{shouldShowConfigureStartInputsButton && (
|
||||
<div
|
||||
className='flex flex-none cursor-pointer items-center whitespace-nowrap rounded-[6px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[9px] py-[2px] font-medium font-sans text-[12px] text-[var(--text-primary)] hover:bg-[var(--surface-7)] dark:hover:border-[var(--surface-7)] dark:hover:bg-[var(--border-1)]'
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='flex-none cursor-pointer whitespace-nowrap rounded-[6px]'
|
||||
title='Add chat inputs to Start block'
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation()
|
||||
handleConfigureStartInputs()
|
||||
}}
|
||||
>
|
||||
<span className='whitespace-nowrap'>Add inputs</span>
|
||||
</div>
|
||||
<span className='whitespace-nowrap text-[12px]'>Add inputs</span>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<OutputSelect
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { Check, Clipboard } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
@@ -882,13 +883,14 @@ console.log(data);`
|
||||
<code className='text-[10px]'><start.files></code>.
|
||||
</p>
|
||||
{missingFields.any && (
|
||||
<div
|
||||
className='flex flex-none cursor-pointer items-center whitespace-nowrap rounded-[6px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[9px] py-[2px] font-medium font-sans text-[12px] text-[var(--text-primary)] hover:bg-[var(--surface-7)] dark:hover:border-[var(--surface-7)] dark:hover:bg-[var(--border-1)]'
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='flex-none cursor-pointer whitespace-nowrap rounded-[6px]'
|
||||
title='Add required A2A input fields to Start block'
|
||||
onClick={handleAddA2AInputs}
|
||||
>
|
||||
<span className='whitespace-nowrap'>Add inputs</span>
|
||||
</div>
|
||||
<span className='whitespace-nowrap text-[12px]'>Add inputs</span>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { memo, useMemo, useRef } from 'react'
|
||||
import { RepeatIcon, SplitIcon } from 'lucide-react'
|
||||
import { Handle, type NodeProps, Position, useReactFlow } from 'reactflow'
|
||||
import { Badge } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { HANDLE_POSITIONS } from '@/lib/workflows/blocks/block-dimensions'
|
||||
import { type DiffStatus, hasDiffStatus } from '@/lib/workflows/diff/types'
|
||||
@@ -79,7 +78,6 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
|
||||
? currentBlock.is_diff
|
||||
: undefined
|
||||
|
||||
const isEnabled = currentBlock?.enabled ?? true
|
||||
const isPreview = data?.isPreview || false
|
||||
|
||||
// Focus state
|
||||
@@ -186,21 +184,14 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
|
||||
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
|
||||
<div
|
||||
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
|
||||
style={{ backgroundColor: isEnabled ? blockIconBg : 'gray' }}
|
||||
style={{ backgroundColor: blockIconBg }}
|
||||
>
|
||||
<BlockIcon className='h-[16px] w-[16px] text-white' />
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
'truncate font-medium text-[16px]',
|
||||
!isEnabled && 'text-[var(--text-muted)]'
|
||||
)}
|
||||
title={blockName}
|
||||
>
|
||||
<span className='font-medium text-[16px]' title={blockName}>
|
||||
{blockName}
|
||||
</span>
|
||||
</div>
|
||||
{!isEnabled && <Badge variant='gray-secondary'>disabled</Badge>}
|
||||
</div>
|
||||
|
||||
{!isPreview && (
|
||||
|
||||
@@ -3323,12 +3323,15 @@ const WorkflowContent = React.memo(() => {
|
||||
onOpenLogs={handleContextOpenLogs}
|
||||
onToggleVariables={handleContextToggleVariables}
|
||||
onToggleChat={handleContextToggleChat}
|
||||
onInvite={handleContextInvite}
|
||||
isVariablesOpen={isVariablesOpen}
|
||||
isChatOpen={isChatOpen}
|
||||
hasClipboard={hasClipboard()}
|
||||
disableEdit={!effectivePermissions.canEdit}
|
||||
disableAdmin={!effectivePermissions.canAdmin}
|
||||
canUndo={canUndo}
|
||||
canRedo={canRedo}
|
||||
isInvitationsDisabled={isInvitationsDisabled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -214,15 +214,6 @@ export const A2ABlock: BlockConfig<A2AResponse> = {
|
||||
],
|
||||
config: {
|
||||
tool: (params) => params.operation as string,
|
||||
params: (params) => {
|
||||
const { fileUpload, fileReference, ...rest } = params
|
||||
const hasFileUpload = Array.isArray(fileUpload) ? fileUpload.length > 0 : !!fileUpload
|
||||
const files = hasFileUpload ? fileUpload : fileReference
|
||||
return {
|
||||
...rest,
|
||||
...(files ? { files } : {}),
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
|
||||
@@ -25,7 +25,7 @@ const badgeVariants = cva(
|
||||
orange: `${STATUS_BASE} bg-[#fed7aa] text-[#c2410c] dark:bg-[rgba(249,115,22,0.2)] dark:text-[#fdba74]`,
|
||||
amber: `${STATUS_BASE} bg-[#fde68a] text-[#a16207] dark:bg-[rgba(245,158,11,0.2)] dark:text-[#fcd34d]`,
|
||||
teal: `${STATUS_BASE} bg-[#99f6e4] text-[#0f766e] dark:bg-[rgba(20,184,166,0.2)] dark:text-[#5eead4]`,
|
||||
cyan: `${STATUS_BASE} bg-[var(--surface-4)] text-[#0891b2] dark:bg-[rgba(14,165,233,0.2)] dark:text-[#7dd3fc]`,
|
||||
cyan: `${STATUS_BASE} bg-[#a5f3fc] text-[#0e7490] dark:bg-[rgba(14,165,233,0.2)] dark:text-[#7dd3fc]`,
|
||||
'gray-secondary': `${STATUS_BASE} bg-[var(--surface-4)] text-[var(--text-secondary)]`,
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>What's New at Sim</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background-color:#ffffff;">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" style="background-color:#ffffff;">
|
||||
<tr>
|
||||
<td align="center" style="padding:0 16px;">
|
||||
<table width="600" cellspacing="0" cellpadding="0" border="0" role="presentation" style="max-width:600px;width:100%;">
|
||||
|
||||
<!-- Logo -->
|
||||
<tr>
|
||||
<td align="center" style="padding-top:32px;padding-bottom:16px;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/logo.png" width="79" alt="Sim Logo" style="display:block;width:79px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Intro Paragraph -->
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:20px;padding-top:8px;text-align:left;">
|
||||
<p style="margin:0;">We're excited to introduce <strong>Sim v0.5</strong>, the next evolution of our agent workflow platform—built for teams who need <strong>context-aware AI assistance</strong>, <strong>seamless tool deployment</strong>, and <strong>full execution observability</strong> in production.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- CTA Button -->
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:25px;padding-top:5px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center" style="background-color:#32bd7e;border-radius:5px;">
|
||||
<a href="https://sim.ai" style="display:inline-block;font-weight:500;font-size:14px;padding:7px 16px;text-decoration:none;color:#ffffff;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;" target="_blank">Try Sim</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Header Text -->
|
||||
<tr>
|
||||
<td align="left" valign="top" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;word-break:break-word;padding-top:10px;padding-bottom:8px;text-align:left;">
|
||||
<h2 style="margin:0;color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:28px;font-weight:600;word-break:break-word;">Get things done <em>faster</em></h2>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:30px;text-align:left;">
|
||||
<p style="margin:0;">You're focused on the most critical tasks, let agents handle the rest.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 1: Copilot -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:10px;padding-bottom:12px;text-align:left;">
|
||||
<strong>Copilot</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="border-radius:8px;font-size:0;line-height:0;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/copilot.jpg" width="570" alt="Sim Copilot" style="display:block;width:100%;border-radius:8px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Copilot is a context-aware assistant embedded directly into the Sim editor. It has first-class access to your workflows, blocks, execution logs, and documentation—helping you build, debug, and optimize without leaving the canvas. Ask it to explain a workflow, propose changes, or execute actions. All writes are gated behind explicit approval, so you stay in control.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 2: MCP Deployment -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:20px;padding-bottom:12px;text-align:left;">
|
||||
<strong>MCP Deployment</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://sim.ai" style="text-decoration:none;display:block;" target="_blank">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation" width="100%" style="background-color:#181C1E;border-radius:8px;">
|
||||
<tr>
|
||||
<td align="center" style="padding:40px 30px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:16px;">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/mcp.png" width="48" height="48" alt="MCP" style="display:block;" border="0">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p style="margin:0;color:#ffffff;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:20px;font-weight:500;">Expose workflows as tools for any agent</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Deploy any workflow as an MCP tool and expose it to any MCP-compatible agent—Claude Desktop, Cursor, or your own applications. Define custom tool names, descriptions, and parameter schemas. Your workflows become reusable building blocks that other agents can invoke, turning Sim into the backend for your agentic systems.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 3: Logs & Dashboard -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:20px;padding-bottom:12px;text-align:left;">
|
||||
<strong>Logs & Dashboard</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="border-radius:8px;font-size:0;line-height:0;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/dashboard.jpg" width="570" alt="Logs & Dashboard" style="display:block;width:100%;border-radius:8px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Full observability for every execution. Trace spans show you exactly what happened at each step—durations, token usage, cost breakdowns by model, and error details when things go wrong. Filter by time range, trigger type, or workflow. Live mode auto-refreshes every 5 seconds. Restore any previous workflow state with execution snapshots.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 4: Collaboration -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:20px;padding-bottom:12px;text-align:left;">
|
||||
<strong>Realtime Collaboration</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="border-radius:8px;font-size:0;line-height:0;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/collaboration.jpg" width="570" alt="Realtime Collaboration" style="display:block;width:100%;border-radius:8px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Invite teammates to your workspace and build workflows together in realtime—like Figma for AI agents. See cursors move, blocks added, and configurations updated as they happen. The operation queue ensures reliability even under poor network conditions. Undo/redo works per-user, so your changes stay separate from your collaborators'.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:10px;padding-top:10px;">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" height="1" style="border-top:1px solid #ededed;">
|
||||
<tr>
|
||||
<td height="0" style="font-size:0;line-height:0;">­</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Final CTA -->
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:15px;padding-top:15px;text-align:left;">
|
||||
<p style="margin:0;">Ready to build? These features are available now in Sim.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:30px;padding-top:15px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center" style="background-color:#32bd7e;border-radius:5px;">
|
||||
<a href="https://sim.ai" style="display:inline-block;font-weight:500;font-size:14px;padding:7px 16px;text-decoration:none;color:#ffffff;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;" target="_blank">Get Started</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Divider -->
|
||||
<tr>
|
||||
<td align="center" style="padding-top:20px;padding-bottom:20px;">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" height="1" style="border-top:1px solid #ededed;">
|
||||
<tr>
|
||||
<td height="0" style="font-size:0;line-height:0;">­</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Social links row -->
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="padding:0 8px 0 0;">
|
||||
<a href="https://sim.ai/x" rel="noopener noreferrer" target="_blank">
|
||||
<img src="https://sim.ai/static/x-icon.png" width="20" height="20" alt="X" style="display:block;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="padding:0 8px;">
|
||||
<a href="https://sim.ai/discord" rel="noopener noreferrer" target="_blank">
|
||||
<img src="https://sim.ai/static/discord-icon.png" width="20" height="20" alt="Discord" style="display:block;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="padding:0 8px;">
|
||||
<a href="https://sim.ai/github" rel="noopener noreferrer" target="_blank">
|
||||
<img src="https://sim.ai/static/github-icon.png" width="20" height="20" alt="GitHub" style="display:block;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="16" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Address row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
Sim, 80 Langton St, San Francisco, CA 94103, USA
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="8" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Contact row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
Questions? <a href="mailto:support@sim.ai" style="color:#737373;text-decoration:underline;font-weight:normal;">support@sim.ai</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="8" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Links row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
<a href="https://sim.ai/privacy" style="color:#737373;text-decoration:underline;font-weight:normal;" rel="noopener noreferrer" target="_blank">Privacy Policy</a>
|
||||
•
|
||||
<a href="https://sim.ai/terms" style="color:#737373;text-decoration:underline;font-weight:normal;" rel="noopener noreferrer" target="_blank">Terms of Service</a>
|
||||
•
|
||||
<a href="{{{RESEND_UNSUBSCRIBE_URL}}}" style="color:#737373;text-decoration:underline;font-weight:normal;" rel="noopener noreferrer" target="_blank">Unsubscribe</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="16" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Copyright row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
© 2026 Sim, All Rights Reserved
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Bottom spacer -->
|
||||
<tr>
|
||||
<td height="32" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
GetBlockConfigInput,
|
||||
GetBlockConfigResult,
|
||||
} from '@/lib/copilot/tools/shared/schemas'
|
||||
import { getLatestBlock } from '@/blocks/registry'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
|
||||
interface GetBlockConfigArgs {
|
||||
blockType: string
|
||||
@@ -40,7 +40,8 @@ export class GetBlockConfigClientTool extends BaseClientTool {
|
||||
},
|
||||
getDynamicText: (params, state) => {
|
||||
if (params?.blockType && typeof params.blockType === 'string') {
|
||||
const blockConfig = getLatestBlock(params.blockType)
|
||||
// Look up the block config to get the human-readable name
|
||||
const blockConfig = getBlock(params.blockType)
|
||||
const blockName = (blockConfig?.name ?? params.blockType.replace(/_/g, ' ')).toLowerCase()
|
||||
const opSuffix = params.operation ? ` (${params.operation})` : ''
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
GetBlockOptionsInput,
|
||||
GetBlockOptionsResult,
|
||||
} from '@/lib/copilot/tools/shared/schemas'
|
||||
import { getLatestBlock } from '@/blocks/registry'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
|
||||
interface GetBlockOptionsArgs {
|
||||
blockId: string
|
||||
@@ -43,7 +43,8 @@ export class GetBlockOptionsClientTool extends BaseClientTool {
|
||||
(params as any)?.block_id ||
|
||||
(params as any)?.block_type
|
||||
if (typeof blockId === 'string') {
|
||||
const blockConfig = getLatestBlock(blockId)
|
||||
// Look up the block config to get the human-readable name
|
||||
const blockConfig = getBlock(blockId)
|
||||
const blockName = (blockConfig?.name ?? blockId.replace(/_/g, ' ')).toLowerCase()
|
||||
|
||||
switch (state) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
GetBlockConfigResult,
|
||||
type GetBlockConfigResultType,
|
||||
} from '@/lib/copilot/tools/shared/schemas'
|
||||
import { registry as blockRegistry, getLatestBlock } from '@/blocks/registry'
|
||||
import { registry as blockRegistry } from '@/blocks/registry'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { getUserPermissionConfig } from '@/executor/utils/permission-check'
|
||||
import { PROVIDER_DEFINITIONS } from '@/providers/models'
|
||||
@@ -452,12 +452,9 @@ export const getBlockConfigServerTool: BaseServerTool<
|
||||
const inputs = extractInputsFromSubBlocks(subBlocks, operation, trigger)
|
||||
const outputs = extractOutputs(blockConfig, operation, trigger)
|
||||
|
||||
const latestBlock = getLatestBlock(blockType)
|
||||
const displayName = latestBlock?.name ?? blockConfig.name
|
||||
|
||||
const result = {
|
||||
blockType,
|
||||
blockName: displayName,
|
||||
blockName: blockConfig.name,
|
||||
operation,
|
||||
trigger,
|
||||
inputs,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
GetBlockOptionsResult,
|
||||
type GetBlockOptionsResultType,
|
||||
} from '@/lib/copilot/tools/shared/schemas'
|
||||
import { registry as blockRegistry, getLatestBlock } from '@/blocks/registry'
|
||||
import { registry as blockRegistry } from '@/blocks/registry'
|
||||
import { getUserPermissionConfig } from '@/executor/utils/permission-check'
|
||||
import { tools as toolsRegistry } from '@/tools/registry'
|
||||
|
||||
@@ -113,12 +113,9 @@ export const getBlockOptionsServerTool: BaseServerTool<
|
||||
}
|
||||
}
|
||||
|
||||
const latestBlock = getLatestBlock(blockId)
|
||||
const displayName = latestBlock?.name ?? blockConfig.name
|
||||
|
||||
const result = {
|
||||
blockId,
|
||||
blockName: displayName,
|
||||
blockName: blockConfig.name,
|
||||
operations,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
import { SYSTEM_SUBBLOCK_IDS, TRIGGER_RUNTIME_SUBBLOCK_IDS } from '@/triggers/constants'
|
||||
import {
|
||||
normalizedStringify,
|
||||
@@ -13,20 +13,6 @@ import {
|
||||
sortEdges,
|
||||
} from './normalize'
|
||||
|
||||
/** Block with optional diff markers added by copilot */
|
||||
type BlockWithDiffMarkers = BlockState & {
|
||||
is_diff?: string
|
||||
field_diffs?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** SubBlock with optional diff marker */
|
||||
type SubBlockWithDiffMarker = {
|
||||
id: string
|
||||
type: string
|
||||
value: unknown
|
||||
is_diff?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the current workflow state with the deployed state to detect meaningful changes
|
||||
* @param currentState - The current workflow state
|
||||
@@ -77,32 +63,21 @@ export function hasWorkflowChanged(
|
||||
// - subBlocks: handled separately below
|
||||
// - layout: contains measuredWidth/measuredHeight from autolayout
|
||||
// - height: block height measurement from autolayout
|
||||
// - outputs: derived from subBlocks (e.g., inputFormat), already compared via subBlocks
|
||||
// - is_diff, field_diffs: diff markers from copilot edits
|
||||
const currentBlockWithDiff = currentBlock as BlockWithDiffMarkers
|
||||
const deployedBlockWithDiff = deployedBlock as BlockWithDiffMarkers
|
||||
|
||||
const {
|
||||
position: _currentPos,
|
||||
subBlocks: currentSubBlocks = {},
|
||||
layout: _currentLayout,
|
||||
height: _currentHeight,
|
||||
outputs: _currentOutputs,
|
||||
is_diff: _currentIsDiff,
|
||||
field_diffs: _currentFieldDiffs,
|
||||
...currentRest
|
||||
} = currentBlockWithDiff
|
||||
} = currentBlock
|
||||
|
||||
const {
|
||||
position: _deployedPos,
|
||||
subBlocks: deployedSubBlocks = {},
|
||||
layout: _deployedLayout,
|
||||
height: _deployedHeight,
|
||||
outputs: _deployedOutputs,
|
||||
is_diff: _deployedIsDiff,
|
||||
field_diffs: _deployedFieldDiffs,
|
||||
...deployedRest
|
||||
} = deployedBlockWithDiff
|
||||
} = deployedBlock
|
||||
|
||||
// Also exclude width/height from data object (container dimensions from autolayout)
|
||||
const {
|
||||
@@ -181,13 +156,14 @@ export function hasWorkflowChanged(
|
||||
}
|
||||
}
|
||||
|
||||
// Compare type and other properties (excluding diff markers and value)
|
||||
const currentSubBlockWithDiff = currentSubBlocks[subBlockId] as SubBlockWithDiffMarker
|
||||
const deployedSubBlockWithDiff = deployedSubBlocks[subBlockId] as SubBlockWithDiffMarker
|
||||
const { value: _cv, is_diff: _cd, ...currentSubBlockRest } = currentSubBlockWithDiff
|
||||
const { value: _dv, is_diff: _dd, ...deployedSubBlockRest } = deployedSubBlockWithDiff
|
||||
// Compare type and other properties
|
||||
const currentSubBlockWithoutValue = { ...currentSubBlocks[subBlockId], value: undefined }
|
||||
const deployedSubBlockWithoutValue = { ...deployedSubBlocks[subBlockId], value: undefined }
|
||||
|
||||
if (normalizedStringify(currentSubBlockRest) !== normalizedStringify(deployedSubBlockRest)) {
|
||||
if (
|
||||
normalizedStringify(currentSubBlockWithoutValue) !==
|
||||
normalizedStringify(deployedSubBlockWithoutValue)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +376,6 @@ describe('Database Helpers', () => {
|
||||
forEachItems: '',
|
||||
doWhileCondition: '',
|
||||
whileCondition: '',
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
expect(result?.parallels['parallel-1']).toEqual({
|
||||
@@ -385,7 +384,6 @@ describe('Database Helpers', () => {
|
||||
count: 5,
|
||||
distribution: ['item1', 'item2'],
|
||||
parallelType: 'count',
|
||||
enabled: true,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -273,7 +273,6 @@ export async function loadWorkflowFromNormalizedTables(
|
||||
forEachItems: (config as Loop).forEachItems ?? '',
|
||||
whileCondition: (config as Loop).whileCondition ?? '',
|
||||
doWhileCondition: (config as Loop).doWhileCondition ?? '',
|
||||
enabled: migratedBlocks[subflow.id]?.enabled ?? true,
|
||||
}
|
||||
loops[subflow.id] = loop
|
||||
|
||||
@@ -302,7 +301,6 @@ export async function loadWorkflowFromNormalizedTables(
|
||||
(config as Parallel).parallelType === 'collection'
|
||||
? (config as Parallel).parallelType
|
||||
: 'count',
|
||||
enabled: migratedBlocks[subflow.id]?.enabled ?? true,
|
||||
}
|
||||
parallels[subflow.id] = parallel
|
||||
} else {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 108 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -172,14 +172,7 @@ export type TimeRange =
|
||||
| 'All time'
|
||||
| 'Custom range'
|
||||
|
||||
export type LogLevel =
|
||||
| 'error'
|
||||
| 'info'
|
||||
| 'running'
|
||||
| 'pending'
|
||||
| 'cancelled'
|
||||
| 'all'
|
||||
| (string & {})
|
||||
export type LogLevel = 'error' | 'info' | 'running' | 'pending' | 'all' | (string & {})
|
||||
/** Core trigger types for workflow execution */
|
||||
export const CORE_TRIGGER_TYPES = [
|
||||
'manual',
|
||||
|
||||
@@ -130,7 +130,6 @@ export interface Loop {
|
||||
forEachItems?: any[] | Record<string, any> | string // Items or expression
|
||||
whileCondition?: string // JS expression that evaluates to boolean (for while loops)
|
||||
doWhileCondition?: string // JS expression that evaluates to boolean (for do-while loops)
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Parallel {
|
||||
@@ -139,7 +138,6 @@ export interface Parallel {
|
||||
distribution?: any[] | Record<string, any> | string // Items or expression
|
||||
count?: number // Number of parallel executions for count-based parallel
|
||||
parallelType?: 'count' | 'collection' // Explicit parallel type to avoid inference bugs
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Variable {
|
||||
|
||||
@@ -72,7 +72,6 @@ export function convertLoopBlockToLoop(
|
||||
nodes: findChildNodes(loopBlockId, blocks),
|
||||
iterations: loopBlock.data?.count || DEFAULT_LOOP_ITERATIONS,
|
||||
loopType,
|
||||
enabled: loopBlock.enabled,
|
||||
}
|
||||
|
||||
loop.forEachItems = loopBlock.data?.collection || ''
|
||||
@@ -114,7 +113,6 @@ export function convertParallelBlockToParallel(
|
||||
distribution,
|
||||
count,
|
||||
parallelType: validatedParallelType,
|
||||
enabled: parallelBlock.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user