refactor(runner): add builtin action provider interface

This commit is contained in:
2026-03-19 10:39:06 +08:00
parent 67d7fd34f8
commit 5b9b9c3c09
4 changed files with 65 additions and 31 deletions

View File

@@ -0,0 +1,7 @@
package work.slhaf.partner.module.modules.action.builtin;
import java.util.List;
public interface BuiltinActionProvider {
List<BuiltinActionRegistry.BuiltinActionDefinition> provideBuiltinActions();
}

View File

@@ -9,6 +9,7 @@ import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.MetaActionNotFoundException; import work.slhaf.partner.core.action.exception.MetaActionNotFoundException;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -23,8 +24,6 @@ public class BuiltinActionRegistry extends AbstractAgentModule.Standalone {
@InjectCapability @InjectCapability
private ActionCapability actionCapability; private ActionCapability actionCapability;
private final BuiltinCommandActionManager builtinCommandActionManager = new BuiltinCommandActionManager();
@Init @Init
public void init() { public void init() {
definitions.clear(); definitions.clear();
@@ -36,14 +35,10 @@ public class BuiltinActionRegistry extends AbstractAgentModule.Standalone {
} }
protected List<BuiltinActionDefinition> buildDefaultActionDefinitions() { protected List<BuiltinActionDefinition> buildDefaultActionDefinitions() {
return List.of( List<BuiltinActionDefinition> builtinActionDefinitions = new ArrayList<>();
builtinCommandActionManager.buildCommandExecuteDefinition(), BuiltinActionProvider commandActionProvider = new BuiltinCommandActionProvider();
builtinCommandActionManager.buildCommandStartDefinition(), builtinActionDefinitions.addAll(commandActionProvider.provideBuiltinActions());
builtinCommandActionManager.buildCommandInspectDefinition(), return builtinActionDefinitions;
builtinCommandActionManager.buildCommandReadDefinition(),
builtinCommandActionManager.buildCommandCancelDefinition(),
builtinCommandActionManager.buildCommandOverviewDefinition()
);
} }
public void defineBuiltinAction(String name, MetaActionInfo metaActionInfo, Function<Map<String, Object>, String> invoker) { public void defineBuiltinAction(String name, MetaActionInfo metaActionInfo, Function<Map<String, Object>, String> invoker) {
@@ -109,4 +104,4 @@ public class BuiltinActionRegistry extends AbstractAgentModule.Standalone {
} }
} }
} }

View File

