From 8cd12f73797053c89ec9b34baeee5e4aa8e5978a Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Mon, 23 Mar 2026 17:24:19 +0800 Subject: [PATCH] refactor(action): add resume/interrupt methods to ExecutableAction; add related definitions to support acquiring help from user --- .../partner/core/action/entity/Action.kt | 22 +++ .../BuiltinInterventionActionProvider.java | 143 +++++++++++++++++- 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/entity/Action.kt b/Partner-Core/src/main/java/work/slhaf/partner/core/action/entity/Action.kt index 07f59625..177e06cb 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/entity/Action.kt +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/entity/Action.kt @@ -1,6 +1,7 @@ package work.slhaf.partner.core.action.entity import work.slhaf.partner.module.modules.action.executor.entity.HistoryAction +import java.time.Instant import java.time.ZonedDateTime import java.util.* import kotlin.time.Duration @@ -112,6 +113,27 @@ sealed class ExecutableAction : Action() { val additionalContext: MutableMap> = mutableMapOf() override val timeout: Duration = 10.minutes + + /** + * @param timeout 最长打断时间 + * @return 是否超时结束 + */ + fun interrupt(timeout: Int): Boolean { + status = Status.INTERRUPTED + val interruptAt = Instant.now().epochSecond + + while (status == Status.INTERRUPTED) { + Thread.sleep(500); + if (Instant.now().epochSecond - interruptAt > timeout) { + return false + } + } + return true; + } + + fun resume() { + status = Status.EXECUTING + } } /** diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/builtin/BuiltinInterventionActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/builtin/BuiltinInterventionActionProvider.java index ea834437..e7d2b177 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/builtin/BuiltinInterventionActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/builtin/BuiltinInterventionActionProvider.java @@ -2,13 +2,21 @@ package work.slhaf.partner.module.modules.action.builtin; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.api.agent.factory.component.annotation.AgentComponent; import work.slhaf.partner.core.action.ActionCapability; +import work.slhaf.partner.core.action.entity.Action; import work.slhaf.partner.core.action.entity.ExecutableAction; import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.intervention.InterventionType; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; +import work.slhaf.partner.core.cognition.BlockContent; +import work.slhaf.partner.core.cognition.CognitionCapability; +import work.slhaf.partner.core.cognition.ContextBlock; +import work.slhaf.partner.core.cognition.ContextWorkspace; import java.util.*; import java.util.function.Function; @@ -24,16 +32,145 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { @InjectCapability private ActionCapability actionCapability; + @InjectCapability + private CognitionCapability cognitionCapability; @Override public List provideBuiltinActions() { return List.of( buildCreateInterventionDefinition(), buildShowAvailableMetaActionsDefinition(), - buildShowIntervenableActionsDefinition() + buildShowIntervenableActionsDefinition(), + buildAcquireInterventionDefinition(), + buildResumeInterruptedActionDefinition() ); } + private BuiltinActionRegistry.BuiltinActionDefinition buildResumeInterruptedActionDefinition() { + Set tags = new HashSet<>(basicTags); + tags.add("Agent Turn"); + tags.add("Action Management"); + + MetaActionInfo info = new MetaActionInfo( + false, + null, + Map.of( + "actionId", "Uuid of the interrupted executable action to resume." + ), + "Resume action that is interrupted.", + tags, + Set.of(), + Set.of(), + false, + JSONObject.of( + "result", "Plain text resume result, describes whether it is succeed or fail reason." + ) + ); + + Function, String> invoker = params -> { + String actionId = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "actionId"); + try { + ExecutableAction executableAction = getExecutableAction(actionId); + executableAction.resume(); + return "Resume succeed"; + } catch (Exception e) { + return "Failed to resume action[" + actionId + "], reason: " + e.getLocalizedMessage(); + } + }; + + return new BuiltinActionRegistry.BuiltinActionDefinition( + createActionKey("resume_interrupted_action"), + info, + invoker + ); + } + + + /** + * 尝试向指定用户请求干预,通过自对话通道 + * + * @return 内建 MetaAction 定义数据 + */ + private BuiltinActionRegistry.BuiltinActionDefinition buildAcquireInterventionDefinition() { + Set tags = new HashSet<>(basicTags); + tags.add("Agent Turn"); + tags.add("Action Management"); + + MetaActionInfo info = new MetaActionInfo( + true, + null, + Map.of( + "actionId", "Uuid of the executing action that should enter intervention flow.", + "actionInfo", "Readable summary of the current action, used to explain the intervention context to the target.", + "demand", "What feedback, decision or operation is required from the target user.", + "target", "Target user or channel identifier that should receive the intervention request.", + "input", "Prompt content used to initiate the intervention turn toward the target.", + "timeout", "Maximum wait time for the interruption result, in the unit expected by ExecutableAction.interrupt(timeout)." + ), + "Try to acquire the target user to intervene this Action.", + tags, + Set.of(), + Set.of(), + false, + JSONObject.of( + "result", "Plain text intervention status. It describes whether the target answered before timeout, or returns an error reason when the turn/interruption flow fails." + ) + ); + + Function, String> invoker = params -> { + String actionId = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "actionId").trim(); + String actionInfo = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "actionInfo").trim(); + String demand = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "demand").trim(); + String target = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "target").trim(); + String input = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "input").trim(); + int timeout = BuiltinActionRegistry.BuiltinActionDefinition.requireInt(params, "timeout"); + + ContextWorkspace contextWorkspace = cognitionCapability.contextWorkspace(); + String blockName = "acquire_intervention-" + actionId; + String source = "action_executor"; + contextWorkspace.register(new ContextBlock( + new BlockContent(blockName, source) { + @Override + protected void fillXml(@NotNull Document document, @NotNull Element root) { + appendTextElement(document, root, "action_id", actionId); + appendTextElement(document, root, "action_info", actionInfo); + appendTextElement(document, root, "demand", demand); + } + }, + Set.of(ContextBlock.VisibleDomain.ACTION, ContextBlock.VisibleDomain.COMMUNICATION), + 10, + 10, + 20 + )); + + + try { + ExecutableAction executableAction = getExecutableAction(actionId); + cognitionCapability.initiateTurn(input, target); + boolean normal = executableAction.interrupt(timeout); + return normal ? target + "not answered" : target + "answered"; + } catch (Exception e) { + return "Error happened while calling turn: " + e.getLocalizedMessage(); + } finally { + contextWorkspace.expire(blockName, source); + } + }; + + return new BuiltinActionRegistry.BuiltinActionDefinition( + createActionKey("acquire_intervention"), + info, + invoker + ); + } + + private ExecutableAction getExecutableAction(String actionId) { + return actionCapability.listActions(Action.Status.EXECUTING, null) + .stream() + .filter(action -> action.getUuid().equals(actionId)) + .findFirst() + .orElseThrow(); + } + /** * 用于展示当前已存在的可被干预的行动 * @@ -175,6 +312,10 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { intervention.setActions(actions); actionCapability.handleInterventions(List.of(intervention), target); + + ExecutableAction executableAction = getExecutableAction(targetId); + executableAction.resume(); + return JSONObject.of("ok", true).toJSONString(); };