refactor(MetaAction): migrate to Kotlin data class, merge MetaActionType/ResultStatus into nested enums, and update runner/action usages

This commit is contained in:
2026-02-08 17:15:58 +08:00
parent 6ba5784a7f
commit 7dd2104689
12 changed files with 112 additions and 112 deletions

View File

@@ -8,7 +8,10 @@ import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore
import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod;
import work.slhaf.partner.common.vector.VectorClient;
import work.slhaf.partner.core.PartnerCore;
import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.entity.ActionData;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.PhaserRecord;
import work.slhaf.partner.core.action.entity.cache.ActionCacheData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData;
@@ -214,17 +217,17 @@ public class ActionCore extends PartnerCore<ActionCore> {
if (metaActionInfo == null) {
throw new MetaActionNotFoundException("未找到对应的行动程序信息" + actionKey);
}
MetaAction metaAction = new MetaAction();
metaAction.setParams(metaActionInfo.getParams());
metaAction.setType(MetaActionType.MCP);
metaAction.setIo(metaActionInfo.isIo());
String[] split = actionKey.split("::");
if (split.length < 2) {
throw new MetaActionNotFoundException("未找到对应的行动程序,原因: 传入的 actionKey(" + actionKey + ") 存在异常");
}
metaAction.setLocation(split[0]);
metaAction.setName(split[1]);
return metaAction;
return new MetaAction(
split[1],
metaActionInfo.isIo(),
MetaAction.Type.MCP,
split[0]
);
}
@CapabilityMethod

View File

