Compare commits

..

32 Commits

Author SHA1 Message Date
Vikhyath Mondreti
b09f683072 v0.5.63: ui and performance improvements, more google tools 2026-01-18 15:22:42 -08:00
Vikhyath Mondreti
1dbf92db3f fix(api): tool input parsing into table from agent output (#2879)
* fix(api): transformTable to map agent output to table subblock format

* fix api

* add test
2026-01-18 14:43:02 -08:00
Waleed
3a923648cb feat(ux): more explicit verbiage on some dialog menus, google drive updates, advanved to additional fields, remove general settings store sync in favor of tanstack (#2875)
* fix(verbiage): more explicit verbiage on some dialog menus, google drive updates, advanved to additional fields, remove general settings store sync in favor of tanstack

* updated docs

* nested tag dropdown, more well-defined nested outputs, keyboard nav for context menus, etc

* cleanup

* allow cannonical toggle even if depends on not satisfied

* remove smooth scroll in tag drop

* fix selection

* fix

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
2026-01-18 13:40:59 -08:00
Vikhyath Mondreti
a8bb0db660 v0.5.62: webhook bug fixes, seeding default subblock values, block selection fixes 2026-01-16 20:27:06 -08:00
Waleed
af82820a28 v0.5.61: webhook improvements, workflow controls, react query for deployment status, chat fixes, reducto and pulse OCR, linear fixes 2026-01-16 18:06:23 -08:00
Waleed
4372841797 v0.5.60: invitation flow improvements, chat fixes, a2a improvements, additional copilot actions 2026-01-15 00:02:18 -08:00
Waleed
5e8c843241 v0.5.59: a2a support, documentation 2026-01-13 13:21:21 -08:00
Waleed
7bf3d73ee6 v0.5.58: export folders, new tools, permissions groups enhancements 2026-01-13 00:56:59 -08:00
Vikhyath Mondreti
7ffc11a738 v0.5.57: subagents, context menu improvements, bug fixes 2026-01-11 11:38:40 -08:00
Waleed
be578e2ed7 v0.5.56: batch operations, access control and permission groups, billing fixes 2026-01-10 00:31:34 -08:00
Waleed
f415e5edc4 v0.5.55: polling groups, bedrock provider, devcontainer fixes, workflow preview enhancements 2026-01-08 23:36:56 -08:00
Waleed
13a6e6c3fa v0.5.54: seo, model blacklist, helm chart updates, fireflies integration, autoconnect improvements, billing fixes 2026-01-07 16:09:45 -08:00
Waleed
f5ab7f21ae v0.5.53: hotkey improvements, added redis fallback, fixes for workflow tool 2026-01-06 23:34:52 -08:00
Waleed
bfb6fffe38 v0.5.52: new port-based router block, combobox expression and variable support 2026-01-06 16:14:10 -08:00
Waleed
4fbec0a43f v0.5.51: triggers, kb, condition block improvements, supabase and grain integration updates 2026-01-06 14:26:46 -08:00
Waleed
585f5e365b v0.5.50: import improvements, ui upgrades, kb styling and performance improvements 2026-01-05 00:35:55 -08:00
Waleed
3792bdd252 v0.5.49: hitl improvements, new email styles, imap trigger, logs context menu (#2672)
* feat(logs-context-menu): consolidated logs utils and types, added logs record context menu (#2659)

* feat(email): welcome email; improvement(emails): ui/ux (#2658)

* feat(email): welcome email; improvement(emails): ui/ux

* improvement(emails): links, accounts, preview

* refactor(emails): file structure and wrapper components

* added envvar for personal emails sent, added isHosted gate

* fixed failing tests, added env mock

* fix: removed comment

---------

Co-authored-by: waleed <walif6@gmail.com>

* fix(logging): hitl + trigger dev crash protection (#2664)

* hitl gaps

* deal with trigger worker crashes

* cleanup import strcuture

* feat(imap): added support for imap trigger (#2663)

* feat(tools): added support for imap trigger

* feat(imap): added parity, tested

* ack PR comments

* final cleanup

* feat(i18n): update translations (#2665)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* fix(grain): updated grain trigger to auto-establish trigger (#2666)

Co-authored-by: aadamgough <adam@sim.ai>

* feat(admin): routes to manage deployments (#2667)

* feat(admin): routes to manage deployments

* fix naming fo deployed by

* feat(time-picker): added timepicker emcn component, added to playground, added searchable prop for dropdown, added more timezones for schedule, updated license and notice date (#2668)

* feat(time-picker): added timepicker emcn component, added to playground, added searchable prop for dropdown, added more timezones for schedule, updated license and notice date

* removed unused params, cleaned up redundant utils

* improvement(invite): aligned styling (#2669)

* improvement(invite): aligned with rest of app

* fix(invite): error handling

* fix: addressed comments

---------

Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com>
Co-authored-by: aadamgough <adam@sim.ai>
2026-01-03 13:19:18 -08:00
Waleed
eb5d1f3e5b v0.5.48: copy-paste workflow blocks, docs updates, mcp tool fixes 2025-12-31 18:00:04 -08:00
Waleed
54ab82c8dd v0.5.47: deploy workflow as mcp, kb chunks tokenizer, UI improvements, jira service management tools 2025-12-30 23:18:58 -08:00
Waleed
f895bf469b v0.5.46: build improvements, greptile, light mode improvements 2025-12-29 02:17:52 -08:00
Waleed
dd3209af06 v0.5.45: light mode fixes, realtime usage indicator, docker build improvements 2025-12-27 19:57:42 -08:00
Waleed
b6ba3b50a7 v0.5.44: keyboard shortcuts, autolayout, light mode, byok, testing improvements 2025-12-26 21:25:19 -08:00
Waleed
b304233062 v0.5.43: export logs, circleback, grain, vertex, code hygiene, schedule improvements 2025-12-23 19:19:18 -08:00
Vikhyath Mondreti
57e4b49bd6 v0.5.42: fix memory migration 2025-12-23 01:24:54 -08:00
Vikhyath Mondreti
e12dd204ed v0.5.41: memory fixes, copilot improvements, knowledgebase improvements, LLM providers standardization 2025-12-23 00:15:18 -08:00
Vikhyath Mondreti
3d9d9cbc54 v0.5.40: supabase ops to allow non-public schemas, jira uuid 2025-12-21 22:28:05 -08:00
Waleed
0f4ec962ad v0.5.39: notion, workflow variables fixes 2025-12-20 20:44:00 -08:00
Waleed
4827866f9a v0.5.38: snap to grid, copilot ux improvements, billing line items 2025-12-20 17:24:38 -08:00
Waleed
3e697d9ed9 v0.5.37: redaction utils consolidation, logs updates, autoconnect improvements, additional kb tag types 2025-12-19 22:31:55 -08:00
Martin Yankov
4431a1a484 fix(helm): add custom egress rules to realtime network policy (#2481)
The realtime service network policy was missing the custom egress rules section
that allows configuration of additional egress rules via values.yaml. This caused
the realtime pods to be unable to connect to external databases (e.g., PostgreSQL
on port 5432) when using external database configurations.

The app network policy already had this section, but the realtime network policy
was missing it, creating an inconsistency and preventing the realtime service
from accessing external databases configured via networkPolicy.egress values.

This fix adds the same custom egress rules template section to the realtime
network policy, matching the app network policy behavior and allowing users to
configure database connectivity via values.yaml.
2025-12-19 18:59:08 -08:00
Waleed
4d1a9a3f22 v0.5.36: hitl improvements, opengraph, slack fixes, one-click unsubscribe, auth checks, new db indexes 2025-12-19 01:27:49 -08:00
Vikhyath Mondreti
eb07a080fb v0.5.35: helm updates, copilot improvements, 404 for docs, salesforce fixes, subflow resize clamping 2025-12-18 16:23:19 -08:00
9 changed files with 233 additions and 243 deletions

View File

@@ -1,5 +1,6 @@
import type { RequestParams, RequestResponse } from '@/tools/http/types'
import { getDefaultHeaders, processUrl, transformTable } from '@/tools/http/utils'
import { getDefaultHeaders, processUrl } from '@/tools/http/utils'
import { transformTable } from '@/tools/shared/table'
import type { ToolConfig } from '@/tools/types'
export const requestTool: ToolConfig<RequestParams, RequestResponse> = {

View File

@@ -1,6 +1,7 @@
import { createLogger } from '@sim/logger'
import { isTest } from '@/lib/core/config/feature-flags'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { transformTable } from '@/tools/shared/table'
import type { TableRow } from '@/tools/types'
const logger = createLogger('HTTPRequestUtils')
@@ -119,28 +120,3 @@ export const shouldUseProxy = (url: string): boolean => {
return false
}
}
/**
* Transforms a table from the store format to a key-value object
* Local copy of the function to break circular dependencies
* @param table Array of table rows from the store
* @returns Record of key-value pairs
*/
export const transformTable = (table: TableRow[] | null): Record<string, any> => {
if (!table) return {}
return table.reduce(
(acc, row) => {
if (row.cells?.Key && row.cells?.Value !== undefined) {
// Extract the Value cell as is - it should already be properly resolved
// by the InputResolver based on variable type (number, string, boolean etc.)
const value = row.cells.Value
// Store the correctly typed value in the result object
acc[row.cells.Key] = value
}
return acc
},
{} as Record<string, any>
)
}

View File

@@ -1,6 +1,6 @@
import type { KnowledgeCreateDocumentResponse } from '@/tools/knowledge/types'
import { formatDocumentTagsForAPI, parseDocumentTags } from '@/tools/params'
import { enrichKBTagsSchema } from '@/tools/schema-enrichers'
import { formatDocumentTagsForAPI, parseDocumentTags } from '@/tools/shared/tags'
import type { ToolConfig } from '@/tools/types'
export const knowledgeCreateDocumentTool: ToolConfig<any, KnowledgeCreateDocumentResponse> = {

View File

@@ -1,6 +1,6 @@
import type { KnowledgeSearchResponse } from '@/tools/knowledge/types'
import { parseTagFilters } from '@/tools/params'
import { enrichKBTagFiltersSchema } from '@/tools/schema-enrichers'
import { parseTagFilters } from '@/tools/shared/tags'
import type { ToolConfig } from '@/tools/types'
export const knowledgeSearchTool: ToolConfig<any, KnowledgeSearchResponse> = {

View File

@@ -1,11 +1,11 @@
import { createLogger } from '@sim/logger'
import type { StructuredFilter } from '@/lib/knowledge/types'
import { extractInputFieldsFromBlocks } from '@/lib/workflows/input-format'
import {
evaluateSubBlockCondition,
type SubBlockCondition,
} from '@/lib/workflows/subblocks/visibility'
import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types'
import { isEmptyTagValue } from '@/tools/shared/tags'
import type { ParameterVisibility, ToolConfig } from '@/tools/types'
import { getTool } from '@/tools/utils'
@@ -23,194 +23,6 @@ export function isNonEmpty(value: unknown): boolean {
// Tag/Value Parsing Utilities
// ============================================================================
/**
* Document tag entry format used in create_document tool
*/
export interface DocumentTagEntry {
tagName: string
value: string
}
/**
* Tag filter entry format used in search tool
*/
export interface TagFilterEntry {
tagName: string
tagSlot?: string
tagValue: string | number | boolean
fieldType?: string
operator?: string
valueTo?: string | number
}
/**
* Checks if a tag value is effectively empty (unfilled/default entry)
*/
function isEmptyTagEntry(entry: Record<string, unknown>): boolean {
if (!entry.tagName || (typeof entry.tagName === 'string' && entry.tagName.trim() === '')) {
return true
}
return false
}
/**
* Checks if a tag-based value is effectively empty (only contains default/unfilled entries).
* Works for both documentTags and tagFilters parameters in various formats.
*
* @param value - The tag value to check (can be JSON string, array, or object)
* @returns true if the value is empty or only contains unfilled entries
*/
export function isEmptyTagValue(value: unknown): boolean {
if (!value) return true
// Handle JSON string format
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value)
if (!Array.isArray(parsed)) return false
if (parsed.length === 0) return true
return parsed.every((entry: Record<string, unknown>) => isEmptyTagEntry(entry))
} catch {
return false
}
}
// Handle array format directly
if (Array.isArray(value)) {
if (value.length === 0) return true
return value.every((entry: Record<string, unknown>) => isEmptyTagEntry(entry))
}
// Handle object format (LLM format: { "Category": "foo", "Priority": 5 })
if (typeof value === 'object' && value !== null) {
const entries = Object.entries(value)
if (entries.length === 0) return true
return entries.every(([, val]) => val === undefined || val === null || val === '')
}
return false
}
/**
* Filters valid document tags from an array, removing empty entries
*/
function filterValidDocumentTags(tags: unknown[]): DocumentTagEntry[] {
return tags
.filter((entry): entry is Record<string, unknown> => {
if (typeof entry !== 'object' || entry === null) return false
const e = entry as Record<string, unknown>
if (!e.tagName || (typeof e.tagName === 'string' && e.tagName.trim() === '')) return false
if (e.value === undefined || e.value === null || e.value === '') return false
return true
})
.map((entry) => ({
tagName: String(entry.tagName),
value: String(entry.value),
}))
}
/**
* Parses document tags from various formats into a normalized array format.
* Used by create_document tool to handle tags from both UI and LLM sources.
*
* @param value - Document tags in object, array, or JSON string format
* @returns Normalized array of document tag entries, or empty array if invalid
*/
export function parseDocumentTags(value: unknown): DocumentTagEntry[] {
if (!value) return []
// Handle object format from LLM: { "Category": "foo", "Priority": 5 }
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
return Object.entries(value)
.filter(([tagName, tagValue]) => {
if (!tagName || tagName.trim() === '') return false
if (tagValue === undefined || tagValue === null || tagValue === '') return false
return true
})
.map(([tagName, tagValue]) => ({
tagName,
value: String(tagValue),
}))
}
// Handle JSON string format from UI
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
return filterValidDocumentTags(parsed)
}
} catch {
// Invalid JSON, return empty
}
return []
}
// Handle array format directly
if (Array.isArray(value)) {
return filterValidDocumentTags(value)
}
return []
}
/**
* Parses tag filters from various formats into a normalized StructuredFilter array.
* Used by search tool to handle tag filters from both UI and LLM sources.
*
* @param value - Tag filters in array or JSON string format
* @returns Normalized array of structured filters, or empty array if invalid
*/
export function parseTagFilters(value: unknown): StructuredFilter[] {
if (!value) return []
let tagFilters = value
// Handle JSON string format
if (typeof tagFilters === 'string') {
try {
tagFilters = JSON.parse(tagFilters)
} catch {
return []
}
}
// Must be an array at this point
if (!Array.isArray(tagFilters)) return []
return tagFilters
.filter((filter): filter is Record<string, unknown> => {
if (typeof filter !== 'object' || filter === null) return false
const f = filter as Record<string, unknown>
if (!f.tagName || (typeof f.tagName === 'string' && f.tagName.trim() === '')) return false
if (f.fieldType === 'boolean') {
return f.tagValue !== undefined
}
if (f.tagValue === undefined || f.tagValue === null) return false
if (typeof f.tagValue === 'string' && f.tagValue.trim().length === 0) return false
return true
})
.map((filter) => ({
tagName: filter.tagName as string,
tagSlot: (filter.tagSlot as string) || '',
fieldType: (filter.fieldType as string) || 'text',
operator: (filter.operator as string) || 'eq',
value: filter.tagValue as string | number | boolean,
valueTo: filter.valueTo as string | number | undefined,
}))
}
/**
* Converts parsed document tags to the format expected by the create document API.
* Returns the documentTagsData JSON string if there are valid tags.
*/
export function formatDocumentTagsForAPI(tags: DocumentTagEntry[]): { documentTagsData?: string } {
if (tags.length === 0) return {}
return {
documentTagsData: JSON.stringify(tags),
}
}
export interface Option {
label: string
value: string

View File

@@ -0,0 +1,38 @@
import type { TableRow } from '@/tools/types'
/**
* Transforms a table from the store format to a key-value object.
*/
export const transformTable = (
table: TableRow[] | Record<string, any> | string | null
): Record<string, any> => {
if (!table) return {}
if (typeof table === 'string') {
try {
const parsed = JSON.parse(table) as TableRow[] | Record<string, any>
return transformTable(parsed)
} catch {
return {}
}
}
if (Array.isArray(table)) {
return table.reduce(
(acc, row) => {
if (row.cells?.Key && row.cells?.Value !== undefined) {
const value = row.cells.Value
acc[row.cells.Key] = value
}
return acc
},
{} as Record<string, any>
)
}
if (typeof table === 'object') {
return table
}
return {}
}

View File

@@ -0,0 +1,168 @@
import type { StructuredFilter } from '@/lib/knowledge/types'
/**
* Document tag entry format used in create_document tool.
*/
export interface DocumentTagEntry {
tagName: string
value: string
}
/**
* Tag filter entry format used in search tool.
*/
export interface TagFilterEntry {
tagName: string
tagSlot?: string
tagValue: string | number | boolean
fieldType?: string
operator?: string
valueTo?: string | number
}
/**
* Checks if a tag value is effectively empty (unfilled/default entry).
*/
function isEmptyTagEntry(entry: Record<string, unknown>): boolean {
if (!entry.tagName || (typeof entry.tagName === 'string' && entry.tagName.trim() === '')) {
return true
}
return false
}
/**
* Checks if a tag-based value is effectively empty (only contains default/unfilled entries).
*/
export function isEmptyTagValue(value: unknown): boolean {
if (!value) return true
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value)
if (!Array.isArray(parsed)) return false
if (parsed.length === 0) return true
return parsed.every((entry: Record<string, unknown>) => isEmptyTagEntry(entry))
} catch {
return false
}
}
if (Array.isArray(value)) {
if (value.length === 0) return true
return value.every((entry: Record<string, unknown>) => isEmptyTagEntry(entry))
}
if (typeof value === 'object' && value !== null) {
const entries = Object.entries(value)
if (entries.length === 0) return true
return entries.every(([, val]) => val === undefined || val === null || val === '')
}
return false
}
/**
* Filters valid document tags from an array, removing empty entries.
*/
function filterValidDocumentTags(tags: unknown[]): DocumentTagEntry[] {
return tags
.filter((entry): entry is Record<string, unknown> => {
if (typeof entry !== 'object' || entry === null) return false
const e = entry as Record<string, unknown>
if (!e.tagName || (typeof e.tagName === 'string' && e.tagName.trim() === '')) return false
if (e.value === undefined || e.value === null || e.value === '') return false
return true
})
.map((entry) => ({
tagName: String(entry.tagName),
value: String(entry.value),
}))
}
/**
* Parses document tags from various formats into a normalized array format.
*/
export function parseDocumentTags(value: unknown): DocumentTagEntry[] {
if (!value) return []
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
return Object.entries(value)
.filter(([tagName, tagValue]) => {
if (!tagName || tagName.trim() === '') return false
if (tagValue === undefined || tagValue === null || tagValue === '') return false
return true
})
.map(([tagName, tagValue]) => ({
tagName,
value: String(tagValue),
}))
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
return filterValidDocumentTags(parsed)
}
} catch {
return []
}
return []
}
if (Array.isArray(value)) {
return filterValidDocumentTags(value)
}
return []
}
/**
* Parses tag filters from various formats into a normalized StructuredFilter array.
*/
export function parseTagFilters(value: unknown): StructuredFilter[] {
if (!value) return []
let tagFilters = value
if (typeof tagFilters === 'string') {
try {
tagFilters = JSON.parse(tagFilters)
} catch {
return []
}
}
if (!Array.isArray(tagFilters)) return []
return tagFilters
.filter((filter): filter is Record<string, unknown> => {
if (typeof filter !== 'object' || filter === null) return false
const f = filter as Record<string, unknown>
if (!f.tagName || (typeof f.tagName === 'string' && f.tagName.trim() === '')) return false
if (f.fieldType === 'boolean') {
return f.tagValue !== undefined
}
if (f.tagValue === undefined || f.tagValue === null) return false
if (typeof f.tagValue === 'string' && f.tagValue.trim().length === 0) return false
return true
})
.map((filter) => ({
tagName: filter.tagName as string,
tagSlot: (filter.tagSlot as string) || '',
fieldType: (filter.fieldType as string) || 'text',
operator: (filter.operator as string) || 'eq',
value: filter.tagValue as string | number | boolean,
valueTo: filter.valueTo as string | number | undefined,
}))
}
/**
* Converts parsed document tags to the format expected by the create document API.
*/
export function formatDocumentTagsForAPI(tags: DocumentTagEntry[]): { documentTagsData?: string } {
if (tags.length === 0) return {}
return {
documentTagsData: JSON.stringify(tags),
}
}

View File

@@ -1,5 +1,6 @@
import { createMockFetch, loggerMock } from '@sim/testing'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { transformTable } from '@/tools/shared/table'
import type { ToolConfig } from '@/tools/types'
import {
createCustomToolRequestBody,
@@ -7,7 +8,6 @@ import {
executeRequest,
formatRequestParams,
getClientEnvVars,
transformTable,
validateRequiredParametersAfterMerge,
} from '@/tools/utils'
@@ -91,6 +91,25 @@ describe('transformTable', () => {
enabled: false,
})
})
it.concurrent('should parse JSON string inputs and transform rows', () => {
const table = [
{ id: '1', cells: { Key: 'city', Value: 'SF' } },
{ id: '2', cells: { Key: 'temp', Value: 64 } },
]
const result = transformTable(JSON.stringify(table))
expect(result).toEqual({
city: 'SF',
temp: 64,
})
})
it.concurrent('should parse JSON string object inputs', () => {
const result = transformTable(JSON.stringify({ a: 1, b: 'two' }))
expect(result).toEqual({ a: 1, b: 'two' })
})
})
describe('formatRequestParams', () => {

View File

@@ -5,7 +5,7 @@ import { useCustomToolsStore } from '@/stores/custom-tools'
import { useEnvironmentStore } from '@/stores/settings/environment'
import { extractErrorMessage } from '@/tools/error-extractors'
import { tools } from '@/tools/registry'
import type { TableRow, ToolConfig, ToolResponse } from '@/tools/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
const logger = createLogger('ToolsUtils')
@@ -70,30 +70,6 @@ export function resolveToolId(toolName: string): string {
return toolName
}
/**
* Transforms a table from the store format to a key-value object
* @param table Array of table rows from the store
* @returns Record of key-value pairs
*/
export const transformTable = (table: TableRow[] | null): Record<string, any> => {
if (!table) return {}
return table.reduce(
(acc, row) => {
if (row.cells?.Key && row.cells?.Value !== undefined) {
// Extract the Value cell as is - it should already be properly resolved
// by the InputResolver based on variable type (number, string, boolean etc.)
const value = row.cells.Value
// Store the correctly typed value in the result object
acc[row.cells.Key] = value
}
return acc
},
{} as Record<string, any>
)
}
interface RequestParams {
url: string
method: string