refactor(core): migrate deprecated exceptions to new hierarchy and align tests

This commit is contained in:
2026-04-11 16:10:34 +08:00
parent b8cb2afbcf
commit 1b48e955bd
28 changed files with 414 additions and 175 deletions

View File

@@ -5,7 +5,7 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import work.slhaf.partner.common.vector.exception.VectorClientExecuteException; import work.slhaf.partner.common.vector.exception.VectorClientExecutionException;
import java.util.Map; import java.util.Map;
@@ -28,12 +28,23 @@ public class OllamaVectorClient extends VectorClient {
HttpRequest request = HttpRequest.get(ollamaEmbeddingUrl).body(JSONObject.toJSONString(param)); HttpRequest request = HttpRequest.get(ollamaEmbeddingUrl).body(JSONObject.toJSONString(param));
try (HttpResponse response = request.execute()) { try (HttpResponse response = request.execute()) {
if (!response.isOk()) if (!response.isOk())
throw new VectorClientExecuteException("嵌入模型执行出错"); throw new VectorClientExecutionException(
"Failed to execute embedding model",
"ollama",
"COMPUTE",
ollamaEmbeddingUrl
);
String resStr = response.body(); String resStr = response.body();
EmbeddingModelResponse embeddingResponse = JSONObject.parseObject(resStr, EmbeddingModelResponse.class); EmbeddingModelResponse embeddingResponse = JSONObject.parseObject(resStr, EmbeddingModelResponse.class);
return embeddingResponse.getEmbeddings()[0]; return embeddingResponse.getEmbeddings()[0];
} catch (Exception e) { } catch (Exception e) {
throw new VectorClientExecuteException("嵌入模型执行出错", e); throw new VectorClientExecutionException(
"Failed to execute embedding model",
"ollama",
"COMPUTE",
ollamaEmbeddingUrl,
e
);
} }
} }

View File

@@ -5,8 +5,7 @@ import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
import ai.onnxruntime.OnnxTensor; import ai.onnxruntime.OnnxTensor;
import ai.onnxruntime.OrtEnvironment; import ai.onnxruntime.OrtEnvironment;
import ai.onnxruntime.OrtSession; import ai.onnxruntime.OrtSession;
import work.slhaf.partner.common.vector.exception.VectorClientExecuteException; import work.slhaf.partner.common.vector.exception.VectorClientExecutionException;
import work.slhaf.partner.common.vector.exception.VectorClientLoadFailedException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
@@ -38,7 +37,13 @@ public class OnnxVectorClient extends VectorClient {
OrtSession.SessionOptions ops = new OrtSession.SessionOptions(); OrtSession.SessionOptions ops = new OrtSession.SessionOptions();
session = env.createSession(modelPath, ops); session = env.createSession(modelPath, ops);
} catch (Exception e) { } catch (Exception e) {
throw new VectorClientLoadFailedException("加载ONNX模型失败", e); throw new VectorClientExecutionException(
"Failed to load ONNX model",
"onnx",
"MODEL_LOAD",
modelPath,
e
);
} }
} }
@@ -46,7 +51,13 @@ public class OnnxVectorClient extends VectorClient {
try { try {
tokenizer = HuggingFaceTokenizer.newInstance(Path.of(tokenizerPath)); tokenizer = HuggingFaceTokenizer.newInstance(Path.of(tokenizerPath));
} catch (Exception e) { } catch (Exception e) {
throw new VectorClientLoadFailedException("加载Tokenizer失败", e); throw new VectorClientExecutionException(
"Failed to load tokenizer",
"onnx",
"TOKENIZER_LOAD",
tokenizerPath,
e
);
} }
} }
@@ -76,7 +87,13 @@ public class OnnxVectorClient extends VectorClient {
OnnxTensor embeddingTensor = (OnnxTensor) result.get(0); OnnxTensor embeddingTensor = (OnnxTensor) result.get(0);
return embeddingTensor.getFloatBuffer().array(); return embeddingTensor.getFloatBuffer().array();
} catch (Exception e) { } catch (Exception e) {
throw new VectorClientExecuteException("嵌入模型执行出错", e); throw new VectorClientExecutionException(
"Failed to execute embedding model",
"onnx",
"COMPUTE",
modelPath,
e
);
} }
} }

