Skip to content

Commit 797f91a

Browse files
committed
Prompt surface
1 parent 64cbcab commit 797f91a

4 files changed

Lines changed: 56 additions & 12 deletions

File tree

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/builders.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ export function createValidatedEdge(
457457
type: 'invalid_edge_target',
458458
operationType,
459459
blockId: sourceBlockId,
460-
reason: `Edge from "${sourceBlockId}" to "${targetBlockId}" deferred - target block does not exist yet; it will be created automatically once the target block is added`,
460+
reason: `Edge from "${sourceBlockId}" to "${targetBlockId}" deferred until the target block "${targetBlockId}" exists - if it is created later (in this or a following edit) the engine wires this edge automatically; if you did not intend to create "${targetBlockId}", fix the target id.`,
461461
details: { sourceHandle, targetHandle, targetId: targetBlockId },
462462
})
463463
return false

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w
3232
import { normalizeWorkflowState } from '@/stores/workflows/workflow/validation'
3333
import { applyOperationsToWorkflowState } from './engine'
3434
import { formatWorkflowLintMessage, hasWorkflowLintIssues, lintEditedWorkflowState } from './lint'
35-
import type { EditWorkflowParams, ValidationError } from './types'
35+
import { isDeferredSkippedItem, type EditWorkflowParams, type ValidationError } from './types'
3636
import { preValidateCredentialInputs, validateWorkflowSelectorIds } from './validation'
3737

3838
async function getCurrentWorkflowStateFromDb(
@@ -226,9 +226,28 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
226226
? validationErrors.map((e) => `Block "${e.blockId}" (${e.blockType}): ${e.error}`)
227227
: undefined
228228

229-
// Format skipped items for LLM feedback
230-
const skippedMessages =
231-
skippedItems.length > 0 ? skippedItems.map((item) => item.reason) : undefined
229+
// Split engine skipped items into genuine failures vs benign, self-healing
230+
// deferrals. A deferred forward-reference edge (invalid_edge_target) is NOT
231+
// a failure: the engine wires it automatically once its target block exists
232+
// (this call or a later one) via pendingConnections. Surfacing it through
233+
// the same "skipped" failure channel as real skips makes a literal model
234+
// thrash (re-issuing a self-healing op). Keep them in separate result fields
235+
// and preserve item.type/details so the prompt can branch on a
236+
// machine-readable category instead of pattern-matching prose.
237+
const mapSkippedItem = (item: (typeof skippedItems)[number]) => ({
238+
type: item.type,
239+
operationType: item.operationType,
240+
blockId: item.blockId,
241+
reason: item.reason,
242+
...(item.details && { details: item.details }),
243+
})
244+
245+
const genuineSkippedItems = skippedItems.filter((item) => !isDeferredSkippedItem(item))
246+
const deferredItems = skippedItems.filter((item) => isDeferredSkippedItem(item))
247+
248+
const skippedDetails =
249+
genuineSkippedItems.length > 0 ? genuineSkippedItems.map(mapSkippedItem) : undefined
250+
const deferredDetails = deferredItems.length > 0 ? deferredItems.map(mapSkippedItem) : undefined
232251

233252
// Persist the workflow state to the database
234253
const finalWorkflowState = validation.sanitizedState || modifiedWorkflowState
@@ -320,9 +339,13 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
320339
inputValidationErrors: inputErrors,
321340
inputValidationMessage: `${inputErrors.length} input(s) were rejected due to validation errors. The workflow was still updated with valid inputs only. Errors: ${inputErrors.join('; ')}`,
322341
}),
323-
...(skippedMessages && {
324-
skippedItems: skippedMessages,
325-
skippedItemsMessage: `${skippedItems.length} operation(s) were skipped due to invalid references. Details: ${skippedMessages.join('; ')}`,
342+
...(skippedDetails && {
343+
skippedItems: skippedDetails,
344+
skippedItemsMessage: `${skippedDetails.length} operation(s) were skipped (not applied) and need attention. Each item includes a machine-readable "type" (e.g. block_not_found, block_locked, duplicate_block_name, invalid_block_type, invalid_source_handle, invalid_target_handle, invalid_edge_scope). Details: ${skippedDetails.map((item) => item.reason).join('; ')}`,
345+
}),
346+
...(deferredDetails && {
347+
deferredConnections: deferredDetails,
348+
deferredMessage: `${deferredDetails.length} edge(s) were deferred because their target block does not exist yet. This is NOT a failure and does NOT need fixing: the engine wires these edges automatically once the target block exists (in this edit or a later one). Do not re-issue them. Only act on a deferred edge if its target id was a typo or hallucination that you do not intend to create. Details: ${deferredDetails.map((item) => item.reason).join('; ')}`,
326349
}),
327350
...(sanitizationWarnings && {
328351
sanitizationWarnings,

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/operations.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -872,10 +872,12 @@ export function handleInsertIntoSubflowOperation(
872872
}
873873

874874
if (subflowBlock.type !== 'loop' && subflowBlock.type !== 'parallel') {
875-
logger.error('Subflow block has invalid type', {
876-
subflowId,
877-
type: subflowBlock.type,
878-
block_id,
875+
logSkippedItem(skippedItems, {
876+
type: 'invalid_subflow_parent',
877+
operationType: 'insert_into_subflow',
878+
blockId: block_id,
879+
reason: `Cannot insert block "${block_id}" into "${subflowId}" - the target is a "${subflowBlock.type}" block, not a loop or parallel container`,
880+
details: { subflowId, subflowType: subflowBlock.type },
879881
})
880882
return
881883
}

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ export interface SkippedItem {
6262
details?: Record<string, any>
6363
}
6464

65+
/**
66+
* Skipped-item types that represent benign, SELF-HEALING deferrals rather than
67+
* failures. A deferred forward-reference edge (`invalid_edge_target`) is
68+
* recorded as a pending connection and wired automatically once its target
69+
* block exists -- possibly on a later edit_workflow call. It must be surfaced to
70+
* the model as informational, NOT through the "skipped/failed operation"
71+
* channel; otherwise a literal model re-issues the self-healing operation in a
72+
* loop. See `createValidatedEdge` (builders.ts) and `resolvePendingConnections`
73+
* (engine.ts).
74+
*/
75+
export const DEFERRED_SKIPPED_ITEM_TYPES: ReadonlySet<SkippedItemType> = new Set([
76+
'invalid_edge_target',
77+
])
78+
79+
/** Whether a skipped item is a benign deferral (see DEFERRED_SKIPPED_ITEM_TYPES). */
80+
export function isDeferredSkippedItem(item: SkippedItem): boolean {
81+
return DEFERRED_SKIPPED_ITEM_TYPES.has(item.type)
82+
}
83+
6584
/**
6685
* Logs and records a skipped item
6786
*/

0 commit comments

Comments
 (0)