mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
wip
This commit is contained in:
@@ -16,9 +16,9 @@ class OptionalBlockConditions(BaseModel):
|
||||
default=False,
|
||||
description="Skip block if any required credentials are missing",
|
||||
)
|
||||
input_flag: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Name of boolean agent input field that controls skip behavior",
|
||||
check_skip_input: bool = Field(
|
||||
default=True,
|
||||
description="Check the standard 'skip' input to control skip behavior",
|
||||
)
|
||||
kv_flag: Optional[str] = Field(
|
||||
default=None,
|
||||
|
||||
@@ -168,11 +168,11 @@ async def should_skip_node(
|
||||
if conditions.on_missing_credentials:
|
||||
conditions_met.append(False)
|
||||
|
||||
# Check input flag
|
||||
if conditions.input_flag and conditions.input_flag in input_data:
|
||||
flag_value = input_data.get(conditions.input_flag, False)
|
||||
if flag_value is True: # Skip if flag is True
|
||||
skip_reasons.append(f"Input flag '{conditions.input_flag}' is true")
|
||||
# Check standard skip_run_block input (automatically added for optional blocks)
|
||||
if conditions.check_skip_input and "skip_run_block" in input_data:
|
||||
skip_value = input_data.get("skip_run_block", False)
|
||||
if skip_value is True: # Skip if input is True
|
||||
skip_reasons.append("Skip input is true")
|
||||
conditions_met.append(True)
|
||||
else:
|
||||
conditions_met.append(False)
|
||||
|
||||
@@ -193,14 +193,14 @@ class TestShouldSkipNode:
|
||||
assert should_skip is False
|
||||
assert reason == ""
|
||||
|
||||
async def test_skip_on_input_flag_true(
|
||||
async def test_skip_on_skip_input_true(
|
||||
self, mock_node, mock_creds_manager, user_context
|
||||
):
|
||||
"""Test skipping when input flag is true."""
|
||||
"""Test skipping when skip_run_block input is true."""
|
||||
mock_node.metadata = {
|
||||
"optional": {
|
||||
"enabled": True,
|
||||
"conditions": {"input_flag": "skip_this_block"},
|
||||
"conditions": {"check_skip_input": True},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,20 +209,20 @@ class TestShouldSkipNode:
|
||||
creds_manager=mock_creds_manager,
|
||||
user_id="test_user",
|
||||
user_context=user_context,
|
||||
input_data={"skip_this_block": True},
|
||||
input_data={"skip_run_block": True},
|
||||
graph_id="test_graph_id",
|
||||
)
|
||||
assert should_skip is True
|
||||
assert "Input flag 'skip_this_block' is true" in reason
|
||||
assert "Skip input is true" in reason
|
||||
|
||||
async def test_no_skip_on_input_flag_false(
|
||||
async def test_no_skip_on_skip_input_false(
|
||||
self, mock_node, mock_creds_manager, user_context
|
||||
):
|
||||
"""Test no skip when input flag is false."""
|
||||
"""Test no skip when skip_run_block input is false."""
|
||||
mock_node.metadata = {
|
||||
"optional": {
|
||||
"enabled": True,
|
||||
"conditions": {"input_flag": "skip_this_block"},
|
||||
"conditions": {"check_skip_input": True},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ class TestShouldSkipNode:
|
||||
creds_manager=mock_creds_manager,
|
||||
user_id="test_user",
|
||||
user_context=user_context,
|
||||
input_data={"skip_this_block": False},
|
||||
input_data={"skip_run_block": False},
|
||||
graph_id="test_graph_id",
|
||||
)
|
||||
assert should_skip is False
|
||||
@@ -246,7 +246,7 @@ class TestShouldSkipNode:
|
||||
"enabled": True,
|
||||
"conditions": {
|
||||
"on_missing_credentials": True,
|
||||
"input_flag": "skip_block",
|
||||
"check_skip_input": True,
|
||||
"operator": "or",
|
||||
},
|
||||
}
|
||||
@@ -264,12 +264,12 @@ class TestShouldSkipNode:
|
||||
user_context=user_context,
|
||||
input_data={
|
||||
"credentials": {"id": "cred_123"},
|
||||
"skip_block": True,
|
||||
"skip_run_block": True,
|
||||
},
|
||||
graph_id="test_graph_id",
|
||||
)
|
||||
assert should_skip is True # OR: at least one condition met
|
||||
assert "Input flag 'skip_block' is true" in reason
|
||||
assert "Skip input is true" in reason
|
||||
|
||||
async def test_skip_with_and_operator(
|
||||
self, mock_node, mock_creds_manager, user_context
|
||||
@@ -280,7 +280,7 @@ class TestShouldSkipNode:
|
||||
"enabled": True,
|
||||
"conditions": {
|
||||
"on_missing_credentials": True,
|
||||
"input_flag": "skip_block",
|
||||
"check_skip_input": True,
|
||||
"operator": "and",
|
||||
},
|
||||
}
|
||||
@@ -298,7 +298,7 @@ class TestShouldSkipNode:
|
||||
user_context=user_context,
|
||||
input_data={
|
||||
"credentials": {"id": "cred_123"},
|
||||
"skip_block": False,
|
||||
"skip_run_block": False,
|
||||
},
|
||||
graph_id="test_graph_id",
|
||||
)
|
||||
@@ -312,7 +312,7 @@ class TestShouldSkipNode:
|
||||
mock_node.metadata = {
|
||||
"optional": {
|
||||
"enabled": True,
|
||||
"conditions": {"input_flag": "skip_this"},
|
||||
"conditions": {"check_skip_input": True},
|
||||
"skip_message": "Custom skip message for testing",
|
||||
}
|
||||
}
|
||||
@@ -322,7 +322,7 @@ class TestShouldSkipNode:
|
||||
creds_manager=mock_creds_manager,
|
||||
user_id="test_user",
|
||||
user_context=user_context,
|
||||
input_data={"skip_this": True},
|
||||
input_data={"skip_run_block": True},
|
||||
graph_id="test_graph_id",
|
||||
)
|
||||
assert should_skip is True
|
||||
@@ -403,7 +403,7 @@ class TestShouldSkipNode:
|
||||
"enabled": True,
|
||||
"conditions": {
|
||||
"kv_flag": "enable_integration",
|
||||
"input_flag": "force_skip",
|
||||
"check_skip_input": True,
|
||||
"operator": "or",
|
||||
},
|
||||
}
|
||||
@@ -417,17 +417,17 @@ class TestShouldSkipNode:
|
||||
return_value=False
|
||||
)
|
||||
|
||||
# Even though KV flag is False, input_flag is True so it should skip (OR operator)
|
||||
# Even though KV flag is False, skip_run_block is True so it should skip (OR operator)
|
||||
should_skip, reason = await should_skip_node(
|
||||
node=mock_node,
|
||||
creds_manager=mock_creds_manager,
|
||||
user_id="test_user",
|
||||
user_context=user_context,
|
||||
input_data={"force_skip": True},
|
||||
input_data={"skip_run_block": True},
|
||||
graph_id="test_graph_id",
|
||||
)
|
||||
assert should_skip is True
|
||||
assert "Input flag 'force_skip' is true" in reason
|
||||
assert "Skip input is true" in reason
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -130,6 +130,22 @@ export const CustomNode = React.memo(
|
||||
let subGraphID = "";
|
||||
const isOptional = data.metadata?.optional?.enabled || false;
|
||||
|
||||
// Automatically add skip_run_block input for optional blocks
|
||||
if (isOptional && !data.inputSchema.properties?.skip_run_block) {
|
||||
data.inputSchema = {
|
||||
...data.inputSchema,
|
||||
properties: {
|
||||
skip_run_block: {
|
||||
type: "boolean",
|
||||
title: "Skip Block",
|
||||
description: "When true, this block will be skipped during execution",
|
||||
default: false,
|
||||
},
|
||||
...data.inputSchema.properties,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (data.uiType === BlockUIType.AGENT) {
|
||||
// Display the graph's schema instead AgentExecutorBlock's schema.
|
||||
data.inputSchema = data.hardcodedValues?.input_schema || {};
|
||||
@@ -819,20 +835,20 @@ export const CustomNode = React.memo(
|
||||
</span>
|
||||
</ContextMenu.Item>
|
||||
<div className="pl-12 text-xs text-gray-500 dark:text-gray-400 space-y-1 py-1">
|
||||
{data.metadata?.optional?.conditions?.check_skip_input !== false && (
|
||||
<div>• Has skip input handle</div>
|
||||
)}
|
||||
{data.metadata?.optional?.conditions?.on_missing_credentials && (
|
||||
<div>• Skip on missing credentials</div>
|
||||
)}
|
||||
{data.metadata?.optional?.conditions?.input_flag && (
|
||||
<div>• Input flag: {data.metadata.optional.conditions.input_flag}</div>
|
||||
)}
|
||||
{data.metadata?.optional?.conditions?.kv_flag && (
|
||||
<div>• KV flag: {data.metadata.optional.conditions.kv_flag}</div>
|
||||
)}
|
||||
{data.metadata?.optional?.conditions?.operator === 'and' && (
|
||||
<div>• Using AND operator</div>
|
||||
)}
|
||||
{!data.metadata?.optional?.conditions?.on_missing_credentials &&
|
||||
!data.metadata?.optional?.conditions?.input_flag &&
|
||||
{data.metadata?.optional?.conditions?.check_skip_input === false &&
|
||||
!data.metadata?.optional?.conditions?.on_missing_credentials &&
|
||||
!data.metadata?.optional?.conditions?.kv_flag && (
|
||||
<div>• No conditions set</div>
|
||||
)}
|
||||
@@ -1189,23 +1205,23 @@ export const CustomNode = React.memo(
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium dark:text-gray-100">
|
||||
Input Flag (boolean agent input)
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
value={data.metadata?.optional?.conditions?.input_flag || ''}
|
||||
type="checkbox"
|
||||
id="check_skip_input"
|
||||
checked={data.metadata?.optional?.conditions?.check_skip_input !== false}
|
||||
onChange={(e) => {
|
||||
const conditions = data.metadata?.optional?.conditions || {};
|
||||
saveOptionalConditions({
|
||||
...conditions,
|
||||
input_flag: e.target.value || undefined,
|
||||
check_skip_input: e.target.checked,
|
||||
});
|
||||
}}
|
||||
placeholder="e.g., skip_linear"
|
||||
className="w-full p-2 border rounded dark:bg-gray-700 dark:text-white dark:border-gray-600"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<label htmlFor="check_skip_input" className="dark:text-gray-100">
|
||||
Add skip input handle (skip_run_block)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import { Node } from "@xyflow/react";
|
||||
import { CustomNodeData } from "@/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode";
|
||||
import type {
|
||||
CredentialsMetaInput,
|
||||
GraphMeta,
|
||||
@@ -17,6 +18,7 @@ interface RunInputDialogProps {
|
||||
isOpen: boolean;
|
||||
doClose: () => void;
|
||||
graph: GraphMeta;
|
||||
nodes?: Node<CustomNodeData>[];
|
||||
doRun?: (
|
||||
inputs: Record<string, any>,
|
||||
credentialsInputs: Record<string, CredentialsMetaInput>,
|
||||
@@ -33,6 +35,7 @@ export function RunnerInputDialog({
|
||||
isOpen,
|
||||
doClose,
|
||||
graph,
|
||||
nodes,
|
||||
doRun,
|
||||
doCreateSchedule,
|
||||
}: RunInputDialogProps) {
|
||||
@@ -79,6 +82,7 @@ export function RunnerInputDialog({
|
||||
<AgentRunDraftView
|
||||
className="p-0"
|
||||
graph={graph}
|
||||
nodes={nodes}
|
||||
doRun={doRun ? handleRun : undefined}
|
||||
onRun={doRun ? undefined : doClose}
|
||||
doCreateSchedule={doCreateSchedule ? handleSchedule : undefined}
|
||||
|
||||
@@ -98,6 +98,7 @@ const RunnerUIWrapper = forwardRef<RunnerUIWrapperRef, RunnerUIWrapperProps>(
|
||||
isOpen={isRunInputDialogOpen}
|
||||
doClose={() => setIsRunInputDialogOpen(false)}
|
||||
graph={graph}
|
||||
nodes={nodes}
|
||||
doRun={saveAndRun}
|
||||
doCreateSchedule={createRunSchedule}
|
||||
/>
|
||||
|
||||
@@ -43,9 +43,11 @@ import {
|
||||
|
||||
import { AgentStatus, AgentStatusChip } from "./agent-status-chip";
|
||||
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
|
||||
import { Node } from "@xyflow/react";
|
||||
|
||||
export function AgentRunDraftView({
|
||||
graph,
|
||||
nodes,
|
||||
agentPreset,
|
||||
doRun: _doRun,
|
||||
onRun,
|
||||
@@ -59,6 +61,7 @@ export function AgentRunDraftView({
|
||||
recommendedScheduleCron,
|
||||
}: {
|
||||
graph: GraphMeta;
|
||||
nodes?: Node<any>[];
|
||||
agentActions?: ButtonAction[];
|
||||
recommendedScheduleCron?: string | null;
|
||||
doRun?: (
|
||||
@@ -146,12 +149,82 @@ export function AgentRunDraftView({
|
||||
}, [agentInputSchema.required, inputValues]);
|
||||
const [allCredentialsAreSet, missingCredentials] = useMemo(() => {
|
||||
const availableCredentials = new Set(Object.keys(inputCredentials));
|
||||
const allCredentials = new Set(Object.keys(agentCredentialsInputFields));
|
||||
let allCredentials = new Set(Object.keys(agentCredentialsInputFields));
|
||||
|
||||
// Filter out credentials for optional blocks with on_missing_credentials
|
||||
if (nodes) {
|
||||
const optionalBlocksWithMissingCreds = nodes.filter(node => {
|
||||
const optional = node.data?.metadata?.optional;
|
||||
return optional?.enabled === true &&
|
||||
optional?.conditions?.on_missing_credentials === true;
|
||||
});
|
||||
|
||||
// If we have optional blocks that can skip on missing credentials,
|
||||
// we'll be more lenient with credential validation
|
||||
if (optionalBlocksWithMissingCreds.length > 0) {
|
||||
// Filter out credentials that might belong to optional blocks
|
||||
const filteredCredentials = new Set<string>();
|
||||
|
||||
for (const credKey of allCredentials) {
|
||||
let belongsToOptionalBlock = false;
|
||||
|
||||
// Check each optional block to see if it might use this credential
|
||||
for (const node of optionalBlocksWithMissingCreds) {
|
||||
// Check if the node's input schema has credential fields
|
||||
const credFields = node.data.inputSchema?.properties || {};
|
||||
|
||||
// Look for credential fields in the block's input schema
|
||||
for (const [fieldName, fieldSchema] of Object.entries(credFields)) {
|
||||
// Check if this is a credentials field (type checking)
|
||||
const isCredentialField =
|
||||
fieldName.toLowerCase().includes('credentials') ||
|
||||
fieldName.toLowerCase().includes('api_key') ||
|
||||
(fieldSchema && typeof fieldSchema === 'object' && fieldSchema !== null &&
|
||||
('credentials' in fieldSchema || 'oauth2' in fieldSchema));
|
||||
|
||||
if (isCredentialField) {
|
||||
|
||||
// Check if this credential key might match this block's needs
|
||||
const credKeyLower = credKey.toLowerCase();
|
||||
|
||||
// Match based on provider patterns in the key
|
||||
// e.g., "linear_api_key-oauth2_credentials" contains "linear"
|
||||
if (node.data.blockType.toLowerCase().includes('linear') &&
|
||||
credKeyLower.includes('linear')) {
|
||||
belongsToOptionalBlock = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Generic match - if the credential key contains the block type
|
||||
const blockTypeWords = node.data.blockType.toLowerCase()
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.split(/[\s_-]+/);
|
||||
|
||||
for (const word of blockTypeWords) {
|
||||
if (word.length > 3 && credKeyLower.includes(word)) {
|
||||
belongsToOptionalBlock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (belongsToOptionalBlock) break;
|
||||
}
|
||||
|
||||
if (!belongsToOptionalBlock) {
|
||||
filteredCredentials.add(credKey);
|
||||
}
|
||||
}
|
||||
allCredentials = filteredCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
availableCredentials.isSupersetOf(allCredentials),
|
||||
[...allCredentials.difference(availableCredentials)],
|
||||
];
|
||||
}, [agentCredentialsInputFields, inputCredentials]);
|
||||
}, [agentCredentialsInputFields, inputCredentials, nodes]);
|
||||
const notifyMissingInputs = useCallback(
|
||||
(needPresetName: boolean = true) => {
|
||||
const allMissingFields = (
|
||||
|
||||
@@ -4801,7 +4801,8 @@
|
||||
"RUNNING",
|
||||
"COMPLETED",
|
||||
"TERMINATED",
|
||||
"FAILED"
|
||||
"FAILED",
|
||||
"SKIPPED"
|
||||
],
|
||||
"title": "AgentExecutionStatus"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user