diff --git a/agent-flow/src/common/Consts.js b/agent-flow/src/common/Consts.js
index d929a08d3a..22369cda70 100644
--- a/agent-flow/src/common/Consts.js
+++ b/agent-flow/src/common/Consts.js
@@ -10,6 +10,7 @@ export const NODE_STATUS = {
RUNNING: 'RUNNING',
ERROR: 'ERROR',
SUCCESS: 'ARCHIVED',
+ SKIPPED: 'SKIPPED',
DEFAULT: 'DEFAULT',
UN_RUNNING: 'UN_RUNNING',
TERMINATED: 'TERMINATED',
@@ -287,4 +288,4 @@ export const DEFAULT_KNOWLEDGE_RETRIEVAL_NODE_EXTENSIONS = {
referenceNode: VIRTUAL_CONTEXT_NODE.id,
value: [VIRTUAL_CONTEXT_NODE_VARIABLES.APP_CREATE_BY],
}],
-};
\ No newline at end of file
+};
diff --git a/agent-flow/src/components/flowRunComponent/RunningStatusPanel.jsx b/agent-flow/src/components/flowRunComponent/RunningStatusPanel.jsx
index 4efd23b905..45141ba038 100644
--- a/agent-flow/src/components/flowRunComponent/RunningStatusPanel.jsx
+++ b/agent-flow/src/components/flowRunComponent/RunningStatusPanel.jsx
@@ -143,9 +143,16 @@ SvgComponent.propTypes = {
};
const TimeComponent = ({shape, runStatus}) => {
+ const statusClassMap = {
+ [NODE_STATUS.SUCCESS]: 'success',
+ [NODE_STATUS.ERROR]: 'failed',
+ [NODE_STATUS.RUNNING]: 'running',
+ [NODE_STATUS.SKIPPED]: 'skipped',
+ };
+ const statusClass = statusClassMap[runStatus];
return (<>
-
-
+
+
{shape.cost ? (shape.cost / 1000) + "s" : "0.000s"}
@@ -182,6 +189,17 @@ const getStatus = (nodeStatus) => {
return
}/>;
},
};
+ case NODE_STATUS.SKIPPED:
+ return {
+ title: t('skipped'),
+ enableReport: true,
+ getTime: (shape) => {
+ return ;
+ },
+ getIcon: () => {
+ return }/>;
+ },
+ };
case NODE_STATUS.RUNNING:
return {
title: t('running'),
@@ -222,4 +240,4 @@ const getStatus = (nodeStatus) => {
}
};
-export default RunningStatusPanel;
\ No newline at end of file
+export default RunningStatusPanel;
diff --git a/agent-flow/src/components/flowRunComponent/style.css b/agent-flow/src/components/flowRunComponent/style.css
index ffe7b7c01a..06a1ba49c9 100644
--- a/agent-flow/src/components/flowRunComponent/style.css
+++ b/agent-flow/src/components/flowRunComponent/style.css
@@ -31,6 +31,10 @@
background-color: rgb(198, 57, 57, 0.2);
}
+.time-text-container-skipped {
+ background-color: rgba(107, 109, 117, 0.16);
+}
+
.time-text {
font-family: SF Pro Display, -apple-system, BlinkMacSystemFont, Segoe Ul, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 12px;
@@ -52,6 +56,10 @@
color: rgb(198, 57, 57);
}
+.time-text-skipped {
+ color: rgb(107, 109, 117);
+}
+
.button-text {
color: rgb(77, 77, 77);
font-family: SF Pro Display, -apple-system, BlinkMacSystemFont, Segoe Ul, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;
@@ -213,4 +221,4 @@
position: absolute;
border-radius: 16px;
background: rgba(197, 197, 197, 0.13);
-}
\ No newline at end of file
+}
diff --git a/agent-flow/src/flow/runners.js b/agent-flow/src/flow/runners.js
index 34b8dfc145..eff75a467e 100644
--- a/agent-flow/src/flow/runners.js
+++ b/agent-flow/src/flow/runners.js
@@ -75,7 +75,7 @@ export const standardRunner = (node) => {
return false;
}
const data = dataList.find(d => d.nodeId === preNode.id);
- return data && data.status === NODE_STATUS.SUCCESS;
+ return data && (data.status === NODE_STATUS.SUCCESS || data.status === NODE_STATUS.SKIPPED);
};
return self;
@@ -174,4 +174,4 @@ export const inactiveNodeRunner = (node) => {
};
return self;
-};
\ No newline at end of file
+};
diff --git a/agent-flow/src/i18n/en_US.json b/agent-flow/src/i18n/en_US.json
index 30b0717997..62d20a917c 100644
--- a/agent-flow/src/i18n/en_US.json
+++ b/agent-flow/src/i18n/en_US.json
@@ -123,6 +123,7 @@
"running": "Running",
"notRunning": "Not running",
"runFailed": "Failed",
+ "skipped": "Skipped",
"runResult": "Running Result",
"promptName": "Prompt Word",
"promptTextarea": "Use {{Variable name}} to add a variable",
diff --git a/agent-flow/src/i18n/zh_CN.json b/agent-flow/src/i18n/zh_CN.json
index 4e35a09a38..772203ac55 100644
--- a/agent-flow/src/i18n/zh_CN.json
+++ b/agent-flow/src/i18n/zh_CN.json
@@ -423,6 +423,7 @@
"running": "运行中",
"notRunning": "未运行",
"runFailed": "运行失败",
+ "skipped": "已跳过",
"runResult": "运行结果",
"closeRunResult": "收起运行结果",
"invalidCategory": "存在不合法的类目,请先修改",
diff --git a/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/domain/EndNodeStatus.java b/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/domain/EndNodeStatus.java
new file mode 100644
index 0000000000..fbd38cbc71
--- /dev/null
+++ b/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/domain/EndNodeStatus.java
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fit.jober.aipp.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * END节点状态领域对象.
+ *
+ * @author 张越
+ * @since 2026-04-01
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@SuperBuilder
+public class EndNodeStatus extends BaseDomain {
+ private Long id;
+ private String traceId;
+ private String endNodeId;
+ private String status;
+ private long startTime;
+ private long endTime;
+ private String instanceId;
+ private String flowDefinitionId;
+
+ /**
+ * 获取节点执行时间.
+ *
+ * @return 执行时间(毫秒).
+ */
+ public long getExecutionCost() {
+ return this.endTime - this.startTime;
+ }
+}
diff --git a/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fitable/AippFlowEndCallback.java b/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fitable/AippFlowEndCallback.java
index 5814971f02..a2ee3a43b4 100644
--- a/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fitable/AippFlowEndCallback.java
+++ b/app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fitable/AippFlowEndCallback.java
@@ -14,23 +14,30 @@
import modelengine.fit.jane.common.entity.OperationContext;
import modelengine.fit.jober.aipp.constants.AippConst;
import modelengine.fit.jober.aipp.domain.AppBuilderForm;
+import modelengine.fit.jober.aipp.domain.AppBuilderFlowGraph;
+import modelengine.fit.jober.aipp.domain.EndNodeStatus;
import modelengine.fit.jober.aipp.domains.appversion.AppVersion;
import modelengine.fit.jober.aipp.domains.appversion.service.AppVersionService;
import modelengine.fit.jober.aipp.domains.business.RunContext;
import modelengine.fit.jober.aipp.domains.task.AppTask;
import modelengine.fit.jober.aipp.domains.task.service.AppTaskService;
import modelengine.fit.jober.aipp.domains.taskinstance.AppTaskInstance;
+import modelengine.fit.jober.aipp.domains.taskinstance.TaskInstanceUpdateEntity;
import modelengine.fit.jober.aipp.domains.taskinstance.service.AppTaskInstanceService;
import modelengine.fit.jober.aipp.dto.chat.AppChatRsp;
+import modelengine.fit.jober.aipp.enums.NodeType;
import modelengine.fit.jober.aipp.entity.AippFlowData;
import modelengine.fit.jober.aipp.entity.AippLogData;
import modelengine.fit.jober.aipp.enums.AippInstLogType;
import modelengine.fit.jober.aipp.enums.MetaInstStatusEnum;
import modelengine.fit.jober.aipp.events.InsertConversationEnd;
import modelengine.fit.jober.aipp.genericable.AppFlowFinishObserver;
+import modelengine.fit.jober.aipp.repository.AppBuilderFlowGraphRepository;
+import modelengine.fit.jober.aipp.repository.EndNodeStatusRepository;
import modelengine.fit.jober.aipp.service.AippLogService;
import modelengine.fit.jober.aipp.service.AppBuilderFormService;
import modelengine.fit.jober.aipp.service.AppChatSseService;
+import modelengine.fit.jober.aipp.util.ConvertUtils;
import modelengine.fit.jober.aipp.util.DataUtils;
import modelengine.fit.jober.aipp.util.FormUtils;
import modelengine.fit.jober.aipp.util.JsonUtils;
@@ -38,6 +45,7 @@
import modelengine.fit.jober.common.exceptions.JobberException;
import modelengine.fit.waterflow.domain.enums.FlowTraceStatus;
import modelengine.fit.waterflow.spi.FlowCallbackService;
+import modelengine.fit.waterflow.spi.lock.DistributedLockProvider;
import modelengine.fitframework.annotation.Component;
import modelengine.fitframework.annotation.Fit;
import modelengine.fitframework.annotation.Fitable;
@@ -55,6 +63,9 @@
import modelengine.jade.app.engine.metrics.po.ConversationRecordPo;
import modelengine.jade.app.engine.metrics.service.ConversationRecordService;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
@@ -62,7 +73,11 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.stream.Collectors;
/**
* 流程结束回调节点
@@ -79,6 +94,7 @@ public class AippFlowEndCallback implements FlowCallbackService {
.put(Constant.DEFAULT, AippInstLogType.MSG)
.put(Constant.LLM_OUTPUT, AippInstLogType.META_MSG)
.build();
+ private static final String TERMINATE_LOCK_PREFIX = "aipp-terminate-lock:";
private final AippLogService aippLogService;
private final BrokerClient brokerClient;
@@ -91,12 +107,18 @@ public class AippFlowEndCallback implements FlowCallbackService {
private final AppTaskInstanceService appTaskInstanceService;
private final AppTaskService appTaskService;
private final AppVersionService appVersionService;
+ private final EndNodeStatusRepository endNodeStatusRepository;
+ private final AppBuilderFlowGraphRepository flowGraphRepository;
+ private final DistributedLockProvider distributedLockProvider;
public AippFlowEndCallback(@Fit AippLogService aippLogService, @Fit BrokerClient brokerClient,
@Fit BeanContainer beanContainer, @Fit ConversationRecordService conversationRecordService,
@Fit AppBuilderFormService formService, @Fit AppChatSseService appChatSseService,
@Fit OutputFormatterChain formatterChain, @Fit AppTaskInstanceService appTaskInstanceService,
- @Fit AppTaskService appTaskService, @Fit AppVersionService appVersionService, FitRuntime fitRuntime) {
+ @Fit AppTaskService appTaskService, @Fit AppVersionService appVersionService,
+ @Fit EndNodeStatusRepository endNodeStatusRepository,
+ @Fit AppBuilderFlowGraphRepository flowGraphRepository,
+ @Fit DistributedLockProvider distributedLockProvider, FitRuntime fitRuntime) {
this.formService = formService;
this.aippLogService = aippLogService;
this.brokerClient = brokerClient;
@@ -107,6 +129,9 @@ public AippFlowEndCallback(@Fit AippLogService aippLogService, @Fit BrokerClient
this.appTaskInstanceService = appTaskInstanceService;
this.appTaskService = appTaskService;
this.appVersionService = appVersionService;
+ this.endNodeStatusRepository = endNodeStatusRepository;
+ this.flowGraphRepository = flowGraphRepository;
+ this.distributedLockProvider = distributedLockProvider;
this.fitRuntime = fitRuntime;
}
@@ -125,14 +150,36 @@ public void callback(List