diff --git a/easy-agents-flow/pom.xml b/easy-agents-flow/pom.xml
index 59bd45a..ac8ff6c 100644
--- a/easy-agents-flow/pom.xml
+++ b/easy-agents-flow/pom.xml
@@ -62,6 +62,12 @@
77.1
+
+ junit
+ junit
+ test
+
+
diff --git a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java
index ea8b93e..be2fe97 100644
--- a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java
+++ b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java
@@ -220,6 +220,7 @@ public class Chain {
if (variables != null && !variables.isEmpty()) {
state.getMemory().putAll(variables);
+ applyStartParameterAliases(state.getMemory(), variables);
fields.add(ChainStateField.MEMORY);
}
@@ -245,6 +246,48 @@ public class Chain {
}
}
+ /**
+ * 为开始节点输入参数补齐 `nodeId.paramName` 与 `paramName` 双向别名。
+ * 这样既兼容运行表单仅提交裸参数名,也兼容设计器内部统一保存完整引用路径。
+ *
+ * @param memory 流程内存
+ * @param variables 本次注入的变量
+ */
+ public void applyStartParameterAliases(Map memory, Map variables) {
+ if (memory == null || variables == null || variables.isEmpty()) {
+ return;
+ }
+ List startNodes = definition == null ? Collections.emptyList() : definition.getStartNodes();
+ if (startNodes == null || startNodes.isEmpty()) {
+ return;
+ }
+ for (Node startNode : startNodes) {
+ if (startNode == null || StringUtil.noText(startNode.getId())) {
+ continue;
+ }
+ List parameters = startNode.getParameters();
+ if (parameters == null || parameters.isEmpty()) {
+ continue;
+ }
+ for (Parameter parameter : parameters) {
+ if (parameter == null || parameter.getRefType() != RefType.INPUT || StringUtil.noText(parameter.getName())) {
+ continue;
+ }
+ String parameterName = parameter.getName().trim();
+ String scopedName = startNode.getId() + "." + parameterName;
+ Object plainValue = variables.get(parameterName);
+ Object scopedValue = variables.get(scopedName);
+
+ if (plainValue != null && !memory.containsKey(scopedName)) {
+ memory.put(scopedName, plainValue);
+ }
+ if (scopedValue != null && !memory.containsKey(parameterName)) {
+ memory.put(parameterName, scopedValue);
+ }
+ }
+ }
+ }
+
public void executeNode(Node node, Trigger trigger) {
try {
EXECUTION_THREAD_LOCAL.set(this);
@@ -745,4 +788,4 @@ public class Chain {
public void setStateInstanceId(String stateInstanceId) {
this.stateInstanceId = stateInstanceId;
}
-}
\ No newline at end of file
+}
diff --git a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java
index 938671e..3f6bdb2 100644
--- a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java
+++ b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java
@@ -156,6 +156,7 @@ public class ChainExecutor {
if (variables != null && !variables.isEmpty()) {
temp.updateStateSafely(s -> {
s.getMemory().putAll(variables);
+ temp.applyStartParameterAliases(s.getMemory(), variables);
return EnumSet.of(ChainStateField.MEMORY);
});
}
diff --git a/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java b/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java
index 8d33f56..eebc8b1 100644
--- a/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java
+++ b/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java
@@ -226,6 +226,14 @@ public class TextTemplate {
*/
private Object getValueByJsonPath(Map root, String path, boolean escapeForJsonOutput) {
try {
+ Object directValue = MapUtil.getByPath(root, path);
+ if (directValue != null) {
+ if (escapeForJsonOutput && directValue instanceof String) {
+ return escapeJsonString((String) directValue);
+ }
+ return directValue;
+ }
+
String fullPath = path.startsWith("$") ? path : "$." + path;
JSONPath compiled = MapUtil.computeIfAbsent(JSONPATH_CACHE, fullPath, JSONPath::compile);
Object value = compiled.eval(root);
diff --git a/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/ChainStartParameterAliasTest.java b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/ChainStartParameterAliasTest.java
new file mode 100644
index 0000000..ad4bfdf
--- /dev/null
+++ b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/ChainStartParameterAliasTest.java
@@ -0,0 +1,60 @@
+package com.easyagents.flow.core.test;
+
+import com.easyagents.flow.core.chain.Chain;
+import com.easyagents.flow.core.chain.ChainDefinition;
+import com.easyagents.flow.core.chain.Parameter;
+import com.easyagents.flow.core.chain.RefType;
+import com.easyagents.flow.core.node.StartNode;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 验证开始节点输入参数在运行时会同时写入裸参数名与 `nodeId.paramName` 别名。
+ */
+public class ChainStartParameterAliasTest {
+
+ @Test
+ public void shouldCreateScopedAliasFromPlainStartInput() {
+ Chain chain = createChain();
+ Map memory = new HashMap<>();
+ Map variables = new HashMap<>();
+ variables.put("user_input", "hello");
+
+ chain.applyStartParameterAliases(memory, variables);
+
+ Assert.assertEquals("hello", memory.get("start_1.user_input"));
+ Assert.assertNull(memory.get("user_input"));
+ }
+
+ @Test
+ public void shouldCreatePlainAliasFromScopedStartInput() {
+ Chain chain = createChain();
+ Map memory = new HashMap<>();
+ Map variables = new HashMap<>();
+ variables.put("start_1.user_input", "hello");
+
+ chain.applyStartParameterAliases(memory, variables);
+
+ Assert.assertEquals("hello", memory.get("user_input"));
+ Assert.assertNull(memory.get("start_1.user_input"));
+ }
+
+ private Chain createChain() {
+ ChainDefinition definition = new ChainDefinition();
+ StartNode startNode = new StartNode();
+ startNode.setId("start_1");
+
+ Parameter parameter = new Parameter();
+ parameter.setName("user_input");
+ parameter.setRefType(RefType.INPUT);
+ startNode.setParameters(java.util.Collections.singletonList(parameter));
+
+ definition.addNode(startNode);
+ definition.setEdges(Collections.emptyList());
+ return new Chain(definition, "state_1");
+ }
+}
diff --git a/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/TextTemplatePathTest.java b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/TextTemplatePathTest.java
new file mode 100644
index 0000000..e47427d
--- /dev/null
+++ b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/TextTemplatePathTest.java
@@ -0,0 +1,24 @@
+package com.easyagents.flow.core.test;
+
+import com.easyagents.flow.core.util.TextTemplate;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 验证文本模板能解析带点号的扁平 key,例如 `nodeId.paramName`。
+ */
+public class TextTemplatePathTest {
+
+ @Test
+ public void shouldResolveFlatScopedKeyBeforeJsonPathFallback() {
+ Map parameters = new HashMap<>();
+ parameters.put("node_1.user_input", "你好啊");
+
+ String result = TextTemplate.of("{{node_1.user_input}}").formatToString(parameters);
+
+ Assert.assertEquals("你好啊", result);
+ }
+}