@@ -12,7 +12,7 @@ import java.util.function.Function;
import static work.slhaf.partner.core.action.ActionCore.BUILTIN_LOCATION; import static work.slhaf.partner.core.action.ActionCore.BUILTIN_LOCATION;
class BuiltinCommandActionManager { class BuiltinCommandActionProvider implements BuiltinActionProvider {
private static final String COMMAND_LOCATION = BUILTIN_LOCATION + "::" + "command"; private static final String COMMAND_LOCATION = BUILTIN_LOCATION + "::" + "command";
private static final String COMMAND_ARG_PREFIX = "arg"; private static final String COMMAND_ARG_PREFIX = "arg";
@@ -25,12 +25,24 @@ class BuiltinCommandActionManager {
private final ConcurrentHashMap<String, CommandHandle> commandHandles = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, CommandHandle> commandHandles = new ConcurrentHashMap<>();
private final CommandExecutionService commandExecutionService = CommandExecutionService.INSTANCE; private final CommandExecutionService commandExecutionService = CommandExecutionService.INSTANCE;
@Override
public List<BuiltinActionRegistry.BuiltinActionDefinition> provideBuiltinActions() {
return List.of(
buildCommandExecuteDefinition(),
buildCommandStartDefinition(),
buildCommandInspectDefinition(),
buildCommandReadDefinition(),
buildCommandCancelDefinition(),
buildCommandOverviewDefinition()
);
}
/** /**
* 用于直接执行的 Builtin MetaAction * 用于直接执行的 Builtin MetaAction
* *
* @return 内建 MetaAction 定义数据参数为常规命令列表返回值为该命令的响应内容 * @return 内建 MetaAction 定义数据参数为常规命令列表返回值为该命令的响应内容
*/ */
BuiltinActionRegistry.BuiltinActionDefinition buildCommandExecuteDefinition() { private BuiltinActionRegistry.BuiltinActionDefinition buildCommandExecuteDefinition() {
Set<String> tags = new HashSet<>(basicTags); Set<String> tags = new HashSet<>(basicTags);
tags.add("Command Execution"); tags.add("Command Execution");
MetaActionInfo info = new MetaActionInfo( MetaActionInfo info = new MetaActionInfo(
@@ -61,7 +73,7 @@ class BuiltinCommandActionManager {
* *
* @return 内建 MetaAction 定义数据参数为命令列表及进程描述返回值为进程句柄 id * @return 内建 MetaAction 定义数据参数为命令列表及进程描述返回值为进程句柄 id
*/ */
BuiltinActionRegistry.BuiltinActionDefinition buildCommandStartDefinition() { private BuiltinActionRegistry.BuiltinActionDefinition buildCommandStartDefinition() {
Set<String> tags = new HashSet<>(basicTags); Set<String> tags = new HashSet<>(basicTags);
tags.add("Command Session"); tags.add("Command Session");
MetaActionInfo info = new MetaActionInfo( MetaActionInfo info = new MetaActionInfo(
@@ -106,7 +118,7 @@ class BuiltinCommandActionManager {
* *
* @return 内建 MetaAction 定义数据参数为进程 id返回值为摘要内容(CommandInspectData) * @return 内建 MetaAction 定义数据参数为进程 id返回值为摘要内容(CommandInspectData)
*/ */
BuiltinActionRegistry.BuiltinActionDefinition buildCommandInspectDefinition() { private BuiltinActionRegistry.BuiltinActionDefinition buildCommandInspectDefinition() {
Set<String> tags = new HashSet<>(basicTags); Set<String> tags = new HashSet<>(basicTags);
tags.add("Command Session"); tags.add("Command Session");
MetaActionInfo info = new MetaActionInfo( MetaActionInfo info = new MetaActionInfo(
@@ -143,7 +155,7 @@ class BuiltinCommandActionManager {
* *
* @return 内建 MetaAction 定义数据参数为进程 id 与读取流(stdout/stderr)返回值为读取内容(CommandReadData) * @return 内建 MetaAction 定义数据参数为进程 id 与读取流(stdout/stderr)返回值为读取内容(CommandReadData)
*/ */
BuiltinActionRegistry.BuiltinActionDefinition buildCommandReadDefinition() { private BuiltinActionRegistry.BuiltinActionDefinition buildCommandReadDefinition() {
Set<String> tags = new HashSet<>(basicTags); Set<String> tags = new HashSet<>(basicTags);
tags.add("Command Session"); tags.add("Command Session");
tags.add("Command Read"); tags.add("Command Read");
@@ -206,7 +218,7 @@ class BuiltinCommandActionManager {
* *
* @return 内建 MetaAction 定义数据参数为进程 id返回值为是否成功取消 * @return 内建 MetaAction 定义数据参数为进程 id返回值为是否成功取消
*/ */
BuiltinActionRegistry.BuiltinActionDefinition buildCommandCancelDefinition() { private BuiltinActionRegistry.BuiltinActionDefinition buildCommandCancelDefinition() {
Set<String> tags = new HashSet<>(basicTags); Set<String> tags = new HashSet<>(basicTags);
tags.add("Command Session"); tags.add("Command Session");
tags.add("Command Cancel"); tags.add("Command Cancel");
@@ -253,7 +265,7 @@ class BuiltinCommandActionManager {
* *
* @return 内建 MetaAction 定义数据无参数返回值为后台进程集合(CommandOverviewItem) * @return 内建 MetaAction 定义数据无参数返回值为后台进程集合(CommandOverviewItem)
*/ */
BuiltinActionRegistry.BuiltinActionDefinition buildCommandOverviewDefinition() { private BuiltinActionRegistry.BuiltinActionDefinition buildCommandOverviewDefinition() {
Set<String> tags = new HashSet<>(basicTags); Set<String> tags = new HashSet<>(basicTags);
tags.add("Command Session"); tags.add("Command Session");
tags.add("Command Overview"); tags.add("Command Overview");
@@ -496,4 +508,4 @@ class BuiltinCommandActionManager {
private String desc; private String desc;
private Integer exitCode; private Integer exitCode;
} }
} }

View File

@@ -5,15 +5,21 @@ import com.alibaba.fastjson2.JSONObject;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map; import java.util.Map;
class BuiltinCommandActionManagerTest { class BuiltinCommandActionProviderTest {
@Test @Test
void testStartInspectReadAndOverview() throws Exception { void testStartInspectReadAndOverview() throws Exception {
BuiltinCommandActionManager manager = new BuiltinCommandActionManager(); BuiltinCommandActionProvider provider = new BuiltinCommandActionProvider();
List<BuiltinActionRegistry.BuiltinActionDefinition> definitions = provider.provideBuiltinActions();
BuiltinActionRegistry.BuiltinActionDefinition start = requireDefinition(definitions, "builtin::command::start");
BuiltinActionRegistry.BuiltinActionDefinition inspectDefinition = requireDefinition(definitions, "builtin::command::inspect");
BuiltinActionRegistry.BuiltinActionDefinition readDefinition = requireDefinition(definitions, "builtin::command::read");
BuiltinActionRegistry.BuiltinActionDefinition overviewDefinition = requireDefinition(definitions, "builtin::command::overview");
String startResult = manager.buildCommandStartDefinition().invoker().apply(Map.of( String startResult = start.invoker().apply(Map.of(
"desc", "demo-session", "desc", "demo-session",
"arg", "sh", "arg", "sh",
"arg1", "-lc", "arg1", "-lc",
@@ -22,7 +28,7 @@ class BuiltinCommandActionManagerTest {
String executionId = JSONObject.parseObject(startResult).getString("executionId"); String executionId = JSONObject.parseObject(startResult).getString("executionId");
Assertions.assertNotNull(executionId); Assertions.assertNotNull(executionId);
JSONObject inspect = waitForInspectExit(manager, executionId); JSONObject inspect = waitForInspectExit(inspectDefinition, executionId);
Assertions.assertEquals("demo-session", inspect.getString("desc")); Assertions.assertEquals("demo-session", inspect.getString("desc"));
Assertions.assertEquals(0, inspect.getInteger("exitCode")); Assertions.assertEquals(0, inspect.getInteger("exitCode"));
Assertions.assertTrue(inspect.getInteger("stdoutSize") > 0); Assertions.assertTrue(inspect.getInteger("stdoutSize") > 0);
@@ -30,7 +36,7 @@ class BuiltinCommandActionManagerTest {
Assertions.assertTrue(inspect.getString("stdoutSummary").contains("hello")); Assertions.assertTrue(inspect.getString("stdoutSummary").contains("hello"));
Assertions.assertTrue(inspect.getString("stderrSummary").contains("oops")); Assertions.assertTrue(inspect.getString("stderrSummary").contains("oops"));
JSONObject read = JSONObject.parseObject(manager.buildCommandReadDefinition().invoker().apply(Map.of( JSONObject read = JSONObject.parseObject(readDefinition.invoker().apply(Map.of(
"id", executionId, "id", executionId,
"limit", 5 "limit", 5
))); )));
@@ -40,7 +46,7 @@ class BuiltinCommandActionManagerTest {
Assertions.assertTrue(read.getBooleanValue("contentTruncated")); Assertions.assertTrue(read.getBooleanValue("contentTruncated"));
Assertions.assertEquals("hello", read.getString("content")); Assertions.assertEquals("hello", read.getString("content"));
JSONObject overview = JSONObject.parseObject(manager.buildCommandOverviewDefinition().invoker().apply(Map.of())); JSONObject overview = JSONObject.parseObject(overviewDefinition.invoker().apply(Map.of()));
JSONArray result = overview.getJSONArray("result"); JSONArray result = overview.getJSONArray("result");
Assertions.assertTrue(result.stream().map(item -> (JSONObject) item) Assertions.assertTrue(result.stream().map(item -> (JSONObject) item)
.anyMatch(item -> executionId.equals(item.getString("executionId")))); .anyMatch(item -> executionId.equals(item.getString("executionId"))));
@@ -48,9 +54,13 @@ class BuiltinCommandActionManagerTest {
@Test @Test
void testCancelStopsBackgroundCommand() throws Exception { void testCancelStopsBackgroundCommand() throws Exception {
BuiltinCommandActionManager manager = new BuiltinCommandActionManager(); BuiltinCommandActionProvider provider = new BuiltinCommandActionProvider();
List<BuiltinActionRegistry.BuiltinActionDefinition> definitions = provider.provideBuiltinActions();
BuiltinActionRegistry.BuiltinActionDefinition start = requireDefinition(definitions, "builtin::command::start");
BuiltinActionRegistry.BuiltinActionDefinition cancelDefinition = requireDefinition(definitions, "builtin::command::cancel");
BuiltinActionRegistry.BuiltinActionDefinition inspectDefinition = requireDefinition(definitions, "builtin::command::inspect");
String startResult = manager.buildCommandStartDefinition().invoker().apply(Map.of( String startResult = start.invoker().apply(Map.of(
"desc", "sleep-session", "desc", "sleep-session",
"arg", "sh", "arg", "sh",
"arg1", "-lc", "arg1", "-lc",
@@ -58,20 +68,30 @@ class BuiltinCommandActionManagerTest {
)); ));
String executionId = JSONObject.parseObject(startResult).getString("executionId"); String executionId = JSONObject.parseObject(startResult).getString("executionId");
JSONObject cancel = JSONObject.parseObject(manager.buildCommandCancelDefinition().invoker().apply(Map.of( JSONObject cancel = JSONObject.parseObject(cancelDefinition.invoker().apply(Map.of(
"id", executionId "id", executionId
))); )));
Assertions.assertEquals(executionId, cancel.getString("executionId")); Assertions.assertEquals(executionId, cancel.getString("executionId"));
Assertions.assertTrue(cancel.getBooleanValue("ok")); Assertions.assertTrue(cancel.getBooleanValue("ok"));
JSONObject inspect = waitForInspectExit(manager, executionId); JSONObject inspect = waitForInspectExit(inspectDefinition, executionId);
Assertions.assertNotNull(inspect.get("endAt")); Assertions.assertNotNull(inspect.get("endAt"));
} }
private JSONObject waitForInspectExit(BuiltinCommandActionManager manager, String executionId) throws Exception { private BuiltinActionRegistry.BuiltinActionDefinition requireDefinition(
List<BuiltinActionRegistry.BuiltinActionDefinition> definitions,
String actionKey
) {
return definitions.stream()
.filter(definition -> actionKey.equals(definition.actionKey()))
.findFirst()
.orElseThrow(() -> new AssertionError("definition not found: " + actionKey));
}
private JSONObject waitForInspectExit(BuiltinActionRegistry.BuiltinActionDefinition inspectDefinition, String executionId) throws Exception {
long deadline = System.currentTimeMillis() + 3000; long deadline = System.currentTimeMillis() + 3000;
while (System.currentTimeMillis() < deadline) { while (System.currentTimeMillis() < deadline) {
JSONObject inspect = JSONObject.parseObject(manager.buildCommandInspectDefinition().invoker().apply(Map.of( JSONObject inspect = JSONObject.parseObject(inspectDefinition.invoker().apply(Map.of(
"id", executionId "id", executionId
))); )));
if (inspect.get("exitCode") != null) { if (inspect.get("exitCode") != null) {