- System credentials
+ System
+ credentials
{hasMissingSystemCredentials && (
(missing)
)}
@@ -163,7 +171,7 @@ export function CredentialsGroupedView({
}
selectedCredentials={selectedCred}
onSelectCredentials={(value) => {
- setInputCredentialsValue(key, value);
+ onCredentialChange(key, value);
}}
siblingInputs={inputValues}
isOptional={!requiredCredentials.has(key)}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/helpers.ts b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts
similarity index 98%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/helpers.ts
rename to autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts
index 72f0fcb451..519ef302c1 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/helpers.ts
+++ b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts
@@ -1,5 +1,5 @@
import { CredentialsProvidersContextType } from "@/providers/agent-credentials/credentials-provider";
-import { getSystemCredentials } from "../../../../../../../../../../../components/contextual/CredentialsInput/helpers";
+import { getSystemCredentials } from "../../helpers";
export type CredentialField = [string, any];
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx
index 347f4e089a..b2f71b14ed 100644
--- a/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx
@@ -35,12 +35,13 @@ export const CredentialFieldTitle = (props: {
uiOptions,
);
- const credentialProvider = toDisplayName(
- getCredentialProviderFromSchema(
- useNodeStore.getState().getHardCodedValues(nodeId),
- schema as BlockIOCredentialsSubSchema,
- ) ?? "",
+ const provider = getCredentialProviderFromSchema(
+ useNodeStore.getState().getHardCodedValues(nodeId),
+ schema as BlockIOCredentialsSubSchema,
);
+ const credentialProvider = provider
+ ? `${toDisplayName(provider)} credential`
+ : "credential";
const updatedUiSchema = updateUiOption(uiSchema, {
showHandles: false,
diff --git a/autogpt_platform/frontend/src/hooks/useAgentSafeMode.ts b/autogpt_platform/frontend/src/hooks/useAgentSafeMode.ts
index 07a2b33674..8e5560ce8f 100644
--- a/autogpt_platform/frontend/src/hooks/useAgentSafeMode.ts
+++ b/autogpt_platform/frontend/src/hooks/useAgentSafeMode.ts
@@ -20,11 +20,15 @@ function hasHITLBlocks(graph: GraphModel | LibraryAgent | Graph): boolean {
if ("has_human_in_the_loop" in graph) {
return !!graph.has_human_in_the_loop;
}
+ return false;
+}
- if (isLibraryAgent(graph)) {
- return graph.settings?.human_in_the_loop_safe_mode !== null;
+function hasSensitiveActionBlocks(
+ graph: GraphModel | LibraryAgent | Graph,
+): boolean {
+ if ("has_sensitive_action" in graph) {
+ return !!graph.has_sensitive_action;
}
-
return false;
}
@@ -40,7 +44,9 @@ export function useAgentSafeMode(graph: GraphModel | LibraryAgent | Graph) {
const graphId = getGraphId(graph);
const isAgent = isLibraryAgent(graph);
- const shouldShowToggle = hasHITLBlocks(graph);
+ const showHITLToggle = hasHITLBlocks(graph);
+ const showSensitiveActionToggle = hasSensitiveActionBlocks(graph);
+ const shouldShowToggle = showHITLToggle || showSensitiveActionToggle;
const { mutateAsync: updateGraphSettings, isPending } =
usePatchV1UpdateGraphSettings();
@@ -56,27 +62,37 @@ export function useAgentSafeMode(graph: GraphModel | LibraryAgent | Graph) {
},
);
- const [localSafeMode, setLocalSafeMode] = useState(null);
+ const [localHITLSafeMode, setLocalHITLSafeMode] = useState(true);
+ const [localSensitiveActionSafeMode, setLocalSensitiveActionSafeMode] =
+ useState(false);
+ const [isLocalStateLoaded, setIsLocalStateLoaded] = useState(false);
useEffect(() => {
if (!isAgent && libraryAgent) {
- const backendValue = libraryAgent.settings?.human_in_the_loop_safe_mode;
- if (backendValue !== undefined) {
- setLocalSafeMode(backendValue);
- }
+ setLocalHITLSafeMode(
+ libraryAgent.settings?.human_in_the_loop_safe_mode ?? true,
+ );
+ setLocalSensitiveActionSafeMode(
+ libraryAgent.settings?.sensitive_action_safe_mode ?? false,
+ );
+ setIsLocalStateLoaded(true);
}
}, [isAgent, libraryAgent]);
- const currentSafeMode = isAgent
- ? graph.settings?.human_in_the_loop_safe_mode
- : localSafeMode;
+ const currentHITLSafeMode = isAgent
+ ? (graph.settings?.human_in_the_loop_safe_mode ?? true)
+ : localHITLSafeMode;
- const isStateUndetermined = isAgent
- ? graph.settings?.human_in_the_loop_safe_mode == null
- : isLoading || localSafeMode === null;
+ const currentSensitiveActionSafeMode = isAgent
+ ? (graph.settings?.sensitive_action_safe_mode ?? false)
+ : localSensitiveActionSafeMode;
- const handleToggle = useCallback(async () => {
- const newSafeMode = !currentSafeMode;
+ const isHITLStateUndetermined = isAgent
+ ? false
+ : isLoading || !isLocalStateLoaded;
+
+ const handleHITLToggle = useCallback(async () => {
+ const newSafeMode = !currentHITLSafeMode;
try {
await updateGraphSettings({
@@ -85,7 +101,7 @@ export function useAgentSafeMode(graph: GraphModel | LibraryAgent | Graph) {
});
if (!isAgent) {
- setLocalSafeMode(newSafeMode);
+ setLocalHITLSafeMode(newSafeMode);
}
if (isAgent) {
@@ -101,37 +117,62 @@ export function useAgentSafeMode(graph: GraphModel | LibraryAgent | Graph) {
queryClient.invalidateQueries({ queryKey: ["v2", "executions"] });
toast({
- title: `Safe mode ${newSafeMode ? "enabled" : "disabled"}`,
+ title: `HITL safe mode ${newSafeMode ? "enabled" : "disabled"}`,
description: newSafeMode
? "Human-in-the-loop blocks will require manual review"
: "Human-in-the-loop blocks will proceed automatically",
duration: 2000,
});
} catch (error) {
- const isNotFoundError =
- error instanceof Error &&
- (error.message.includes("404") || error.message.includes("not found"));
-
- if (!isAgent && isNotFoundError) {
- toast({
- title: "Safe mode not available",
- description:
- "To configure safe mode, please save this graph to your library first.",
- variant: "destructive",
- });
- } else {
- toast({
- title: "Failed to update safe mode",
- description:
- error instanceof Error
- ? error.message
- : "An unexpected error occurred.",
- variant: "destructive",
- });
- }
+ handleToggleError(error, isAgent, toast);
}
}, [
- currentSafeMode,
+ currentHITLSafeMode,
+ graphId,
+ isAgent,
+ graph.id,
+ updateGraphSettings,
+ queryClient,
+ toast,
+ ]);
+
+ const handleSensitiveActionToggle = useCallback(async () => {
+ const newSafeMode = !currentSensitiveActionSafeMode;
+
+ try {
+ await updateGraphSettings({
+ graphId,
+ data: { sensitive_action_safe_mode: newSafeMode },
+ });
+
+ if (!isAgent) {
+ setLocalSensitiveActionSafeMode(newSafeMode);
+ }
+
+ if (isAgent) {
+ queryClient.invalidateQueries({
+ queryKey: getGetV2GetLibraryAgentQueryOptions(graph.id.toString())
+ .queryKey,
+ });
+ }
+
+ queryClient.invalidateQueries({
+ queryKey: ["v1", "graphs", graphId, "executions"],
+ });
+ queryClient.invalidateQueries({ queryKey: ["v2", "executions"] });
+
+ toast({
+ title: `Sensitive action safe mode ${newSafeMode ? "enabled" : "disabled"}`,
+ description: newSafeMode
+ ? "Sensitive action blocks will require manual review"
+ : "Sensitive action blocks will proceed automatically",
+ duration: 2000,
+ });
+ } catch (error) {
+ handleToggleError(error, isAgent, toast);
+ }
+ }, [
+ currentSensitiveActionSafeMode,
graphId,
isAgent,
graph.id,
@@ -141,11 +182,53 @@ export function useAgentSafeMode(graph: GraphModel | LibraryAgent | Graph) {
]);
return {
- currentSafeMode,
+ // HITL safe mode
+ currentHITLSafeMode,
+ showHITLToggle,
+ isHITLStateUndetermined,
+ handleHITLToggle,
+
+ // Sensitive action safe mode
+ currentSensitiveActionSafeMode,
+ showSensitiveActionToggle,
+ handleSensitiveActionToggle,
+
+ // General
isPending,
shouldShowToggle,
- isStateUndetermined,
- handleToggle,
- hasHITLBlocks: shouldShowToggle,
+
+ // Backwards compatibility
+ currentSafeMode: currentHITLSafeMode,
+ isStateUndetermined: isHITLStateUndetermined,
+ handleToggle: handleHITLToggle,
+ hasHITLBlocks: showHITLToggle,
};
}
+
+function handleToggleError(
+ error: unknown,
+ isAgent: boolean,
+ toast: ReturnType["toast"],
+) {
+ const isNotFoundError =
+ error instanceof Error &&
+ (error.message.includes("404") || error.message.includes("not found"));
+
+ if (!isAgent && isNotFoundError) {
+ toast({
+ title: "Safe mode not available",
+ description:
+ "To configure safe mode, please save this graph to your library first.",
+ variant: "destructive",
+ });
+ } else {
+ toast({
+ title: "Failed to update safe mode",
+ description:
+ error instanceof Error
+ ? error.message
+ : "An unexpected error occurred.",
+ variant: "destructive",
+ });
+ }
+}
diff --git a/autogpt_platform/frontend/src/lib/dexie/draft-utils.ts b/autogpt_platform/frontend/src/lib/dexie/draft-utils.ts
index 03232ede30..2a212a3f35 100644
--- a/autogpt_platform/frontend/src/lib/dexie/draft-utils.ts
+++ b/autogpt_platform/frontend/src/lib/dexie/draft-utils.ts
@@ -5,7 +5,7 @@ import isEqual from "lodash/isEqual";
export function cleanNode(node: CustomNode) {
return {
id: node.id,
- position: node.position,
+ // Note: position is intentionally excluded to prevent draft saves when dragging nodes
data: {
hardcodedValues: node.data.hardcodedValues,
title: node.data.title,
diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md
new file mode 100644
index 0000000000..67cb1fc4f3
--- /dev/null
+++ b/docs/CLAUDE.md
@@ -0,0 +1,44 @@
+# Documentation Guidelines
+
+## Block Documentation Manual Sections
+
+When updating manual sections (``) in block documentation files (e.g., `docs/integrations/basic.md`), follow these formats:
+
+### How It Works Section
+
+Provide a technical explanation of how the block functions:
+- Describe the processing logic in 1-2 paragraphs
+- Mention any validation, error handling, or edge cases
+- Use code examples with backticks when helpful (e.g., `[[1, 2], [3, 4]]` becomes `[1, 2, 3, 4]`)
+
+Example:
+```markdown
+
+The block iterates through each list in the input and extends a result list with all elements from each one. It processes lists in order, so `[[1, 2], [3, 4]]` becomes `[1, 2, 3, 4]`.
+
+The block includes validation to ensure each item is actually a list. If a non-list value is encountered, the block outputs an error message instead of proceeding.
+
+```
+
+### Use Case Section
+
+Provide 3 practical use cases in this format:
+- **Bold Heading**: Short one-sentence description
+
+Example:
+```markdown
+
+**Paginated API Merging**: Combine results from multiple API pages into a single list for batch processing or display.
+
+**Parallel Task Aggregation**: Merge outputs from parallel workflow branches that each produce a list of results.
+
+**Multi-Source Data Collection**: Combine data collected from different sources (like multiple RSS feeds or API endpoints) into one unified list.
+
+```
+
+### Style Guidelines
+
+- Keep descriptions concise and action-oriented
+- Focus on practical, real-world scenarios
+- Use consistent terminology with other blocks
+- Avoid overly technical jargon unless necessary
diff --git a/docs/integrations/README.md b/docs/integrations/README.md
index f954ac530f..e444757a49 100644
--- a/docs/integrations/README.md
+++ b/docs/integrations/README.md
@@ -31,6 +31,7 @@ Below is a comprehensive list of all available blocks, categorized by their prim
| [Agent Time Input](basic.md#agent-time-input) | Block for time input |
| [Agent Toggle Input](basic.md#agent-toggle-input) | Block for boolean toggle input |
| [Block Installation](basic.md#block-installation) | Given a code string, this block allows the verification and installation of a block code into the system |
+| [Concatenate Lists](basic.md#concatenate-lists) | Concatenates multiple lists into a single list |
| [Dictionary Is Empty](basic.md#dictionary-is-empty) | Checks if a dictionary is empty |
| [File Store](basic.md#file-store) | Stores the input file in the temporary directory |
| [Find In Dictionary](basic.md#find-in-dictionary) | A block that looks up a value in a dictionary, list, or object by key or index and returns the corresponding value |
diff --git a/docs/integrations/basic.md b/docs/integrations/basic.md
index 367299ffa7..f92d19002f 100644
--- a/docs/integrations/basic.md
+++ b/docs/integrations/basic.md
@@ -634,6 +634,42 @@ This enables extensibility by allowing custom blocks to be added without modifyi
---
+## Concatenate Lists
+
+### What it is
+Concatenates multiple lists into a single list. All elements from all input lists are combined in order.
+
+### How it works
+
+The block iterates through each list in the input and extends a result list with all elements from each one. It processes lists in order, so `[[1, 2], [3, 4]]` becomes `[1, 2, 3, 4]`.
+
+The block includes validation to ensure each item is actually a list. If a non-list value (like a string or number) is encountered, the block outputs an error message instead of proceeding. None values are skipped automatically.
+
+
+### Inputs
+
+| Input | Description | Type | Required |
+|-------|-------------|------|----------|
+| lists | A list of lists to concatenate together. All lists will be combined in order into a single list. | List[List[Any]] | Yes |
+
+### Outputs
+
+| Output | Description | Type |
+|--------|-------------|------|
+| error | Error message if concatenation failed due to invalid input types. | str |
+| concatenated_list | The concatenated list containing all elements from all input lists in order. | List[Any] |
+
+### Possible use case
+
+**Paginated API Merging**: Combine results from multiple API pages into a single list for batch processing or display.
+
+**Parallel Task Aggregation**: Merge outputs from parallel workflow branches that each produce a list of results.
+
+**Multi-Source Data Collection**: Combine data collected from different sources (like multiple RSS feeds or API endpoints) into one unified list.
+
+
+---
+
## Dictionary Is Empty
### What it is