mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Fix condition edges
This commit is contained in:
@@ -232,6 +232,19 @@ export const sseHandlers: Record<string, SSEHandler> = {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isToolAvailableOnSimSide(toolName)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Non-interactive mode (Mothership/MCP): skip confirmation & client gates,
|
||||
// execute server-side directly.
|
||||
if (options.interactive === false) {
|
||||
if (options.autoExecuteTools !== false) {
|
||||
await executeToolAndReport(toolCallId, context, execContext, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (requiresConfirmation) {
|
||||
const decision = await waitForToolDecision(
|
||||
toolCallId,
|
||||
@@ -440,6 +453,15 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
|
||||
return
|
||||
}
|
||||
|
||||
// Non-interactive mode (Mothership/MCP): skip confirmation & client gates,
|
||||
// execute server-side directly.
|
||||
if (options.interactive === false) {
|
||||
if (options.autoExecuteTools !== false) {
|
||||
await executeToolAndReport(toolCallId, context, execContext, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (requiresConfirmation) {
|
||||
const decision = await waitForToolDecision(
|
||||
toolCallId,
|
||||
|
||||
@@ -277,6 +277,10 @@ const SERVER_TOOLS = new Set<string>([
|
||||
'make_api_request',
|
||||
'knowledge_base',
|
||||
'user_table',
|
||||
'run_workflow',
|
||||
'run_workflow_until_block',
|
||||
'run_block',
|
||||
'run_from_block',
|
||||
])
|
||||
|
||||
const SIM_WORKFLOW_TOOL_HANDLERS: Record<
|
||||
|
||||
@@ -93,6 +93,9 @@ export function createBlockFromParams(
|
||||
// Normalize array subblocks with id fields (inputFormat, table rows, etc.)
|
||||
if (shouldNormalizeArrayIds(key)) {
|
||||
sanitizedValue = normalizeArrayWithIds(value)
|
||||
if (JSON_STRING_SUBBLOCK_KEYS.has(key)) {
|
||||
sanitizedValue = JSON.stringify(sanitizedValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for tools - normalize and filter disallowed
|
||||
@@ -127,6 +130,8 @@ export function createBlockFromParams(
|
||||
type: subBlock.type,
|
||||
value: null,
|
||||
}
|
||||
} else {
|
||||
blockState.subBlocks[subBlock.id].type = subBlock.type
|
||||
}
|
||||
})
|
||||
|
||||
@@ -141,16 +146,16 @@ export function createBlockFromParams(
|
||||
blockState.subBlocks.conditions = {
|
||||
id: 'conditions',
|
||||
type: 'condition-input',
|
||||
value: [
|
||||
value: JSON.stringify([
|
||||
{ id: crypto.randomUUID(), title: 'If', value: '' },
|
||||
{ id: crypto.randomUUID(), title: 'Else', value: '' },
|
||||
],
|
||||
]),
|
||||
}
|
||||
} else if (params.type === 'router_v2' && !blockState.subBlocks.routes?.value) {
|
||||
blockState.subBlocks.routes = {
|
||||
id: 'routes',
|
||||
type: 'router-input',
|
||||
value: [{ id: crypto.randomUUID(), title: 'Route 1', value: '' }],
|
||||
value: JSON.stringify([{ id: crypto.randomUUID(), title: 'Route 1', value: '' }]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +256,12 @@ const ARRAY_WITH_ID_SUBBLOCK_TYPES = new Set([
|
||||
'routes', // router-input: Router routes with id, title, value
|
||||
])
|
||||
|
||||
/**
|
||||
* Subblock keys whose UI components expect a JSON string, not a raw array.
|
||||
* After normalizeArrayWithIds returns an array, these must be re-stringified.
|
||||
*/
|
||||
export const JSON_STRING_SUBBLOCK_KEYS = new Set(['conditions', 'routes'])
|
||||
|
||||
/**
|
||||
* Normalizes array subblock values by ensuring each item has a valid UUID.
|
||||
* The LLM may generate arbitrary IDs like "input-desc-001" or "row-1" which need
|
||||
@@ -468,9 +479,16 @@ export function addConnectionsAsEdges(
|
||||
logger: ReturnType<typeof createLogger>,
|
||||
skippedItems?: SkippedItem[]
|
||||
): void {
|
||||
Object.entries(connections).forEach(([sourceHandle, targets]) => {
|
||||
const normalizeHandle = (handle: string): string => {
|
||||
if (handle === 'success') return 'source'
|
||||
return handle
|
||||
}
|
||||
|
||||
Object.entries(connections).forEach(([rawHandle, targets]) => {
|
||||
if (targets === null) return
|
||||
|
||||
const sourceHandle = normalizeHandle(rawHandle)
|
||||
|
||||
const addEdgeForTarget = (targetBlock: string, targetHandle?: string) => {
|
||||
createValidatedEdge(
|
||||
modifiedState,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
applyTriggerConfigToBlockSubblocks,
|
||||
createBlockFromParams,
|
||||
filterDisallowedTools,
|
||||
JSON_STRING_SUBBLOCK_KEYS,
|
||||
normalizeArrayWithIds,
|
||||
normalizeResponseFormat,
|
||||
normalizeTools,
|
||||
@@ -146,6 +147,9 @@ export function handleEditOperation(op: EditWorkflowOperation, ctx: OperationCon
|
||||
// Normalize array subblocks with id fields (inputFormat, table rows, etc.)
|
||||
if (shouldNormalizeArrayIds(key)) {
|
||||
sanitizedValue = normalizeArrayWithIds(value)
|
||||
if (JSON_STRING_SUBBLOCK_KEYS.has(key)) {
|
||||
sanitizedValue = JSON.stringify(sanitizedValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for tools - normalize and filter disallowed
|
||||
@@ -164,9 +168,10 @@ export function handleEditOperation(op: EditWorkflowOperation, ctx: OperationCon
|
||||
}
|
||||
|
||||
if (!block.subBlocks[key]) {
|
||||
const subBlockDef = getBlock(block.type)?.subBlocks.find((sb) => sb.id === key)
|
||||
block.subBlocks[key] = {
|
||||
id: key,
|
||||
type: 'short-input',
|
||||
type: subBlockDef?.type || 'short-input',
|
||||
value: sanitizedValue,
|
||||
}
|
||||
} else {
|
||||
@@ -817,6 +822,9 @@ export function handleInsertIntoSubflowOperation(
|
||||
// Normalize array subblocks with id fields (inputFormat, table rows, etc.)
|
||||
if (shouldNormalizeArrayIds(key)) {
|
||||
sanitizedValue = normalizeArrayWithIds(value)
|
||||
if (JSON_STRING_SUBBLOCK_KEYS.has(key)) {
|
||||
sanitizedValue = JSON.stringify(sanitizedValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for tools - normalize and filter disallowed
|
||||
@@ -835,9 +843,10 @@ export function handleInsertIntoSubflowOperation(
|
||||
}
|
||||
|
||||
if (!existingBlock.subBlocks[key]) {
|
||||
const subBlockDef = getBlock(existingBlock.type)?.subBlocks.find((sb) => sb.id === key)
|
||||
existingBlock.subBlocks[key] = {
|
||||
id: key,
|
||||
type: 'short-input',
|
||||
type: subBlockDef?.type || 'short-input',
|
||||
value: sanitizedValue,
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -44,9 +44,7 @@ You have access to these specialized subagents. Call them by name to delegate ta
|
||||
## Direct Tools
|
||||
|
||||
- **search_online** — Search the web for information.
|
||||
- **memory_file_read(file_path)** — Read a persistent memory file.
|
||||
- **memory_file_write(file_path, content)** — Write/update a persistent memory file.
|
||||
- **memory_file_list()** — List all memory files.
|
||||
- **context_write(file_path, content)** — Write/update persistent context files (WORKSPACE.md, SESSION.md).
|
||||
- **grep(pattern, path?)** — Search workspace VFS file contents.
|
||||
- **glob(pattern)** — Find workspace VFS files by path pattern.
|
||||
- **read(path)** — Read a workspace VFS file.
|
||||
@@ -79,20 +77,48 @@ environment/
|
||||
components/
|
||||
blocks/{type}.json — block type schemas
|
||||
integrations/{svc}/{op}.json — integration tool schemas
|
||||
internal/
|
||||
memories/WORKSPACE.md — workspace inventory (auto-injected)
|
||||
memories/SESSION.md — current session state (auto-injected)
|
||||
\`\`\`
|
||||
|
||||
**Tips**: Use \`glob("workflows/*/deployment.json")\` to see which workflows are deployed and how. Use \`grep("error", "workflows/")\` to find workflows with recent errors.
|
||||
|
||||
## Memory Management
|
||||
## Context System — CRITICAL
|
||||
|
||||
You have persistent memory files that survive across conversations:
|
||||
- **SOUL.md** — Your personality and behavioral guidelines. Read this at the start of conversations.
|
||||
- **USER.md** — Information about the user. Update as you learn preferences and context.
|
||||
- **MEMORY.md** — Key learnings, decisions, and important context. Update after significant interactions.
|
||||
Two context files are auto-injected into your system prompt above. You MUST keep them up to date.
|
||||
|
||||
**At conversation start**: Read SOUL.md and MEMORY.md to load your persistent context.
|
||||
**During conversation**: When the user shares important preferences or you make key decisions, update the relevant file.
|
||||
**Important**: Only write to files when there's genuinely new, important information. Don't update on every message.
|
||||
| File | Scope | Injected as |
|
||||
|------|-------|-------------|
|
||||
| **WORKSPACE.md** | Workspace (persists across chats) | \`## Workspace Context\` above |
|
||||
| **SESSION.md** | This chat only | \`## Session Context\` above |
|
||||
|
||||
### WORKSPACE.md — You MUST keep this current
|
||||
|
||||
**On your FIRST turn**: if Workspace Context above shows "(none discovered yet)", scan the workspace immediately:
|
||||
1. Run \`glob("workflows/*/meta.json")\`, \`glob("knowledgebases/*/meta.json")\`, \`glob("tables/*/meta.json")\`, \`read("environment/credentials.json")\`
|
||||
2. Write the results via \`context_write("WORKSPACE.md", content)\`
|
||||
|
||||
Do this silently as your first action — do NOT ask the user for permission.
|
||||
|
||||
**After ANY resource change** (create/edit/delete workflow, KB, table, credential): update WORKSPACE.md immediately.
|
||||
|
||||
### SESSION.md — You MUST update after every significant action
|
||||
|
||||
After completing any meaningful action (creating a workflow, making edits, deploying, making a decision), rewrite SESSION.md completely with the current state via \`context_write("SESSION.md", content)\`.
|
||||
|
||||
Always rewrite the entire file — never append. Keep the existing section structure.
|
||||
|
||||
### Reading context files
|
||||
|
||||
To read context files, use \`read("internal/memories/WORKSPACE.md")\` or \`read("internal/memories/SESSION.md")\`.
|
||||
|
||||
## Discovery-First Rule
|
||||
|
||||
**Before creating any new resource**, check what already exists:
|
||||
1. Check Workspace Context above for existing resources
|
||||
2. If unclear, run \`glob("workflows/*/meta.json")\` to verify
|
||||
3. Only create if nothing matches the user's request
|
||||
|
||||
## Decision Flow
|
||||
|
||||
@@ -105,7 +131,7 @@ You have persistent memory files that survive across conversations:
|
||||
|
||||
## Important
|
||||
|
||||
- **You work at the workspace level.** When a user mentions a workflow, ask for the workflow name or ID if not provided.
|
||||
- **You work at the workspace level.** When a user mentions a workflow, check Session Context and Workspace Context first.
|
||||
- **Always delegate complex work** to the appropriate subagent.
|
||||
- **Debug first** when something doesn't work — don't guess.
|
||||
- Be concise and results-focused.
|
||||
|
||||
@@ -249,9 +249,20 @@ function sanitizeSubBlocks(
|
||||
}
|
||||
|
||||
// Special handling for condition-input type - clean UI metadata
|
||||
if (subBlock.type === 'condition-input' && typeof subBlock.value === 'string') {
|
||||
const cleanedConditions: string = sanitizeConditions(subBlock.value)
|
||||
sanitized[key] = cleanedConditions
|
||||
if (subBlock.type === 'condition-input') {
|
||||
if (typeof subBlock.value === 'string') {
|
||||
sanitized[key] = sanitizeConditions(subBlock.value)
|
||||
} else if (Array.isArray(subBlock.value)) {
|
||||
sanitized[key] = (subBlock.value as unknown as Array<Record<string, unknown>>).map(
|
||||
(cond) => ({
|
||||
id: String(cond.id ?? ''),
|
||||
title: String(cond.title ?? ''),
|
||||
value: String(cond.value ?? ''),
|
||||
})
|
||||
)
|
||||
} else {
|
||||
sanitized[key] = subBlock.value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -287,16 +298,22 @@ function convertConditionHandleToSimple(
|
||||
// Extract the condition UUID from the handle
|
||||
const conditionId = handle.substring('condition-'.length)
|
||||
|
||||
// Get conditions from block subBlocks
|
||||
// Get conditions from block subBlocks (may be JSON string or array)
|
||||
const conditionsValue = block.subBlocks?.conditions?.value
|
||||
if (!conditionsValue || typeof conditionsValue !== 'string') {
|
||||
if (!conditionsValue) {
|
||||
return handle
|
||||
}
|
||||
|
||||
let conditions: Array<{ id: string; title: string }>
|
||||
try {
|
||||
conditions = JSON.parse(conditionsValue)
|
||||
} catch {
|
||||
if (Array.isArray(conditionsValue)) {
|
||||
conditions = conditionsValue as unknown as Array<{ id: string; title: string }>
|
||||
} else if (typeof conditionsValue === 'string') {
|
||||
try {
|
||||
conditions = JSON.parse(conditionsValue)
|
||||
} catch {
|
||||
return handle
|
||||
}
|
||||
} else {
|
||||
return handle
|
||||
}
|
||||
|
||||
@@ -341,16 +358,22 @@ function convertRouterHandleToSimple(handle: string, _blockId: string, block: Bl
|
||||
// Extract the route UUID from the handle
|
||||
const routeId = handle.substring('router-'.length)
|
||||
|
||||
// Get routes from block subBlocks
|
||||
// Get routes from block subBlocks (may be JSON string or array)
|
||||
const routesValue = block.subBlocks?.routes?.value
|
||||
if (!routesValue || typeof routesValue !== 'string') {
|
||||
if (!routesValue) {
|
||||
return handle
|
||||
}
|
||||
|
||||
let routes: Array<{ id: string; title?: string }>
|
||||
try {
|
||||
routes = JSON.parse(routesValue)
|
||||
} catch {
|
||||
if (Array.isArray(routesValue)) {
|
||||
routes = routesValue as unknown as Array<{ id: string; title?: string }>
|
||||
} else if (typeof routesValue === 'string') {
|
||||
try {
|
||||
routes = JSON.parse(routesValue)
|
||||
} catch {
|
||||
return handle
|
||||
}
|
||||
} else {
|
||||
return handle
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user