refactor(action): pass launcher through meta action flow instead of inferring command by file extension

This commit is contained in:
2026-03-14 21:27:38 +08:00
parent 603b0835c5
commit cba9ff4f0b
10 changed files with 62 additions and 57 deletions

View File

@@ -329,7 +329,8 @@ public class ActionCore extends PartnerCore<ActionCore> {
MetaAction.Type type = BUILTIN_LOCATION.equals(split[0]) ? MetaAction.Type.BUILTIN : MetaAction.Type.MCP;
return new MetaAction(
split[1],
metaActionInfo.isIo(),
metaActionInfo.getIo(),
metaActionInfo.getLauncher(),
type,
split[0]
);

View File

@@ -10,6 +10,7 @@ public class GeneratedData {
private List<String> dependencies;
private String code;
private String codeType;
private String launcher;
private boolean serialize;
private JSONObject responseSchema;
}

View File

@@ -13,9 +13,13 @@ data class MetaAction(
* 是否IO密集用于决定使用何种线程池
*/
val io: Boolean = false,
/**
* 启动器/解释器,对于原生 MCP Tool 、Dynamic Action 来说可忽略,目前仅用于 ORIGIN 类型
*/
val launcher: String? = null,
/**
* 行动程序类型,可分为 MCP、ORIGIN、BUILTIN 三种,
* 分别对应读取到的 MCP Tool、生成的临时行动程序、本地内置行动
* 分别对应读取到的 MCP Tool、生成的临时行动程序、内置行动
*/
val type: Type,
/**

View File

@@ -1,25 +1,44 @@
package work.slhaf.partner.core.action.entity;
package work.slhaf.partner.core.action.entity
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import com.alibaba.fastjson2.JSONObject
import java.util.List;
import java.util.Map;
@Data
public class MetaActionInfo {
private boolean io;
data class MetaActionInfo(
/**
* 是否 IO 密集
*/
val io: Boolean,
/**
* 所需的启动器/解释器
*/
val launcher: String?,
/**
* 参数描述
*/
val params: Map<String, String>,
/**
* 行动功能描述
*/
val description: String,
/**
* 行动标签
*/
val tags: Set<String>,
/**
* 前置行动依赖
*/
val preActions: Set<String>,
/**
* 后置行动依赖
*/
val postActions: Set<String>,
private Map<String, Object> params;
private String description;
private List<String> tags;
private List<String> preActions;
private List<String> postActions;
/**
* 是否严格依赖前置行动的成功执行若为true且前置行动失败则不执行该行动后置任务多为触发式。默认即执行。
*/
private boolean strictDependencies;
private JSONObject responseSchema;
}
val strictDependencies: Boolean,
/**
* 响应格式说明
*/
val responseSchema: JSONObject,
)

View File

