diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolAdapter.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolAdapter.java
new file mode 100644
index 0000000..e7a64c1
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolAdapter.java
@@ -0,0 +1,184 @@
+package com.easyagents.agent.runtime.tool.operate;
+
+import com.easyagents.agent.runtime.AgentRuntimeException;
+import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
+import com.easyagents.agent.runtime.tool.AgentToolCategory;
+import com.easyagents.agent.runtime.tool.AgentToolSpec;
+import com.easyagents.agent.runtime.tool.AgentToolVisibility;
+import io.agentscope.core.tool.Toolkit;
+import io.agentscope.core.tool.coding.ShellCommandTool;
+import io.agentscope.core.tool.file.ReadFileTool;
+import io.agentscope.core.tool.file.WriteFileTool;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.*;
+
+/**
+ * AgentScope 内置操作工具适配器。
+ *
+ *
该适配器只负责将 Easy-Agents 的操作工具声明转换为 AgentScope Toolkit 中的原生工具。
+ * Shell 工具的人工审批不使用 AgentScope {@code ShellCommandTool} 的同步 callback,而是通过
+ * Easy-Agents 现有 {@code ToolHitlInterceptor} 统一处理,以保持 SSE 暂停、恢复和审计语义一致。
+ */
+public class AgentOperateToolAdapter {
+
+ public static final String VIEW_TEXT_FILE_TOOL = "view_text_file";
+ public static final String LIST_DIRECTORY_TOOL = "list_directory";
+ public static final String WRITE_TEXT_FILE_TOOL = "write_text_file";
+ public static final String INSERT_TEXT_FILE_TOOL = "insert_text_file";
+ public static final String EXECUTE_SHELL_COMMAND_TOOL = "execute_shell_command";
+
+ /**
+ * 将操作工具声明注册到 Toolkit,并返回供 HITL 拦截器使用的工具声明。
+ *
+ * @param specs 操作工具声明
+ * @param toolkit AgentScope Toolkit
+ * @return 已启用操作工具对应的 AgentToolSpec
+ */
+ public List register(List specs, Toolkit toolkit) {
+ List toolSpecs = new ArrayList<>();
+ if (specs == null || specs.isEmpty()) {
+ return toolSpecs;
+ }
+ for (AgentOperateToolSpec spec : specs) {
+ if (spec == null || !spec.isEnabled()) {
+ continue;
+ }
+ registerOne(spec, toolkit, toolSpecs);
+ }
+ return toolSpecs;
+ }
+
+ /**
+ * 解析已启用操作工具会注册到 AgentScope 的工具名称。
+ *
+ * @param specs 操作工具声明
+ * @return 工具名称集合
+ */
+ public Set enabledToolNames(List specs) {
+ Set names = new LinkedHashSet<>();
+ if (specs == null || specs.isEmpty()) {
+ return names;
+ }
+ for (AgentOperateToolSpec spec : specs) {
+ if (spec == null || !spec.isEnabled() || spec.getType() == null) {
+ continue;
+ }
+ switch (spec.getType()) {
+ case READ_FILE -> {
+ names.add(VIEW_TEXT_FILE_TOOL);
+ names.add(LIST_DIRECTORY_TOOL);
+ }
+ case WRITE_FILE -> {
+ names.add(WRITE_TEXT_FILE_TOOL);
+ names.add(INSERT_TEXT_FILE_TOOL);
+ }
+ case SHELL -> names.add(EXECUTE_SHELL_COMMAND_TOOL);
+ default -> {
+ }
+ }
+ }
+ return names;
+ }
+
+ private void registerOne(AgentOperateToolSpec spec, Toolkit toolkit, List toolSpecs) {
+ AgentOperateToolType type = spec.getType();
+ if (type == null) {
+ throw new AgentRuntimeException("Agent operate tool type is required.");
+ }
+ Path baseDir = validateBaseDir(spec);
+ switch (type) {
+ case READ_FILE -> {
+ assertNoToolConflict(toolkit, VIEW_TEXT_FILE_TOOL);
+ assertNoToolConflict(toolkit, LIST_DIRECTORY_TOOL);
+ toolkit.registerTool(new ReadFileTool(baseDir.toString()));
+ toolSpecs.add(toolSpec(spec, VIEW_TEXT_FILE_TOOL, "View text file content.", false));
+ toolSpecs.add(toolSpec(spec, LIST_DIRECTORY_TOOL, "List files and directories.", false));
+ }
+ case WRITE_FILE -> {
+ assertNoToolConflict(toolkit, WRITE_TEXT_FILE_TOOL);
+ assertNoToolConflict(toolkit, INSERT_TEXT_FILE_TOOL);
+ toolkit.registerTool(new WriteFileTool(baseDir.toString()));
+ toolSpecs.add(toolSpec(spec, WRITE_TEXT_FILE_TOOL, "Write or replace text file content.", true));
+ toolSpecs.add(toolSpec(spec, INSERT_TEXT_FILE_TOOL, "Insert text into a file.", true));
+ }
+ case SHELL -> {
+ assertNoToolConflict(toolkit, EXECUTE_SHELL_COMMAND_TOOL);
+ Charset charset = parseCharset(spec);
+ toolkit.registerAgentTool(new ShellCommandTool(baseDir.toString(), spec.getShellAllowedCommands(), null,
+ null, charset));
+ toolSpecs.add(toolSpec(spec, EXECUTE_SHELL_COMMAND_TOOL, "Execute shell command.", true));
+ }
+ default -> throw new AgentRuntimeException("Unsupported agent operate tool type: " + type);
+ }
+ }
+
+ private Path validateBaseDir(AgentOperateToolSpec spec) {
+ String baseDir = spec.getBaseDir();
+ if (baseDir == null || baseDir.isBlank()) {
+ throw new AgentRuntimeException("Agent operate tool baseDir is required.");
+ }
+ Path path = Path.of(baseDir).toAbsolutePath().normalize();
+ if (!Path.of(baseDir).isAbsolute()) {
+ throw new AgentRuntimeException("Agent operate tool baseDir must be an absolute path: " + baseDir);
+ }
+ return path;
+ }
+
+ private Charset parseCharset(AgentOperateToolSpec spec) {
+ String charsetName = spec.getShellCharset();
+ if (charsetName == null || charsetName.isBlank()) {
+ return StandardCharsets.UTF_8;
+ }
+ try {
+ return Charset.forName(charsetName.trim());
+ } catch (Exception error) {
+ throw new AgentRuntimeException("Invalid shell charset: " + charsetName, error);
+ }
+ }
+
+ private AgentToolSpec toolSpec(AgentOperateToolSpec operateSpec,
+ String toolName,
+ String description,
+ boolean defaultApprovalRequired) {
+ boolean approvalRequired = operateSpec.getApprovalRequired() == null
+ ? defaultApprovalRequired : operateSpec.getApprovalRequired();
+ AgentToolSpec toolSpec = new AgentToolSpec();
+ toolSpec.setName(toolName);
+ toolSpec.setDescription(description);
+ toolSpec.setCategory(AgentToolCategory.CUSTOM);
+ toolSpec.setVisibility(AgentToolVisibility.VISIBLE);
+ toolSpec.setApprovalRequired(approvalRequired);
+ toolSpec.setApprovalRequest(approvalRequest(operateSpec, approvalRequired));
+ toolSpec.setMetadata(metadata(operateSpec));
+ return toolSpec;
+ }
+
+ private AgentToolApprovalRequest approvalRequest(AgentOperateToolSpec operateSpec, boolean approvalRequired) {
+ AgentToolApprovalRequest request = operateSpec.getApprovalRequest();
+ if (request != null) {
+ return request;
+ }
+ AgentToolApprovalRequest defaultRequest = new AgentToolApprovalRequest();
+ if (approvalRequired) {
+ defaultRequest.setApprovalPrompt("请确认是否允许智能体执行该操作工具。");
+ }
+ return defaultRequest;
+ }
+
+ private Map metadata(AgentOperateToolSpec spec) {
+ Map metadata = new LinkedHashMap<>();
+ metadata.put("operateTool", true);
+ metadata.put("operateToolType", spec.getType().name());
+ metadata.put("baseDir", spec.getBaseDir());
+ return metadata;
+ }
+
+ private void assertNoToolConflict(Toolkit toolkit, String toolName) {
+ if (toolkit.getTool(toolName) != null) {
+ throw new AgentRuntimeException("Agent operate tool conflicts with existing tool: " + toolName);
+ }
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolSpec.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolSpec.java
new file mode 100644
index 0000000..78df6de
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolSpec.java
@@ -0,0 +1,150 @@
+package com.easyagents.agent.runtime.tool.operate;
+
+import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Agent 操作类工具声明。
+ *
+ * 操作类工具是 runtime 直接适配的 AgentScope 内置工具,用于读文件、写文件和执行 Shell。
+ * 这些工具直接作用于后端 JVM 所在宿主环境,调用方必须按 agent、session 或 user 维度传入受控
+ * 的绝对工作目录。
+ */
+public class AgentOperateToolSpec {
+
+ private AgentOperateToolType type;
+ private boolean enabled = true;
+ private String baseDir;
+ private Boolean approvalRequired;
+ private AgentToolApprovalRequest approvalRequest;
+ private Set shellAllowedCommands = new LinkedHashSet<>();
+ private String shellCharset;
+
+ /**
+ * 获取操作工具类型。
+ *
+ * @return 操作工具类型
+ */
+ public AgentOperateToolType getType() {
+ return type;
+ }
+
+ /**
+ * 设置操作工具类型。
+ *
+ * @param type 操作工具类型
+ */
+ public void setType(AgentOperateToolType type) {
+ this.type = type;
+ }
+
+ /**
+ * 返回是否启用该操作工具。
+ *
+ * @return 启用时为 true
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * 设置是否启用该操作工具。
+ *
+ * @param enabled 启用标记
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * 获取操作工具工作目录。
+ *
+ * @return 绝对工作目录
+ */
+ public String getBaseDir() {
+ return baseDir;
+ }
+
+ /**
+ * 设置操作工具工作目录。
+ *
+ * @param baseDir 绝对工作目录
+ */
+ public void setBaseDir(String baseDir) {
+ this.baseDir = baseDir;
+ }
+
+ /**
+ * 获取审批开关覆盖值。
+ *
+ * @return 审批覆盖值,null 表示使用工具类型默认值
+ */
+ public Boolean getApprovalRequired() {
+ return approvalRequired;
+ }
+
+ /**
+ * 设置审批开关覆盖值。
+ *
+ * @param approvalRequired 审批覆盖值
+ */
+ public void setApprovalRequired(Boolean approvalRequired) {
+ this.approvalRequired = approvalRequired;
+ }
+
+ /**
+ * 获取审批请求配置。
+ *
+ * @return 审批请求配置
+ */
+ public AgentToolApprovalRequest getApprovalRequest() {
+ return approvalRequest;
+ }
+
+ /**
+ * 设置审批请求配置。
+ *
+ * @param approvalRequest 审批请求配置
+ */
+ public void setApprovalRequest(AgentToolApprovalRequest approvalRequest) {
+ this.approvalRequest = approvalRequest;
+ }
+
+ /**
+ * 获取 Shell 命令白名单。
+ *
+ * @return Shell 命令白名单
+ */
+ public Set getShellAllowedCommands() {
+ return shellAllowedCommands;
+ }
+
+ /**
+ * 设置 Shell 命令白名单。
+ *
+ * @param shellAllowedCommands Shell 命令白名单
+ */
+ public void setShellAllowedCommands(Set shellAllowedCommands) {
+ this.shellAllowedCommands = shellAllowedCommands == null ? new LinkedHashSet<>() : new LinkedHashSet<>(shellAllowedCommands);
+ }
+
+ /**
+ * 获取 Shell 输出字符集名称。
+ *
+ * @return 字符集名称
+ */
+ public String getShellCharset() {
+ return shellCharset;
+ }
+
+ /**
+ * 设置 Shell 输出字符集名称。
+ *
+ * @param shellCharset 字符集名称
+ */
+ public void setShellCharset(String shellCharset) {
+ this.shellCharset = shellCharset;
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolType.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolType.java
new file mode 100644
index 0000000..e566420
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolType.java
@@ -0,0 +1,22 @@
+package com.easyagents.agent.runtime.tool.operate;
+
+/**
+ * Agent 操作类工具类型。
+ */
+public enum AgentOperateToolType {
+
+ /**
+ * 读取文本文件与列出目录。
+ */
+ READ_FILE,
+
+ /**
+ * 写入、覆盖或插入文本文件。
+ */
+ WRITE_FILE,
+
+ /**
+ * 在服务进程所在宿主环境执行 Shell 命令。
+ */
+ SHELL
+}
diff --git a/easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolAdapterTest.java b/easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolAdapterTest.java
new file mode 100644
index 0000000..0accff4
--- /dev/null
+++ b/easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/tool/operate/AgentOperateToolAdapterTest.java
@@ -0,0 +1,101 @@
+package com.easyagents.agent.runtime.tool.operate;
+
+import com.easyagents.agent.runtime.AgentRuntimeException;
+import com.easyagents.agent.runtime.tool.AgentToolSpec;
+import io.agentscope.core.tool.Toolkit;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 测试 Agent 操作类工具适配器。
+ */
+public class AgentOperateToolAdapterTest {
+
+ private final AgentOperateToolAdapter adapter = new AgentOperateToolAdapter();
+
+ @Test
+ public void shouldRegisterReadFileToolsWithDefaultHitlDisabled() {
+ Toolkit toolkit = new Toolkit();
+ AgentOperateToolSpec spec = spec(AgentOperateToolType.READ_FILE);
+
+ List toolSpecs = adapter.register(List.of(spec), toolkit);
+
+ Assert.assertNotNull(toolkit.getTool(AgentOperateToolAdapter.VIEW_TEXT_FILE_TOOL));
+ Assert.assertNotNull(toolkit.getTool(AgentOperateToolAdapter.LIST_DIRECTORY_TOOL));
+ Assert.assertEquals(2, toolSpecs.size());
+ Assert.assertTrue(toolSpecs.stream().noneMatch(AgentToolSpec::isApprovalRequired));
+ }
+
+ @Test
+ public void shouldRegisterWriteFileToolsWithDefaultHitlEnabled() {
+ Toolkit toolkit = new Toolkit();
+ AgentOperateToolSpec spec = spec(AgentOperateToolType.WRITE_FILE);
+
+ List toolSpecs = adapter.register(List.of(spec), toolkit);
+
+ Assert.assertNotNull(toolkit.getTool(AgentOperateToolAdapter.WRITE_TEXT_FILE_TOOL));
+ Assert.assertNotNull(toolkit.getTool(AgentOperateToolAdapter.INSERT_TEXT_FILE_TOOL));
+ Assert.assertEquals(2, toolSpecs.size());
+ Assert.assertTrue(toolSpecs.stream().allMatch(AgentToolSpec::isApprovalRequired));
+ }
+
+ @Test
+ public void shouldRegisterShellToolWithEmptyWhitelist() {
+ Toolkit toolkit = new Toolkit();
+ AgentOperateToolSpec spec = spec(AgentOperateToolType.SHELL);
+ spec.setShellAllowedCommands(Set.of());
+
+ List toolSpecs = adapter.register(List.of(spec), toolkit);
+
+ Assert.assertNotNull(toolkit.getTool(AgentOperateToolAdapter.EXECUTE_SHELL_COMMAND_TOOL));
+ Assert.assertEquals(1, toolSpecs.size());
+ Assert.assertTrue(toolSpecs.get(0).isApprovalRequired());
+ }
+
+ @Test
+ public void shouldSkipDisabledOperateTool() {
+ Toolkit toolkit = new Toolkit();
+ AgentOperateToolSpec spec = spec(AgentOperateToolType.SHELL);
+ spec.setEnabled(false);
+
+ List toolSpecs = adapter.register(List.of(spec), toolkit);
+
+ Assert.assertNull(toolkit.getTool(AgentOperateToolAdapter.EXECUTE_SHELL_COMMAND_TOOL));
+ Assert.assertTrue(toolSpecs.isEmpty());
+ }
+
+ @Test(expected = AgentRuntimeException.class)
+ public void shouldRejectMissingBaseDir() {
+ AgentOperateToolSpec spec = spec(AgentOperateToolType.READ_FILE);
+ spec.setBaseDir(null);
+
+ adapter.register(List.of(spec), new Toolkit());
+ }
+
+ @Test(expected = AgentRuntimeException.class)
+ public void shouldRejectRelativeBaseDir() {
+ AgentOperateToolSpec spec = spec(AgentOperateToolType.READ_FILE);
+ spec.setBaseDir("relative/workspace");
+
+ adapter.register(List.of(spec), new Toolkit());
+ }
+
+ @Test(expected = AgentRuntimeException.class)
+ public void shouldRejectToolNameConflict() {
+ Toolkit toolkit = new Toolkit();
+ AgentOperateToolSpec first = spec(AgentOperateToolType.READ_FILE);
+ AgentOperateToolSpec second = spec(AgentOperateToolType.READ_FILE);
+
+ adapter.register(List.of(first, second), toolkit);
+ }
+
+ private AgentOperateToolSpec spec(AgentOperateToolType type) {
+ AgentOperateToolSpec spec = new AgentOperateToolSpec();
+ spec.setType(type);
+ spec.setBaseDir(System.getProperty("java.io.tmpdir"));
+ return spec;
+ }
+}