@@ -1,61 +1,73 @@
package work.slhaf.partner.core.action.entity;
package work.slhaf.partner.core.action.entity
import lombok.Data;
import java.util.Map;
/**
* 行动链中的单一元素,封装了调用外部行动程序的必要信息与结果容器,可被{@link work.slhaf.partner.core.action.ActionCapability}执行
* 行动链中的单一元素,封装了调用外部行动程序的必要信息与结果容器,可被[work.slhaf.partner.core.action.ActionCapability]执行
*/
@Data
public class MetaAction {
data class MetaAction(
/**
* 行动name用于标识行动程序
*/
private String name;
/**
* 行动程序可接受的参数,由调用处设置
*/
private Map<String, Object> params;
/**
* 行动结果,包括执行状态和相应内容(执行结果或者错误信息)
*/
private Result result = new Result();
val name: String,
/**
* 是否IO密集用于决定使用何种线程池
*/
private boolean io;
val io: Boolean = false,
/**
* 行动程序类型,可分为 MCP、ORIGIN 两种,前者对应读取到的 MCP Tool、后者对应生成的临时行动程序
*/
private MetaActionType type;
val type: Type,
/**
* 当类型为 MCP 时,该字段对应相应 MCP Client 注册时生成的 id;
* 当类型为 ORIGIN 时,该字段对应相应的磁盘路径字符串
*/
private String location;
val location: String,
) {
/**
* 行动程序可接受的参数,由调用处设置
*/
val params: MutableMap<String, Any> = mutableMapOf()
/**
* 行动结果,包括执行状态和相应内容(执行结果或者错误信息)
*/
var result = Result()
val key: String
/**
* actionKey 将由 location+name 共同定位
*
* @return actionKey
*/
public String getKey() {
return location + "::" + name;
get() = "$location::$name"
class Result {
var status = Status.WAITING
var data: String? = null
fun reset() {
status = Status.WAITING
data = null
}
@Data
public static class Result {
private ResultStatus status = ResultStatus.WAITING;
private String data = null;
}
public enum ResultStatus {
enum class Status {
SUCCESS,
FAILED,
WAITING
}
}
enum class Type {
/**
* 将调用的 MCP 工具,可包括远程、本地任意服务
*/
MCP,
/**
* 适用于‘临时生成’的行动程序,在生成后根据序列化选项及执行情况,进行持久化
*/
ORIGIN
}
}

View File

@@ -1,12 +0,0 @@
package work.slhaf.partner.core.action.entity;
public enum MetaActionType {
/**
* 将调用的 MCP 工具,可包括远程、本地任意服务
*/
MCP,
/**
* 适用于‘临时生成’的行动程序,在生成后根据序列化选项及执行情况,进行持久化
*/
ORIGIN
}

View File

@@ -29,7 +29,6 @@ import work.slhaf.partner.common.mcp.InProcessMcpTransport;
import work.slhaf.partner.core.action.entity.ActionFileMetaData;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.MetaActionType;
import work.slhaf.partner.core.action.exception.ActionInitFailedException;
import work.slhaf.partner.core.action.exception.ActionSerializeFailedException;
@@ -200,8 +199,8 @@ public class LocalRunnerClient extends RunnerClient {
RunnerResponse response;
try {
response = switch (metaAction.getType()) {
case MetaActionType.MCP -> doRunWithMcp(metaAction);
case MetaActionType.ORIGIN -> doRunWithOrigin(metaAction);
case MetaAction.Type.MCP -> doRunWithMcp(metaAction);
case MetaAction.Type.ORIGIN -> doRunWithOrigin(metaAction);
};
} catch (Exception e) {
response = new RunnerResponse();
@@ -251,8 +250,8 @@ public class LocalRunnerClient extends RunnerClient {
}
@Override
public String buildTmpPath(MetaAction tempAction, String codeType) {
return Path.of(TMP_ACTION_PATH, System.currentTimeMillis() + "-" + tempAction.getKey() + codeType).toString();
public String buildTmpPath(String actionKey, String codeType) {
return Path.of(TMP_ACTION_PATH, System.currentTimeMillis() + "-" + actionKey + codeType).toString();
}
@Override

View File

@@ -9,7 +9,6 @@ import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.core.action.entity.ActionFileMetaData;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaAction.Result;
import work.slhaf.partner.core.action.entity.MetaAction.ResultStatus;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.ActionInitFailedException;
@@ -67,17 +66,17 @@ public abstract class RunnerClient {
public void submit(MetaAction metaAction) {
// 获取已存在行动列表
Result result = metaAction.getResult();
if (!result.getStatus().equals(ResultStatus.WAITING)) {
if (!result.getStatus().equals(Result.Status.WAITING)) {
return;
}
RunnerResponse response = doRun(metaAction);
result.setData(response.getData());
result.setStatus(response.isOk() ? ResultStatus.SUCCESS : ResultStatus.FAILED);
result.setStatus(response.isOk() ? Result.Status.SUCCESS : Result.Status.FAILED);
}
protected abstract RunnerResponse doRun(MetaAction metaAction);
public abstract String buildTmpPath(MetaAction tempAction, String codeType);
public abstract String buildTmpPath(String actionKey, String codeType);
public abstract void tmpSerialize(MetaAction tempAction, String code, String codeType) throws IOException;

View File

@@ -40,7 +40,7 @@ public class SandboxRunnerClient extends RunnerClient {
}
@Override
public String buildTmpPath(MetaAction tempAction, String codeType) {
public String buildTmpPath(String actionKey, String codeType) {
throw new UnsupportedOperationException("Unimplemented method 'buildTmpPath'");
}

View File

@@ -203,7 +203,7 @@ public class ActionExecutor extends AgentRunningSubModule<ActionExecutorInput, V
continue;
}
ExecutorService executor = next.isIo() ? virtualExecutor : platformExecutor;
ExecutorService executor = next.getIo() ? virtualExecutor : platformExecutor;
executor.execute(buildMataActionTask(next, phaserRecord, source));
if (first) {
@@ -238,7 +238,7 @@ public class ActionExecutor extends AgentRunningSubModule<ActionExecutorInput, V
val extractorResult = paramsExtractor.execute(extractorInput);
if (extractorResult.isOk()) {
metaAction.setParams(extractorResult.getParams());
metaAction.getParams().putAll(extractorResult.getParams());
runnerClient.submit(metaAction);
val historyAction = new HistoryAction(actionKey, actionCapability.loadMetaActionInfo(actionKey).getDescription(), metaAction.getResult().getData());
actionData.getHistory()
@@ -251,21 +251,21 @@ public class ActionExecutor extends AgentRunningSubModule<ActionExecutorInput, V
// 如果本次修复被认为成功,则将补充的信息添加至 additionalContext
case RepairerResult.RepairerStatus.OK -> {
additionalContext.addAll(repairerResult.getFixedData());
result.setStatus(MetaAction.ResultStatus.WAITING);
result.setStatus(MetaAction.Result.Status.WAITING);
}
// 此处的修复失败来自系统内部的执行失败:其余方式均不可行时将回退至当前分支
case RepairerResult.RepairerStatus.FAILED -> {
result.setStatus(MetaAction.ResultStatus.FAILED);
result.setStatus(MetaAction.Result.Status.FAILED);
result.setData("行动执行失败");
}
// 此处对应已在 repairer 内发起外部请求,故在此处进行阻塞
case RepairerResult.RepairerStatus.ACQUIRE -> {
phaserRecord.interrupt();
result.setStatus(MetaAction.ResultStatus.WAITING);
result.setStatus(MetaAction.Result.Status.WAITING);
}
}
}
} while (result.getStatus().equals(MetaAction.ResultStatus.WAITING));
} while (result.getStatus().equals(MetaAction.Result.Status.WAITING));
} catch (Exception e) {
log.error("Action executing failed: {}", actionKey, e);
} finally {

View File

@@ -16,7 +16,6 @@ import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore.ExecutorType;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaAction.Result;
import work.slhaf.partner.core.action.entity.MetaAction.ResultStatus;
import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.core.cognation.CognationCapability;
import work.slhaf.partner.module.modules.action.dispatcher.executor.entity.GeneratorInput;
@@ -112,7 +111,7 @@ public class ActionRepairer extends AgentRunningSubModule<RepairerInput, Repaire
runnerClient.submit(tempAction);
// 根据 tempAction 的执行状态设置修复结果
Result actionResult = tempAction.getResult();
if (actionResult.getStatus() != ResultStatus.SUCCESS) {
if (actionResult.getStatus() != MetaAction.Result.Status.SUCCESS) {
result.setStatus(RepairerStatus.FAILED);
return result;
}
@@ -137,7 +136,7 @@ public class ActionRepairer extends AgentRunningSubModule<RepairerInput, Repaire
AtomicInteger failedCount = new AtomicInteger(0);
for (String key : actionKeys) {
MetaAction action = actionCapability.loadMetaAction(key);
executor = action.isIo() ? virtual : platform;
executor = action.getIo() ? virtual : platform;
executor.execute(() -> {
try {
runnerClient.submit(action);

View File

@@ -1,6 +1,7 @@
package work.slhaf.partner.module.modules.action.dispatcher.executor;
import com.alibaba.fastjson2.JSONObject;
import lombok.val;
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule;
import work.slhaf.partner.api.agent.factory.module.annotation.Init;
@@ -11,7 +12,6 @@ import work.slhaf.partner.common.util.ExtractUtil;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.entity.GeneratedData;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionType;
import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.module.modules.action.dispatcher.executor.entity.GeneratorInput;
import work.slhaf.partner.module.modules.action.dispatcher.executor.entity.GeneratorResult;
@@ -40,15 +40,22 @@ public class DynamicActionGenerator extends AgentRunningSubModule<GeneratorInput
// 由于 SCRIPT 类型程序都是在 SandboxRunner 内部的磁盘上加载然后执行的,
// 所以此处的输入内容也只需要指定输入参数、临时key、是否持久化即可路径将按照指定规则统一构建不可交给LLM生成
String prompt = buildPrompt(input);
// 响应结果需要包含几个特殊数据: 依赖项、代码内容、是否序列化、响应数据释义
ChatResponse response = this.singleChat(prompt);
GeneratedData generatorData = JSONObject
.parseObject(ExtractUtil.extractJson(response.getMessage()), GeneratedData.class);
MetaAction tempAction = buildAction(input);
val location = runnerClient.buildTmpPath(input.getActionName(), generatorData.getCodeType());
MetaAction tempAction = new MetaAction(
input.getActionName(),
true,
MetaAction.Type.ORIGIN,
location
);
// 将临时行动单元序列化至临时文件夹,并设置程序路径、放置在队列中,等待执行状态变化,并根据序列化选项选择是否补充 MetaActionInfo 并持久序列化
// 通过 ActionCapability 暴露的接口序列化至临时文件夹同时返回Path对象并设置。队列建议交给 SandboxRunner
// 持有,包括监听与序列化线程
tempAction.setLocation(runnerClient.buildTmpPath(tempAction, generatorData.getCodeType()));
runnerClient.tmpSerialize(tempAction, generatorData.getCode(), generatorData.getCodeType());
if (generatorData.isSerialize()) {
waitingSerialize();
@@ -64,15 +71,6 @@ public class DynamicActionGenerator extends AgentRunningSubModule<GeneratorInput
throw new UnsupportedOperationException("Unimplemented method 'waitingSerialize'");
}
private MetaAction buildAction(GeneratorInput input) {
MetaAction tempAction = new MetaAction();
tempAction.setName(input.getActionName());
tempAction.setParams(input.getParams());
tempAction.setIo(true);
tempAction.setType(MetaActionType.ORIGIN);
return tempAction;
}
private String buildPrompt(GeneratorInput data) {
JSONObject prompt = new JSONObject();
prompt.put("[行动描述]", data.getDescription());

View File

@@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.MetaActionType;
import java.io.IOException;
import java.nio.file.Files;
@@ -177,12 +176,14 @@ public class LocalRunnerClientTest {
+ " }";
}
static MetaAction buildMetaAction(MetaActionType type, String location, String name, Map<String, Object> params) {
MetaAction metaAction = new MetaAction();
metaAction.setType(type);
metaAction.setLocation(location);
metaAction.setName(name);
metaAction.setParams(params);
static MetaAction buildMetaAction(MetaAction.Type type, String location, String name, Map<String, Object> params) {
MetaAction metaAction = new MetaAction(
name,
false,
type,
location
);
metaAction.getParams().putAll(params);
return metaAction;
}
}
@@ -782,7 +783,7 @@ public class LocalRunnerClientTest {
try {
Path script = tempDir.resolve("run");
Files.writeString(script, "echo ok\n");
MetaAction metaAction = buildMetaAction(MetaActionType.ORIGIN, script.toString(), "run", Map.of());
MetaAction metaAction = buildMetaAction(MetaAction.Type.ORIGIN, script.toString(), "run", Map.of());
RunnerClient.RunnerResponse response = client.doRun(metaAction);
Assertions.assertNotNull(response);
Assertions.assertFalse(response.isOk());
@@ -801,7 +802,7 @@ public class LocalRunnerClientTest {
try {
Path script = tempDir.resolve("run.sh");
Files.writeString(script, "echo ok\n");
MetaAction metaAction = buildMetaAction(MetaActionType.ORIGIN, script.toString(), "run", Map.of());
MetaAction metaAction = buildMetaAction(MetaAction.Type.ORIGIN, script.toString(), "run", Map.of());
RunnerClient.RunnerResponse response = client.doRun(metaAction);
Assertions.assertNotNull(response);
Assertions.assertTrue(response.isOk());
@@ -818,7 +819,7 @@ public class LocalRunnerClientTest {
LocalRunnerClient client = new LocalRunnerClient(existedMetaActions, executor, tempDir.toString());
try {
MetaAction metaAction = buildMetaAction(MetaActionType.MCP, "missing-client", "missing-tool", Map.of());
MetaAction metaAction = buildMetaAction(MetaAction.Type.MCP, "missing-client", "missing-tool", Map.of());
RunnerClient.RunnerResponse response = client.doRun(metaAction);
Assertions.assertNotNull(response);
Assertions.assertFalse(response.isOk());
@@ -834,7 +835,7 @@ public class LocalRunnerClientTest {
RunnerClient client = new LocalRunnerClient(existedMetaActions, executor, tempDir.toString());
try {
MetaAction metaAction = buildMetaAction(MetaActionType.MCP, "missing-client", "missing-tool", Map.of());
MetaAction metaAction = buildMetaAction(MetaAction.Type.MCP, "missing-client", "missing-tool", Map.of());
client.submit(metaAction);
Assertions.assertNotNull(metaAction.getResult().getData());
} finally {
@@ -861,9 +862,9 @@ public class LocalRunnerClientTest {
waitForCondition(() -> hasActionKey(existedMetaActions, key -> key.startsWith("playwright::")), 20000);
Assertions.assertTrue(hasActionKey(existedMetaActions, key -> key.startsWith("playwright::")));
MetaAction metaAction = buildMetaAction(MetaActionType.MCP, "playwright", "browser_navigate", Map.of("url", "https://deepwiki.com/microsoft/vscode"));
MetaAction metaAction = buildMetaAction(MetaAction.Type.MCP, "playwright", "browser_navigate", Map.of("url", "https://deepwiki.com/microsoft/vscode"));
client.submit(metaAction);
Assertions.assertNotEquals(MetaAction.ResultStatus.WAITING, metaAction.getResult().getStatus());
Assertions.assertNotEquals(MetaAction.Result.Status.WAITING, metaAction.getResult().getStatus());
} finally {
executor.shutdownNow();
}

View File

@@ -23,7 +23,7 @@ public class RunnerClientTest {
}
@Override
public String buildTmpPath(MetaAction tempAction, String codeType) {
public String buildTmpPath(String actionKey, String codeType) {
return null;
}

View File

@@ -100,7 +100,7 @@ class ActionExecutorTest {
when(paramsExtractor.execute(any())).thenReturn(extractorResult);
doAnswer(inv -> {
MetaAction metaAction = inv.getArgument(0);
metaAction.getResult().setStatus(MetaAction.ResultStatus.SUCCESS);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
return null;
}).when(runnerClient).submit(any(MetaAction.class));
@@ -148,7 +148,7 @@ class ActionExecutorTest {
when(paramsExtractor.execute(any())).thenReturn(extractorResult);
doAnswer(inv -> {
MetaAction metaAction = inv.getArgument(0);
metaAction.getResult().setStatus(MetaAction.ResultStatus.SUCCESS);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
log.info("metaAction result:{}", metaAction.getResult().getStatus());
return null;
}).when(runnerClient).submit(any(MetaAction.class));
@@ -177,7 +177,7 @@ class ActionExecutorTest {
lenient().when(paramsExtractor.execute(any())).thenReturn(extractorResult);
lenient().doAnswer(inv -> {
MetaAction metaAction = inv.getArgument(0);
metaAction.getResult().setStatus(MetaAction.ResultStatus.SUCCESS);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
return null;
}).when(runnerClient).submit(any(MetaAction.class));
@@ -211,7 +211,7 @@ class ActionExecutorTest {
doAnswer(inv -> {
MetaAction metaAction = inv.getArgument(0);
metaAction.getResult().setStatus(MetaAction.ResultStatus.SUCCESS);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
return null;
}).when(runnerClient).submit(any(MetaAction.class));
@@ -252,7 +252,7 @@ class ActionExecutorTest {
} catch (InterruptedException ignored) {
}
MetaAction metaAction = actionData.getActionChain().get(0).get(0);
assertEquals(MetaAction.ResultStatus.FAILED, metaAction.getResult().getStatus());
assertEquals(MetaAction.Result.Status.FAILED, metaAction.getResult().getStatus());
verify(runnerClient, never()).submit(any(MetaAction.class));
}
@@ -279,7 +279,7 @@ class ActionExecutorTest {
doAnswer(inv -> {
MetaAction metaAction = inv.getArgument(0);
metaAction.getResult().setStatus(MetaAction.ResultStatus.SUCCESS);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
return null;
}).when(runnerClient).submit(any(MetaAction.class));
@@ -326,7 +326,7 @@ class ActionExecutorTest {
lenient().when(paramsExtractor.execute(any())).thenReturn(ok);
lenient().doAnswer(inv -> {
MetaAction metaAction = inv.getArgument(0);
metaAction.getResult().setStatus(MetaAction.ResultStatus.SUCCESS);
metaAction.getResult().setStatus(MetaAction.Result.Status.SUCCESS);
return null;
}).when(runnerClient).submit(any(MetaAction.class));
@@ -392,11 +392,12 @@ class ActionExecutorTest {
}
private MetaAction buildMetaAction(String name, boolean io) {
MetaAction metaAction = new MetaAction();
metaAction.setName(name);
metaAction.setLocation("loc");
metaAction.setIo(io);
return metaAction;
return new MetaAction(
name,
io,
MetaAction.Type.ORIGIN,
"location"
);
}
private Map<Integer, List<String>> initAdditionalContext(Map<Integer, List<MetaAction>> actionChain) {