@@ -11,18 +11,10 @@ import java.util.concurrent.atomic.AtomicInteger;
public class CommandExecutionService {
public String[] buildCommands(String ext, Map<String, Object> params, String absolutePath) {
String command = switch (ext) {
case "py" -> "python";
case "sh" -> "bash";
default -> null;
};
if (command == null) {
return null;
}
public String[] buildCommands(String launcher, Map<String, Object> params, String absolutePath) {
int paramSize = params == null ? 0 : params.size();
String[] commands = new String[paramSize + 2];
commands[0] = command;
commands[0] = launcher;
commands[1] = absolutePath;
AtomicInteger paramCount = new AtomicInteger(2);
if (params != null) {

View File

@@ -1,6 +1,5 @@
package work.slhaf.partner.core.action.runner.execution;
import cn.hutool.core.io.FileUtil;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.runner.RunnerClient;
@@ -17,18 +16,7 @@ public class OriginExecutionService {
public RunnerClient.RunnerResponse run(MetaAction metaAction) {
RunnerClient.RunnerResponse response = new RunnerClient.RunnerResponse();
File file = new File(metaAction.getLocation());
String ext = FileUtil.getSuffix(file);
if (ext == null || ext.isEmpty()) {
response.setOk(false);
response.setData("未知文件类型");
return response;
}
String[] commands = commandExecutionService.buildCommands(ext, metaAction.getParams(), file.getAbsolutePath());
if (commands == null || commands.length == 0) {
response.setOk(false);
response.setData("不支持的文件类型: " + file.getName());
return response;
}
String[] commands = commandExecutionService.buildCommands(metaAction.getLauncher(), metaAction.getParams(), file.getAbsolutePath());
CommandExecutionService.Result execResult = commandExecutionService.exec(commands);
response.setOk(execResult.isOk());
response.setData(execResult.getTotal());

View File

@@ -1,6 +1,5 @@
package work.slhaf.partner.core.action.runner.mcp;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import io.modelcontextprotocol.common.McpTransportContext;
@@ -292,8 +291,8 @@ public class DynamicActionMcpManager implements AutoCloseable {
Map<String, Object> additional = Map.of(
"pre", info.getPreActions(),
"post", info.getPostActions(),
"strict_pre", info.isStrictDependencies(),
"io", info.isIo()
"strict_pre", info.getStrictDependencies(),
"io", info.getIo()
);
McpSchema.Tool tool = McpSchema.Tool.builder()
.name(name)
@@ -305,18 +304,17 @@ public class DynamicActionMcpManager implements AutoCloseable {
.build();
return McpStatelessServerFeatures.AsyncToolSpecification.builder()
.tool(tool)
.callHandler(buildToolHandler(program))
.callHandler(buildToolHandler(program, info.getLauncher()))
.build();
}
private BiFunction<McpTransportContext, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> buildToolHandler(File program) {
private BiFunction<McpTransportContext, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> buildToolHandler(File program, String launcher) {
return (mcpTransportContext, callToolRequest) -> {
Map<String, Object> arguments = callToolRequest.arguments();
if (arguments == null) {
arguments = Map.of();
}
String ext = FileUtil.getSuffix(program);
String[] commands = commandExecutionService.buildCommands(ext, arguments, program.getAbsolutePath());
String[] commands = commandExecutionService.buildCommands(launcher, arguments, program.getAbsolutePath());
if (commands == null) {
return Mono.just(McpSchema.CallToolResult.builder()
.addTextContent("未知文件类型: " + program.getName())

View File

@@ -46,6 +46,7 @@ public class DynamicActionGenerator extends AbstractAgentModule.Sub<GeneratorInp
MetaAction tempAction = new MetaAction(
input.getActionName(),
true,
generatorData.getLauncher(),
MetaAction.Type.ORIGIN,
location
);
@@ -63,6 +64,7 @@ public class DynamicActionGenerator extends AbstractAgentModule.Sub<GeneratorInp
return result;
}
// TODO persist serialize. For now, it can be scheduled as a cycling-triggered StateAction or as a special MetaAction
private void waitingSerialize() {
throw new UnsupportedOperationException("Unimplemented method 'waitingSerialize'");
}

View File

@@ -11,7 +11,7 @@ public class RepairerInput {
private String userId;
private List<Message> recentMessages;
private Map<String, Object> params;
private Map<String, String> params;
private String actionDescription;
private List<HistoryAction> historyActionResults;
}

View File

@@ -380,12 +380,12 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
for (String actionKey : actionKeys) {
// 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖
MetaActionInfo metaActionInfo = actionCapability.loadMetaActionInfo(actionKey);
List<String> preActions = metaActionInfo.getPreActions();
boolean preActionsExist = preActions != null && !preActions.isEmpty();
Set<String> preActions = metaActionInfo.getPreActions();
boolean preActionsExist = preActions.isEmpty();
if (!preActionsExist) {
continue;
}
if (!metaActionInfo.isStrictDependencies()) {
if (!metaActionInfo.getStrictDependencies()) {
continue;
}
if (checkDependenciesExist(lastOrder, preActions, primaryActionChain)) {
@@ -398,8 +398,8 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
fixed.set(true);
List<String> actionsInChain = primaryActionChain.computeIfAbsent(lastOrder,
list -> new ArrayList<>());
preActions = new ArrayList<>(preActions);
preActions.removeAll(actionsInChain);
preActions = new HashSet<>(preActions);
actionsInChain.forEach(preActions::remove);
actionsInChain.addAll(preActions);
tempOrders.add(lastOrder);
}
@@ -418,7 +418,7 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
}
}
private boolean checkDependenciesExist(int lastOrder, List<String> preActions,
private boolean checkDependenciesExist(int lastOrder, Set<String> preActions,
Map<Integer, List<String>> primaryActionChain) {
if (!primaryActionChain.containsKey(lastOrder)) {
return false;