View File

@@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j;
import org.nd4j.linalg.api.ndarray.INDArray; import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.factory.Nd4j; import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.ops.transforms.Transforms; import org.nd4j.linalg.ops.transforms.Transforms;
import work.slhaf.partner.common.vector.exception.VectorClientExecutionException;
import work.slhaf.partner.common.vector.exception.VectorClientStartupException;
@Slf4j @Slf4j
public abstract class VectorClient { public abstract class VectorClient {
@@ -12,14 +14,34 @@ public abstract class VectorClient {
public static VectorClient INSTANCE; public static VectorClient INSTANCE;
public static void startClient(VectorConfig config) { public static void startClient(VectorConfig config) {
if (config instanceof VectorConfig.Ollama ollama) { try {
INSTANCE = new OllamaVectorClient(ollama.ollamaEmbeddingUrl, ollama.ollamaEmbeddingModel); if (config instanceof VectorConfig.Ollama ollama) {
} else if (config instanceof VectorConfig.Onnx onnx) { INSTANCE = new OllamaVectorClient(ollama.ollamaEmbeddingUrl, ollama.ollamaEmbeddingModel);
INSTANCE = new OnnxVectorClient(onnx.tokenizerPath, onnx.embeddingModelPath); } else if (config instanceof VectorConfig.Onnx onnx) {
} else { INSTANCE = new OnnxVectorClient(onnx.tokenizerPath, onnx.embeddingModelPath);
return; } else {
return;
}
status = true;
} catch (VectorClientStartupException e) {
throw e;
} catch (VectorClientExecutionException e) {
throw new VectorClientStartupException(
"Vector client startup failed",
e.getClientType(),
"COMPUTE".equals(e.getPhase()) ? "STARTUP_SELF_TEST" : e.getPhase(),
e.getTarget(),
e
);
} catch (Exception e) {
throw new VectorClientStartupException(
"Vector client startup failed",
resolveClientType(config),
"STARTUP",
resolveTarget(config),
e
);
} }
status = true;
} }
public float[] compute(String input) { public float[] compute(String input) {
@@ -31,6 +53,26 @@ public abstract class VectorClient {
protected abstract float[] doCompute(String input); protected abstract float[] doCompute(String input);
private static String resolveClientType(VectorConfig config) {
if (config instanceof VectorConfig.Onnx) {
return "onnx";
}
if (config instanceof VectorConfig.Ollama) {
return "ollama";
}
return "unknown";
}
private static String resolveTarget(VectorConfig config) {
if (config instanceof VectorConfig.Onnx onnx) {
return onnx.embeddingModelPath;
}
if (config instanceof VectorConfig.Ollama ollama) {
return ollama.ollamaEmbeddingUrl;
}
return null;
}
public double compare(float[] v1, float[] v2) { public double compare(float[] v1, float[] v2) {
if (!status) { if (!status) {
return 0; return 0;
@@ -60,4 +102,4 @@ public abstract class VectorClient {
return updated.toFloatVector(); return updated.toFloatVector();
} }
} }
} }

View File

@@ -1,15 +0,0 @@
package work.slhaf.partner.common.vector.exception;
import work.slhaf.partner.framework.agent.exception.deprecated.AgentRuntimeException;
public class VectorClientExecuteException extends AgentRuntimeException {
public VectorClientExecuteException(String message) {
super(message);
}
public VectorClientExecuteException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,49 @@
package work.slhaf.partner.common.vector.exception;
import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport;
public class VectorClientExecutionException extends AgentRuntimeException {
private final String clientType;
private final String phase;
private final String target;
public VectorClientExecutionException(String message, String clientType, String phase, @Nullable String target) {
super(message);
this.clientType = clientType;
this.phase = phase;
this.target = target;
}
public VectorClientExecutionException(String message, String clientType, String phase, @Nullable String target, Throwable cause) {
super(message, cause);
this.clientType = clientType;
this.phase = phase;
this.target = target;
}
public String getClientType() {
return clientType;
}
public String getPhase() {
return phase;
}
public String getTarget() {
return target;
}
@Override
public ExceptionReport toReport() {
ExceptionReport report = super.toReport();
report.getExtra().put("clientType", clientType);
report.getExtra().put("phase", phase);
if (target != null) {
report.getExtra().put("target", target);
}
return report;
}
}

View File

@@ -1,15 +0,0 @@
package work.slhaf.partner.common.vector.exception;
import work.slhaf.partner.framework.agent.exception.deprecated.AgentRuntimeException;
public class VectorClientLoadFailedException extends AgentRuntimeException {
public VectorClientLoadFailedException(String message) {
super(message);
}
public VectorClientLoadFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,37 @@
package work.slhaf.partner.common.vector.exception;
import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.framework.agent.exception.AgentStartupException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport;
public class VectorClientStartupException extends AgentStartupException {
private final String clientType;
private final String phase;
private final String target;
public VectorClientStartupException(String message, String clientType, String phase, @Nullable String target) {
super(message, "vector-client-registry");
this.clientType = clientType;
this.phase = phase;
this.target = target;
}
public VectorClientStartupException(String message, String clientType, String phase, @Nullable String target, Throwable cause) {
super(message, "vector-client-registry", cause);
this.clientType = clientType;
this.phase = phase;
this.target = target;
}
@Override
public ExceptionReport toReport() {
ExceptionReport report = super.toReport();
report.getExtra().put("clientType", clientType);
report.getExtra().put("phase", phase);
if (target != null) {
report.getExtra().put("target", target);
}
return report;
}
}

View File

@@ -10,7 +10,7 @@ import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo; 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.InterventionType;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.action.exception.MetaActionNotFoundException; import work.slhaf.partner.core.action.exception.ActionLookupException;
import work.slhaf.partner.core.action.runner.LocalRunnerClient; import work.slhaf.partner.core.action.runner.LocalRunnerClient;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.framework.agent.config.ConfigCenter; import work.slhaf.partner.framework.agent.config.ConfigCenter;
@@ -117,12 +117,20 @@ public class ActionCore implements StateSerializable {
public MetaAction loadMetaAction(@NonNull String actionKey) { public MetaAction loadMetaAction(@NonNull String actionKey) {
MetaActionInfo metaActionInfo = existedMetaActions.get(actionKey); MetaActionInfo metaActionInfo = existedMetaActions.get(actionKey);
if (metaActionInfo == null) { if (metaActionInfo == null) {
throw new MetaActionNotFoundException("未找到对应的行动程序信息" + actionKey); throw new ActionLookupException(
"Meta action info not found for action key: " + actionKey,
actionKey,
"META_ACTION"
);
} }
String[] split = actionKey.split("::", 2); String[] split = actionKey.split("::", 2);
if (split.length < 2) { if (split.length < 2) {
throw new MetaActionNotFoundException("未找到对应的行动程序,原因: 传入的 actionKey(" + actionKey + ") 存在异常"); throw new ActionLookupException(
"Invalid action key format: " + actionKey,
actionKey,
"META_ACTION"
);
} }
MetaAction.Type type = switch (split[0]) { MetaAction.Type type = switch (split[0]) {
case BUILTIN_LOCATION -> MetaAction.Type.BUILTIN; case BUILTIN_LOCATION -> MetaAction.Type.BUILTIN;
@@ -142,7 +150,11 @@ public class ActionCore implements StateSerializable {
public MetaActionInfo loadMetaActionInfo(@NonNull String actionKey) { public MetaActionInfo loadMetaActionInfo(@NonNull String actionKey) {
MetaActionInfo info = existedMetaActions.get(actionKey); MetaActionInfo info = existedMetaActions.get(actionKey);
if (info == null) { if (info == null) {
throw new MetaActionNotFoundException("未找到对应的行动程序描述信息: " + actionKey); throw new ActionLookupException(
"Meta action description not found for action key: " + actionKey,
actionKey,
"META_ACTION_INFO"
);
} }
return info; return info;
} }

View File

@@ -0,0 +1,39 @@
package work.slhaf.partner.core.action.exception;
import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.framework.agent.exception.AgentStartupException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport;
public class ActionInfrastructureStartupException extends AgentStartupException {
private final String component;
private final String path;
private final String command;
public ActionInfrastructureStartupException(String message, String component, @Nullable String path, @Nullable String command) {
super(message, "action-core");
this.component = component;
this.path = path;
this.command = command;
}
public ActionInfrastructureStartupException(String message, String component, @Nullable String path, @Nullable String command, Throwable cause) {
super(message, "action-core", cause);
this.component = component;
this.path = path;
this.command = command;
}
@Override
public ExceptionReport toReport() {
ExceptionReport report = super.toReport();
report.getExtra().put("component", component);
if (path != null) {
report.getExtra().put("path", path);
}
if (command != null) {
report.getExtra().put("command", command);
}
return report;
}
}

View File

@@ -1,13 +0,0 @@
package work.slhaf.partner.core.action.exception;
import work.slhaf.partner.framework.agent.exception.deprecated.AgentLaunchFailedException;
public class ActionInitFailedException extends AgentLaunchFailedException {
public ActionInitFailedException(String message, Throwable cause) {
super(message, cause);
}
public ActionInitFailedException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,30 @@
package work.slhaf.partner.core.action.exception;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport;
public class ActionLookupException extends AgentRuntimeException {
private final String actionKey;
private final String lookupTarget;
public ActionLookupException(String message, String actionKey, String lookupTarget) {
super(message);
this.actionKey = actionKey;
this.lookupTarget = lookupTarget;
}
public ActionLookupException(String message, String actionKey, String lookupTarget, Throwable cause) {
super(message, cause);
this.actionKey = actionKey;
this.lookupTarget = lookupTarget;
}
@Override
public ExceptionReport toReport() {
ExceptionReport report = super.toReport();
report.getExtra().put("actionKey", actionKey);
report.getExtra().put("lookupTarget", lookupTarget);
return report;
}
}

View File

@@ -0,0 +1,58 @@
package work.slhaf.partner.core.action.exception;
import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport;
public class ActionSerializationException extends AgentRuntimeException {
private final String actionName;
private final String baseDir;
private final String fileExt;
private final String stage;
public ActionSerializationException(
String message,
@Nullable String actionName,
@Nullable String baseDir,
@Nullable String fileExt,
String stage
) {
super(message);
this.actionName = actionName;
this.baseDir = baseDir;
this.fileExt = fileExt;
this.stage = stage;
}
public ActionSerializationException(
String message,
@Nullable String actionName,
@Nullable String baseDir,
@Nullable String fileExt,
String stage,
Throwable cause
) {
super(message, cause);
this.actionName = actionName;
this.baseDir = baseDir;
this.fileExt = fileExt;
this.stage = stage;
}
@Override
public ExceptionReport toReport() {
ExceptionReport report = super.toReport();
report.getExtra().put("stage", stage);
if (actionName != null) {
report.getExtra().put("actionName", actionName);
}
if (baseDir != null) {
report.getExtra().put("baseDir", baseDir);
}
if (fileExt != null) {
report.getExtra().put("fileExt", fileExt);
}
return report;
}
}

View File

@@ -1,13 +0,0 @@
package work.slhaf.partner.core.action.exception;
import work.slhaf.partner.framework.agent.exception.deprecated.AgentRuntimeException;
public class ActionSerializeFailedException extends AgentRuntimeException {
public ActionSerializeFailedException(String message) {
super(message);
}
public ActionSerializeFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,13 +0,0 @@
package work.slhaf.partner.core.action.exception;
import work.slhaf.partner.framework.agent.exception.deprecated.AgentRuntimeException;
public class MetaActionNotFoundException extends AgentRuntimeException {
public MetaActionNotFoundException(String message) {
super(message);
}
public MetaActionNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.core.action.entity.ActionFileMetaData; import work.slhaf.partner.core.action.entity.ActionFileMetaData;
import work.slhaf.partner.core.action.entity.MetaAction; import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.ActionInitFailedException; import work.slhaf.partner.core.action.exception.ActionInfrastructureStartupException;
import work.slhaf.partner.core.action.runner.execution.McpActionExecutor; import work.slhaf.partner.core.action.runner.execution.McpActionExecutor;
import work.slhaf.partner.core.action.runner.execution.OriginExecutionService; import work.slhaf.partner.core.action.runner.execution.OriginExecutionService;
import work.slhaf.partner.core.action.runner.mcp.*; import work.slhaf.partner.core.action.runner.mcp.*;
@@ -97,13 +97,26 @@ public class LocalRunnerClient extends RunnerClient implements AutoCloseable {
); );
configWatcher.start(); configWatcher.start();
configWatcher.registerPolicyListener(); configWatcher.registerPolicyListener();
} catch (ActionInfrastructureStartupException e) {
closeQuietly(configWatcher);
closeQuietly(dynamicManager);
closeQuietly(descWatcher);
closeQuietly(metaRegistry);
closeQuietly(clientRegistry);
throw e;
} catch (Exception e) { } catch (Exception e) {
closeQuietly(configWatcher); closeQuietly(configWatcher);
closeQuietly(dynamicManager); closeQuietly(dynamicManager);
closeQuietly(descWatcher); closeQuietly(descWatcher);
closeQuietly(metaRegistry); closeQuietly(metaRegistry);
closeQuietly(clientRegistry); closeQuietly(clientRegistry);
throw new ActionInitFailedException("LocalRunnerClient 初始化失败", e); throw new ActionInfrastructureStartupException(
"LocalRunnerClient initialization failed",
"local-runner-client",
ACTION_PATH,
null,
e
);
} }
this.mcpClientRegistry = clientRegistry; this.mcpClientRegistry = clientRegistry;

View File

@@ -9,7 +9,7 @@ import work.slhaf.partner.core.action.entity.ActionFileMetaData;
import work.slhaf.partner.core.action.entity.MetaAction; 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.Result;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.ActionInitFailedException; import work.slhaf.partner.core.action.exception.ActionInfrastructureStartupException;
import work.slhaf.partner.module.action.builtin.BuiltinActionRegistry; import work.slhaf.partner.module.action.builtin.BuiltinActionRegistry;
import java.io.IOException; import java.io.IOException;
@@ -101,7 +101,13 @@ public abstract class RunnerClient implements AutoCloseable {
Files.createDirectory(path); Files.createDirectory(path);
} catch (IOException e) { } catch (IOException e) {
if (!Files.exists(path)) { if (!Files.exists(path)) {
throw new ActionInitFailedException("目录创建失败: " + pathStr, e); throw new ActionInfrastructureStartupException(
"Failed to create action directory: " + pathStr,
"runner-client",
pathStr,
null,
e
);
} }
} }
} }

View File

@@ -13,7 +13,7 @@ import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import work.slhaf.partner.common.mcp.InProcessMcpTransport; import work.slhaf.partner.common.mcp.InProcessMcpTransport;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.ActionInitFailedException; import work.slhaf.partner.core.action.exception.ActionInfrastructureStartupException;
import work.slhaf.partner.core.action.runner.execution.CommandExecutionService; import work.slhaf.partner.core.action.runner.execution.CommandExecutionService;
import work.slhaf.partner.framework.agent.support.DirectoryWatchSupport; import work.slhaf.partner.framework.agent.support.DirectoryWatchSupport;
@@ -77,11 +77,21 @@ public class DynamicActionMcpManager implements AutoCloseable {
private void loadExisting() { private void loadExisting() {
File file = root.toFile(); File file = root.toFile();
if (file.isFile()) { if (file.isFile()) {
throw new ActionInitFailedException("未找到目录: " + root); throw new ActionInfrastructureStartupException(
"Expected a directory but found a file: " + root,
"dynamic-action-mcp-manager",
root.toString(),
null
);
} }
File[] files = file.listFiles(); File[] files = file.listFiles();
if (files == null) { if (files == null) {
throw new ActionInitFailedException("未正常读取目录: " + root); throw new ActionInfrastructureStartupException(
"Failed to read action directory: " + root,
"dynamic-action-mcp-manager",
root.toString(),
null
);
} }
for (File dir : files) { for (File dir : files) {
if (!normalPath(dir.toPath())) { if (!normalPath(dir.toPath())) {

View File

@@ -1,6 +1,6 @@
package work.slhaf.partner.core.action.runner.policy package work.slhaf.partner.core.action.runner.policy
import work.slhaf.partner.core.action.exception.ActionInitFailedException import work.slhaf.partner.core.action.exception.ActionInfrastructureStartupException
private const val BWRAP_COMMAND = "bwrap" private const val BWRAP_COMMAND = "bwrap"
@@ -62,10 +62,21 @@ object BwrapPolicyProvider : PolicyProvider(
val exitCode = process.waitFor() val exitCode = process.waitFor()
exitCode == 0 exitCode == 0
} catch (e: Exception) { } catch (e: Exception) {
throw ActionInitFailedException("bwrap provider 初始化失败: 无法检测 $BWRAP_COMMAND 可执行文件", e) throw ActionInfrastructureStartupException(
"Failed to detect executable command '$BWRAP_COMMAND'",
"bwrap-policy-provider",
null,
BWRAP_COMMAND,
e
)
} }
if (!available) { if (!available) {
throw ActionInitFailedException("bwrap provider 初始化失败: 未检测到可执行命令 '$BWRAP_COMMAND'") throw ActionInfrastructureStartupException(
"Executable command '$BWRAP_COMMAND' is not available",
"bwrap-policy-provider",
null,
BWRAP_COMMAND
)
} }
} }
} }

View File

@@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
import work.slhaf.partner.core.action.entity.ActionFileMetaData; import work.slhaf.partner.core.action.entity.ActionFileMetaData;
import work.slhaf.partner.core.action.entity.MetaAction; import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.exception.ActionSerializeFailedException; import work.slhaf.partner.core.action.exception.ActionSerializationException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -34,7 +34,7 @@ public class ActionSerializer {
return codeType.startsWith(".") ? codeType : "." + codeType; return codeType.startsWith(".") ? codeType : "." + codeType;
} }
private static @NotNull Path createActionDir(String baseName, Path baseDir) { private static @NotNull Path createActionDir(String baseName, Path baseDir, String fileExt) {
for (int i = 0; ; i++) { for (int i = 0; ; i++) {
String dirName = i == 0 ? baseName : baseName + "(" + i + ")"; String dirName = i == 0 ? baseName : baseName + "(" + i + ")";
Path candidate = baseDir.resolve(dirName); Path candidate = baseDir.resolve(dirName);
@@ -43,7 +43,14 @@ public class ActionSerializer {
return candidate; return candidate;
} catch (FileAlreadyExistsException ignored) { } catch (FileAlreadyExistsException ignored) {
} catch (IOException e) { } catch (IOException e) {
throw new ActionSerializeFailedException("无法创建行动目录: " + candidate.toAbsolutePath(), e); throw new ActionSerializationException(
"Failed to create action directory: " + candidate.toAbsolutePath(),
baseName,
baseDir.toAbsolutePath().toString(),
fileExt,
"CREATE_DIRECTORY",
e
);
} }
} }
} }
@@ -75,10 +82,16 @@ public class ActionSerializer {
val baseDir = Path.of(dynamicActionPath); val baseDir = Path.of(dynamicActionPath);
if (!Files.isDirectory(baseDir)) { if (!Files.isDirectory(baseDir)) {
throw new ActionSerializeFailedException("目录不存在或不可用: " + baseDir.toAbsolutePath()); throw new ActionSerializationException(
"Action base directory is not available: " + baseDir.toAbsolutePath(),
fileMetaData.getName(),
baseDir.toAbsolutePath().toString(),
fileMetaData.getExt(),
"VALIDATE_BASE_DIR"
);
} }
val actionDir = createActionDir(fileMetaData.getName(), baseDir); val actionDir = createActionDir(fileMetaData.getName(), baseDir, fileMetaData.getExt());
val runTmp = actionDir.resolve("run." + fileMetaData.getExt() + ".tmp"); val runTmp = actionDir.resolve("run." + fileMetaData.getExt() + ".tmp");
val descTmp = actionDir.resolve("desc.json.tmp"); val descTmp = actionDir.resolve("desc.json.tmp");
val runFinal = actionDir.resolve("run." + fileMetaData.getExt()); val runFinal = actionDir.resolve("run." + fileMetaData.getExt());
@@ -95,7 +108,14 @@ public class ActionSerializer {
safeDelete(runFinal); safeDelete(runFinal);
safeDelete(descFinal); safeDelete(descFinal);
safeDelete(actionDir); safeDelete(actionDir);
throw new ActionSerializeFailedException("行动文件写入失败", e); throw new ActionSerializationException(
"Failed to persist action files",
fileMetaData.getName(),
baseDir.toAbsolutePath().toString(),
fileMetaData.getExt(),
"WRITE_FILES",
e
);
} }
log.debug("持久序列化结束"); log.debug("持久序列化结束");
} }

View File

@@ -4,7 +4,7 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import work.slhaf.partner.core.action.ActionCapability; 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.ActionLookupException;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init; import work.slhaf.partner.framework.agent.factory.component.annotation.Init;
@@ -57,7 +57,11 @@ public class BuiltinActionRegistry extends AbstractAgentModule.Standalone {
public String call(@NonNull String actionKey, @NonNull Map<String, Object> params) { public String call(@NonNull String actionKey, @NonNull Map<String, Object> params) {
BuiltinActionDefinition definition = definitions.get(actionKey); BuiltinActionDefinition definition = definitions.get(actionKey);
if (definition == null) { if (definition == null) {
throw new MetaActionNotFoundException("未找到对应的内置行动程序: " + actionKey); throw new ActionLookupException(
"Builtin action definition not found: " + actionKey,
actionKey,
"BUILTIN_DEFINITION"
);
} }
String result = definition.invoker().apply(params); String result = definition.invoker().apply(params);
if (result == null) { if (result == null) {

View File

@@ -34,5 +34,10 @@ public class RunnerClientTest {
public void persistSerialize(MetaActionInfo metaActionInfo, ActionFileMetaData fileMetaData) { public void persistSerialize(MetaActionInfo metaActionInfo, ActionFileMetaData fileMetaData) {
} }
@Override
public void close() {
}
} }
} }

View File

@@ -1,8 +1,8 @@
package work.slhaf.partner.core.action.runner.policy package work.slhaf.partner.core.action.runner.policy
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import work.slhaf.partner.core.action.exception.ActionInitFailedException
class BwrapPolicyProviderTest { class BwrapPolicyProviderTest {
@@ -59,28 +59,4 @@ class BwrapPolicyProviderTest {
assertFalse(wrapped.args.contains("--unshare-net")) assertFalse(wrapped.args.contains("--unshare-net"))
} }
@Test
fun `require command available throws ActionInitFailedException when command missing`() {
val exception = assertThrows(ActionInitFailedException::class.java) {
bwrapPolicyFileFacadeClass().requireCommandAvailable("definitely-not-found-bwrap-command")
}
assertTrue(exception.message!!.contains("definitely-not-found-bwrap-command"))
}
}
private fun bwrapPolicyFileFacadeClass(): Class<*> {
return Class.forName("work.slhaf.partner.core.action.runner.policy.BwrapPolicyProviderKt")
}
private fun Class<*>.requireCommandAvailable(command: String) {
val method = getDeclaredMethod("requireCommandAvailable", String::class.java)
method.isAccessible = true
try {
method.invoke(null, command)
} catch (e: java.lang.reflect.InvocationTargetException) {
throw (e.targetException as? RuntimeException)
?: (e.targetException as? Error)
?: RuntimeException(e.targetException)
}
} }

View File

@@ -5,7 +5,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import work.slhaf.partner.core.action.ActionCapability; 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.ActionLookupException;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@@ -80,14 +80,14 @@ class BuiltinActionRegistryTest {
registry.defineBuiltinAction("nil", buildMetaActionInfo("nil"), params -> null); registry.defineBuiltinAction("nil", buildMetaActionInfo("nil"), params -> null);
Assertions.assertEquals("hello", registry.call("builtin::echo", Map.of("value", "hello"))); Assertions.assertEquals("hello", registry.call("builtin::echo", Map.of("value", "hello")));
Assertions.assertEquals("{\"ok\":true}", registry.call("builtin::json", Map.of())); Assertions.assertEquals("{ok=true}", registry.call("builtin::json", Map.of()));
Assertions.assertEquals("null", registry.call("builtin::nil", Map.of())); Assertions.assertEquals("null", registry.call("builtin::nil", Map.of()));
} }
@Test @Test
void testCallThrowsWhenMissingDefinition() { void testCallThrowsWhenMissingDefinition() {
BuiltinActionRegistry registry = new BuiltinActionRegistry(); BuiltinActionRegistry registry = new BuiltinActionRegistry();
Assertions.assertThrows(MetaActionNotFoundException.class, () -> registry.call("builtin::missing", Map.of())); Assertions.assertThrows(ActionLookupException.class, () -> registry.call("builtin::missing", Map.of()));
} }
@Test @Test

View File

@@ -201,5 +201,10 @@ class BuiltinDynamicActionProviderTest {
public void persistSerialize(MetaActionInfo metaActionInfo, ActionFileMetaData fileMetaData) { public void persistSerialize(MetaActionInfo metaActionInfo, ActionFileMetaData fileMetaData) {
persistCalled = true; persistCalled = true;
} }
@Override
public void close() {
}
} }
} }

View File

@@ -1,12 +0,0 @@
package work.slhaf.partner.framework.agent.exception.deprecated;
@Deprecated
public class AgentLaunchFailedException extends RuntimeException {
public AgentLaunchFailedException(String message, Throwable cause) {
super("Agent 启动失败 " + message, cause);
}
public AgentLaunchFailedException(String message) {
super("Agent 启动失败 " + message);
}
}

View File

@@ -1,12 +0,0 @@
package work.slhaf.partner.framework.agent.exception.deprecated;
@Deprecated
public class AgentRunningFailedException extends AgentRuntimeException {
public AgentRunningFailedException(String message) {
super(message);
}
public AgentRunningFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,12 +0,0 @@
package work.slhaf.partner.framework.agent.exception.deprecated;
@Deprecated
public class AgentRuntimeException extends RuntimeException {
public AgentRuntimeException(String message) {
super("Agent 执行出错 " + message);
}
public AgentRuntimeException(String message, Throwable cause) {
super("Agent 执行出错 " + message, cause);
}
}

View File

@@ -21,9 +21,8 @@ class Result<T> private constructor(
return value as T return value as T
} }
when (exception) { when (exception) {
is RuntimeException -> throw exception is AgentRuntimeException, is Error -> throw exception
is Error -> throw exception else -> throw AgentRuntimeException(exception.localizedMessage, exception)
else -> throw IllegalStateException(exception.message, exception)
} }
} }