Compare commits

...

6 Commits

Author SHA1 Message Date
Zamil Majdy
f482eb668b hotfix(backend): resolve tool pin name mismatch in SmartDecisionMakerBlock (#11749)
## Root Cause

Execution a40bdb4a-964d-4684-94e8-b148eb6bcfc2 and all similar
executions have been failing since Nov 12, 2025 when tool pin routing
was refactored to use node IDs. The SmartDecisionMakerBlock was
double-sanitizing field names when emitting tool call outputs:

```python
# Original field name from link: "Max Keyword Difficulty"
original_field_name = field_mapping.get(clean_arg_name)  #  Retrieved correctly
sanitized_arg_name = self.cleanup(original_field_name)   #  Sanitized AGAIN!
emit_key = f"tools_^_{node_id}_~_{sanitized_arg_name}"   # Emits "max_keyword_difficulty"
```

But the parser expected original names from graph links:
```python
# Parser expects: "Max Keyword Difficulty" (from link.sink_name)
# Emit provides: "max_keyword_difficulty" (sanitized)
# Result: Mismatch → Tool never executes
```

### Changes 🏗️

**1. Fixed Emit Logic** (`smart_decision_maker.py` line 1135)
- Removed double sanitization: `sanitized_arg_name =
self.cleanup(original_field_name)`
- Now emits with original field names: `emit_key =
f"tools_^_{node_id}_~_{original_field_name}"`

**2. Made Agent Nodes Consistent** (`smart_decision_maker.py` lines
497-530)
- Added `field_mapping` to agent function signatures (was missing)
- Agent signatures now sanitize property keys for Anthropic API (like
block signatures)
- Stores field_mapping for use during emit

### Impact

**Fixes:**
-  All graphs with multi-word field names (e.g., "Max Keyword
Difficulty", "Minimum Volume")
-  All graphs with special characters in field names (e.g., "API-Key")
-  Both block nodes AND agent nodes now work consistently

**Unaffected:**
- Single-word lowercase field names (e.g., "keyword", "url") - these
were already working

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Verified parse_execution_output handles exact match correctly
  - [x] Verified emit uses original field names
  - [x] Verified field_mapping works for both block and agent nodes
- [x] Re-run execution a40bdb4a-964d-4684-94e8-b148eb6bcfc2 after
deployment to verify fix

#### For configuration changes:
- [x] `.env.default` is updated or already compatible with my changes
(no changes)
- [x] `docker-compose.yml` is updated or already compatible with my
changes (no changes)
- [x] No configuration changes in this PR

### Test Plan

1. **Unit test validation** (completed):
- Field name cleanup: "Max Keyword Difficulty" →
"max_keyword_difficulty" 
   - Parse with exact match: Success 
   - Parse with mismatch: Returns None 

2. **Production validation** (to be done after deployment):
   - Re-run execution a40bdb4a-964d-4684-94e8-b148eb6bcfc2
- Verify AgentExecutor (node 767682f5-694f-4b2a-bf52-fbdcad6a4a4f)
executes successfully
   - Verify execution completes with high correctness score (not 0.20)
   - Monitor for any regressions in existing graphs

### Files Changed

- `backend/blocks/smart_decision_maker.py`: Remove double sanitization,
add agent field_mapping

### Related Issues

- Resolves execution failure a40bdb4a-964d-4684-94e8-b148eb6bcfc2
- Fixes bug introduced in commit 536e2a5ec (Nov 12, 2025)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved field name mapping consistency in the SmartDecisionMaker
block to ensure proper handling of field names throughout function
signatures and tool execution workflows.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-12 02:08:12 +07:00
Bentlybro
fc8434fb30 Merge branch 'master' into dev 2026-01-07 12:02:15 +00:00
Ubbe
3ae08cd48e feat(frontend): use Google Drive Picker on new builder (#11702)
## Changes 🏗️

<img width="600" height="960" alt="Screenshot 2026-01-06 at 17 40 23"
src="https://github.com/user-attachments/assets/61085ec5-a367-45c7-acaa-e3fc0f0af647"
/>

- So when using Google Blocks on the new builder, it shows Google Drive
Picket 🏁

## Checklist 📋

### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
  - [x] Run app locally and test the above


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a Google Drive picker field and widget for forms with an
always-visible remove button and improved single/multi selection
handling.

* **Bug Fixes**
* Better validation and normalization of selected files and consolidated
error messaging.
* Adjusted layout spacing around the picker and selected files for
clearer display.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-07 17:07:09 +07:00
Swifty
4db13837b9 Revert "extracted frontend changes out of the hackathon/copilot branch"
This reverts commit df87867625.
2026-01-07 09:27:25 +01:00
Swifty
df87867625 extracted frontend changes out of the hackathon/copilot branch 2026-01-07 09:25:10 +01:00
Ubbe
4a7bc006a8 hotfix(frontend): chat should be disabled by default (#11639)
### Changes 🏗️

Chat should be disabled by default; otherwise, it flashes, and if Launch
Darkly fails to fail, it is dangerous.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Run locally with Launch Darkly disabled and test the above
2025-12-18 19:04:13 +01:00
9 changed files with 1649 additions and 2149 deletions

View File

@@ -495,8 +495,14 @@ class SmartDecisionMakerBlock(Block):
}
properties = {}
field_mapping = {}
for link in links:
field_name = link.sink_name
clean_field_name = SmartDecisionMakerBlock.cleanup(field_name)
field_mapping[clean_field_name] = field_name
sink_block_input_schema = sink_node.input_default["input_schema"]
sink_block_properties = sink_block_input_schema.get("properties", {}).get(
link.sink_name, {}
@@ -506,7 +512,7 @@ class SmartDecisionMakerBlock(Block):
if "description" in sink_block_properties
else f"The {link.sink_name} of the tool"
)
properties[link.sink_name] = {
properties[clean_field_name] = {
"type": "string",
"description": description,
"default": json.dumps(sink_block_properties.get("default", None)),
@@ -519,7 +525,7 @@ class SmartDecisionMakerBlock(Block):
"strict": True,
}
# Store node info for later use in output processing
tool_function["_field_mapping"] = field_mapping
tool_function["_sink_node_id"] = sink_node.id
return {"type": "function", "function": tool_function}
@@ -1129,8 +1135,9 @@ class SmartDecisionMakerBlock(Block):
original_field_name = field_mapping.get(clean_arg_name, clean_arg_name)
arg_value = tool_args.get(clean_arg_name)
sanitized_arg_name = self.cleanup(original_field_name)
emit_key = f"tools_^_{sink_node_id}_~_{sanitized_arg_name}"
# Use original_field_name directly (not sanitized) to match link sink_name
# The field_mapping already translates from LLM's cleaned names to original names
emit_key = f"tools_^_{sink_node_id}_~_{original_field_name}"
logger.debug(
"[SmartDecisionMakerBlock|geid:%s|neid:%s] emit %s",

View File

@@ -54,7 +54,7 @@
"@radix-ui/react-tooltip": "1.2.8",
"@rjsf/core": "6.1.2",
"@rjsf/utils": "6.1.2",
"@rjsf/validator-ajv8": "5.24.13",
"@rjsf/validator-ajv8": "6.1.2",
"@sentry/nextjs": "10.27.0",
"@supabase/ssr": "0.7.0",
"@supabase/supabase-js": "2.78.0",

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import { cn } from "@/lib/utils";
import { Cross2Icon } from "@radix-ui/react-icons";
import React, { useCallback } from "react";
import { GoogleDrivePicker } from "./GoogleDrivePicker";
import { isValidFile } from "./helpers";
export interface Props {
config: GoogleDrivePickerConfig;
@@ -27,13 +28,15 @@ export function GoogleDrivePickerInput({
const hasAutoCredentials = !!config.auto_credentials;
// Strip _credentials_id from value for display purposes
const currentFiles = isMultiSelect
? Array.isArray(value)
? value
: []
: value
? [value]
: [];
// Only show files section when there are valid file objects
const currentFiles = React.useMemo(() => {
if (isMultiSelect) {
if (!Array.isArray(value)) return [];
return value.filter(isValidFile);
}
if (!value || !isValidFile(value)) return [];
return [value];
}, [value, isMultiSelect]);
const handlePicked = useCallback(
(files: any[], credentialId?: string) => {
@@ -85,23 +88,27 @@ export function GoogleDrivePickerInput({
return (
<div className={cn("flex flex-col gap-2", className)}>
{/* Picker Button */}
<GoogleDrivePicker
multiselect={config.multiselect || false}
views={config.allowed_views || ["DOCS"]}
scopes={config.scopes || ["https://www.googleapis.com/auth/drive.file"]}
disabled={false}
requirePlatformCredentials={hasAutoCredentials}
onPicked={handlePicked}
onCanceled={() => {
// User canceled - no action needed
}}
onError={handleError}
/>
<div className="mb-4">
{/* Picker Button */}
<GoogleDrivePicker
multiselect={config.multiselect || false}
views={config.allowed_views || ["DOCS"]}
scopes={
config.scopes || ["https://www.googleapis.com/auth/drive.file"]
}
disabled={false}
requirePlatformCredentials={hasAutoCredentials}
onPicked={handlePicked}
onCanceled={() => {
// User canceled - no action needed
}}
onError={handleError}
/>
</div>
{/* Display Selected Files */}
{currentFiles.length > 0 && (
<div className="space-y-1">
<div className="mb-8 space-y-1">
{currentFiles.map((file: any, idx: number) => (
<div
key={file.id || idx}

View File

@@ -119,3 +119,14 @@ export function getCredentialsSchema(scopes: string[]) {
secret: true,
} satisfies BlockIOCredentialsSubSchema;
}
export function isValidFile(
file: unknown,
): file is { id?: string; name?: string } {
return (
typeof file === "object" &&
file !== null &&
(typeof (file as { id?: unknown }).id === "string" ||
typeof (file as { name?: unknown }).name === "string")
);
}

View File

@@ -4,11 +4,10 @@ import {
TemplatesType,
} from "@rjsf/utils";
import { AnyOfField } from "./anyof/AnyOfField";
import { AddButton, CopyButton, RemoveButton } from "./standard/buttons";
import {
ArrayFieldItemTemplate,
ArraySchemaField,
ArrayFieldTemplate,
ArraySchemaField,
} from "./array";
import {
ObjectFieldTemplate,
@@ -16,14 +15,16 @@ import {
WrapIfAdditionalTemplate,
} from "./object";
import { DescriptionField, FieldTemplate, TitleField } from "./standard";
import { AddButton, CopyButton, RemoveButton } from "./standard/buttons";
import {
TextWidget,
SelectWidget,
CheckboxWidget,
FileWidget,
DateWidget,
TimeWidget,
DateTimeWidget,
DateWidget,
FileWidget,
GoogleDrivePickerWidget,
SelectWidget,
TextWidget,
TimeWidget,
} from "./standard/widgets";
const NoButton = () => null;
@@ -65,5 +66,6 @@ export function generateBaseWidgets(): RegistryWidgetsType {
DateWidget,
TimeWidget,
DateTimeWidget,
GoogleDrivePickerWidget,
};
}

View File

@@ -0,0 +1,55 @@
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
import { getFieldErrorKey } from "@/components/renderers/InputRenderer/utils/helpers";
import type { GoogleDrivePickerConfig } from "@/lib/autogpt-server-api/types";
import { cn } from "@/lib/utils";
import { WidgetProps } from "@rjsf/utils";
function hasGoogleDrivePickerConfig(
schema: unknown,
): schema is { google_drive_picker_config?: GoogleDrivePickerConfig } {
return (
typeof schema === "object" &&
schema !== null &&
"google_drive_picker_config" in schema
);
}
export function GoogleDrivePickerWidget(props: WidgetProps) {
const { onChange, disabled, readonly, value, schema, id, formContext } =
props;
const { nodeId } = formContext || {};
const nodeErrors = useNodeStore((state) => {
const node = state.nodes.find((n) => n.id === nodeId);
return node?.data?.errors;
});
const fieldErrorKey = getFieldErrorKey(id ?? "");
const fieldError =
nodeErrors?.[fieldErrorKey] ||
nodeErrors?.[fieldErrorKey.replace(/_/g, ".")] ||
nodeErrors?.[fieldErrorKey.replace(/\./g, "_")] ||
undefined;
const config: GoogleDrivePickerConfig = hasGoogleDrivePickerConfig(schema)
? schema.google_drive_picker_config || {}
: {};
function handleChange(newValue: unknown) {
onChange(newValue);
}
return (
<GoogleDrivePickerInput
config={config}
value={value}
onChange={handleChange}
error={fieldError}
className={cn(
disabled || readonly ? "pointer-events-none opacity-50" : undefined,
)}
showRemoveButton={true}
/>
);
}

View File

@@ -0,0 +1 @@
export { GoogleDrivePickerWidget } from "./GoogleDrivePicketWidget";

View File

@@ -1,7 +1,8 @@
export { default as TextWidget } from "./TextInput";
export { SelectWidget } from "./SelectInput";
export { CheckboxWidget } from "./CheckboxInput";
export { FileWidget } from "./FileInput";
export { DateWidget } from "./DateInput";
export { TimeWidget } from "./TimeInput";
export { DateTimeWidget } from "./DateTimeInput";
export { FileWidget } from "./FileInput";
export { GoogleDrivePickerWidget } from "./GoogleDrivePicker";
export { SelectWidget } from "./SelectInput";
export { default as TextWidget } from "./TextInput";
export { TimeWidget } from "./TimeInput";