refactor(action-core): return Result for meta action lookups and adapt runtime callers

This commit is contained in:
2026-04-11 18:30:24 +08:00
parent 1b48e955bd
commit 2aae1b1f14
8 changed files with 321 additions and 40 deletions

View File

@@ -7,6 +7,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.exception.ActionLookupException;
import work.slhaf.partner.framework.agent.support.Result;
import work.slhaf.partner.module.action.executor.entity.HistoryAction;
import java.nio.file.Path;
@@ -331,4 +333,32 @@ class ActionCoreTest {
assertEquals(1, schedulableAction.getScheduleHistories().size());
assertEquals("cycle-result", schedulableAction.getScheduleHistories().getFirst().getResult());
}
@Test
void shouldReturnResultForMetaActionLookup() {
MetaActionInfo metaActionInfo = new MetaActionInfo(
true,
"python",
Map.of(),
"demo",
Set.of(),
Set.of(),
Set.of(),
false,
JSONObject.of()
);
actionCore.registerMetaActions(Map.of("builtin::demo", metaActionInfo));
Result<MetaAction> success = actionCore.loadMetaAction("builtin::demo");
assertTrue(success.isSuccess());
assertEquals("demo", success.getOrNull().getName());
Result<MetaAction> failure = actionCore.loadMetaAction("builtin::missing");
assertTrue(failure.isFailure());
assertInstanceOf(ActionLookupException.class, failure.exceptionOrNull());
Result<MetaActionInfo> infoFailure = actionCore.loadMetaActionInfo("builtin::missing");
assertTrue(infoFailure.isFailure());
assertInstanceOf(ActionLookupException.class, infoFailure.exceptionOrNull());
}
}

View File

@@ -9,6 +9,7 @@ import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextWorkspace;
import work.slhaf.partner.framework.agent.support.Result;
import work.slhaf.partner.module.action.executor.entity.ExtractorResult;
import work.slhaf.partner.module.action.executor.entity.HistoryAction;
@@ -121,7 +122,7 @@ class ActionExecutorTest {
when(actionCapability.getExecutor(ActionCore.ExecutorType.PLATFORM)).thenReturn(platformExecutor);
when(actionCapability.runnerClient()).thenReturn(runnerClient);
when(actionCapability.listActions(Action.Status.EXECUTING, null)).thenReturn(Set.of());
when(actionCapability.loadMetaActionInfo(anyString())).thenReturn(new MetaActionInfo(
when(actionCapability.loadMetaActionInfo(anyString())).thenReturn(Result.success(new MetaActionInfo(
false,
null,
Map.of(),
@@ -131,7 +132,7 @@ class ActionExecutorTest {
Set.of(),
false,
new com.alibaba.fastjson2.JSONObject()
));
)));
when(cognitionCapability.contextWorkspace()).thenReturn(new ContextWorkspace());
ExtractorResult extractorResult = new ExtractorResult();
@@ -186,6 +187,130 @@ class ActionExecutorTest {
assertEquals("rerun-ok", metaAction.getResult().getData());
}
@Test
void shouldMarkMetaActionFailedWhenMetaActionInfoLookupFailsBeforeExtraction() throws Exception {
ActionCapability actionCapability = Mockito.mock(ActionCapability.class);
CognitionCapability cognitionCapability = Mockito.mock(CognitionCapability.class);
ParamsExtractor paramsExtractor = Mockito.mock(ParamsExtractor.class);
ActionCorrector actionCorrector = Mockito.mock(ActionCorrector.class);
ActionCorrectionRecognizer actionCorrectionRecognizer = Mockito.mock(ActionCorrectionRecognizer.class);
RunnerClient runnerClient = Mockito.mock(RunnerClient.class);
ExecutorService virtualExecutor = registerExecutor(Executors.newFixedThreadPool(2));
ExecutorService platformExecutor = registerExecutor(Executors.newFixedThreadPool(2));
when(actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL)).thenReturn(virtualExecutor);
when(actionCapability.getExecutor(ActionCore.ExecutorType.PLATFORM)).thenReturn(platformExecutor);
when(actionCapability.runnerClient()).thenReturn(runnerClient);
when(actionCapability.listActions(Action.Status.EXECUTING, null)).thenReturn(Set.of());
when(actionCapability.loadMetaActionInfo(anyString())).thenReturn(Result.failure(new work.slhaf.partner.core.action.exception.ActionLookupException(
"missing",
"builtin::command",
"META_ACTION_INFO"
)));
when(cognitionCapability.contextWorkspace()).thenReturn(new ContextWorkspace());
ActionExecutor actionExecutor = new ActionExecutor();
inject(actionExecutor, "actionCapability", actionCapability);
inject(actionExecutor, "cognitionCapability", cognitionCapability);
inject(actionExecutor, "paramsExtractor", paramsExtractor);
inject(actionExecutor, "actionCorrector", actionCorrector);
inject(actionExecutor, "actionCorrectionRecognizer", actionCorrectionRecognizer);
actionExecutor.init();
MetaAction metaAction = metaAction("command");
ImmediateExecutableAction action = new ImmediateExecutableAction(
"urgent",
actionChain(metaAction),
"reason",
"desc",
"planner",
"lookup-fail-uuid"
);
actionExecutor.execute(action);
waitUntilFinished(action);
verify(paramsExtractor, never()).execute(any());
verify(runnerClient, never()).submit(any(MetaAction.class));
assertEquals(Action.Status.SUCCESS, action.getStatus());
assertEquals(MetaAction.Result.Status.FAILED, metaAction.getResult().getStatus());
assertTrue(metaAction.getResult().getData().contains("参数提取失败"));
assertEquals(metaAction.getResult().getData(), action.getResult());
}
@Test
void shouldFallbackToActionKeyWhenHistoryDescriptionLookupFails() throws Exception {
ActionCapability actionCapability = Mockito.mock(ActionCapability.class);
CognitionCapability cognitionCapability = Mockito.mock(CognitionCapability.class);
ParamsExtractor paramsExtractor = Mockito.mock(ParamsExtractor.class);
ActionCorrector actionCorrector = Mockito.mock(ActionCorrector.class);
ActionCorrectionRecognizer actionCorrectionRecognizer = Mockito.mock(ActionCorrectionRecognizer.class);
RunnerClient runnerClient = Mockito.mock(RunnerClient.class);
ExecutorService virtualExecutor = registerExecutor(Executors.newFixedThreadPool(2));
ExecutorService platformExecutor = registerExecutor(Executors.newFixedThreadPool(2));
when(actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL)).thenReturn(virtualExecutor);
when(actionCapability.getExecutor(ActionCore.ExecutorType.PLATFORM)).thenReturn(platformExecutor);
when(actionCapability.runnerClient()).thenReturn(runnerClient);
when(actionCapability.listActions(Action.Status.EXECUTING, null)).thenReturn(Set.of());
when(cognitionCapability.contextWorkspace()).thenReturn(new ContextWorkspace());
when(actionCapability.loadMetaActionInfo(anyString()))
.thenReturn(Result.success(new MetaActionInfo(
false,
null,
Map.of(),
"demo action",
Set.of(),
Set.of(),
Set.of(),
false,
new com.alibaba.fastjson2.JSONObject()
)))
.thenReturn(Result.failure(new work.slhaf.partner.core.action.exception.ActionLookupException(
"missing desc",
"builtin::command",
"META_ACTION_INFO"
)));
ExtractorResult extractorResult = new ExtractorResult();
extractorResult.setOk(true);
extractorResult.setParams(Map.of("fresh", "value"));
when(paramsExtractor.execute(any())).thenReturn(extractorResult);
doAnswer(invocation -> {
MetaAction metaAction = invocation.getArgument(0);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
metaAction.getResult().setData("history-ok");
return null;
}).when(runnerClient).submit(any(MetaAction.class));
ActionExecutor actionExecutor = new ActionExecutor();
inject(actionExecutor, "actionCapability", actionCapability);
inject(actionExecutor, "cognitionCapability", cognitionCapability);
inject(actionExecutor, "paramsExtractor", paramsExtractor);
inject(actionExecutor, "actionCorrector", actionCorrector);
inject(actionExecutor, "actionCorrectionRecognizer", actionCorrectionRecognizer);
actionExecutor.init();
MetaAction metaAction = metaAction("command");
ImmediateExecutableAction action = new ImmediateExecutableAction(
"urgent",
actionChain(metaAction),
"reason",
"desc",
"planner",
"history-fallback-uuid"
);
actionExecutor.execute(action);
waitUntilFinished(action);
assertEquals(Action.Status.SUCCESS, action.getStatus());
assertEquals("builtin::command", action.getHistory().get(1).getFirst().description());
}
private void waitUntilFinished(ImmediateExecutableAction action) throws InterruptedException {
long deadline = System.currentTimeMillis() + 3000;
while (System.currentTimeMillis() < deadline) {