mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +08:00
fix: pass workspace context to tool guard (#617)
This commit is contained in:
parent
dd7e561e48
commit
e88be95cd2
@ -483,6 +483,12 @@ public class ToolExecutionExecutor {
|
|||||||
ChatOrigin origin,
|
ChatOrigin origin,
|
||||||
Set<String> loadedSkills) {
|
Set<String> loadedSkills) {
|
||||||
ChatOrigin safeOrigin = origin != null ? origin : ChatOrigin.EMPTY;
|
ChatOrigin safeOrigin = origin != null ? origin : ChatOrigin.EMPTY;
|
||||||
|
if (isBlank(safeOrigin.conversationId()) && !isBlank(conversationId)) {
|
||||||
|
safeOrigin = safeOrigin.withConversationId(conversationId);
|
||||||
|
}
|
||||||
|
if (isBlank(safeOrigin.workspaceBasePath()) && !isBlank(workspaceBasePath)) {
|
||||||
|
safeOrigin = safeOrigin.withWorkspace(safeOrigin.workspaceId(), workspaceBasePath);
|
||||||
|
}
|
||||||
// Reset per-turn audit dedupe state. A retried denied tool inside the
|
// Reset per-turn audit dedupe state. A retried denied tool inside the
|
||||||
// same turn writes a single audit row; the set is repopulated by the
|
// same turn writes a single audit row; the set is repopulated by the
|
||||||
// denial branch below.
|
// denial branch below.
|
||||||
@ -1297,6 +1303,10 @@ public class ToolExecutionExecutor {
|
|||||||
return GuardDecision.allowed();
|
return GuardDecision.allowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isBlank(String value) {
|
||||||
|
return value == null || value.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deny an approval-required tool when the run is non-interactive (no human can
|
* Deny an approval-required tool when the run is non-interactive (no human can
|
||||||
* approve), returning an actionable message so the agent falls back to a
|
* approve), returning an actionable message so the agent falls back to a
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
package vip.mate.agent.graph.executor;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||||
|
import org.springframework.ai.chat.model.ToolContext;
|
||||||
|
import org.springframework.ai.tool.ToolCallback;
|
||||||
|
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||||
|
import org.springframework.ai.tool.metadata.ToolMetadata;
|
||||||
|
import vip.mate.agent.AgentToolSet;
|
||||||
|
import vip.mate.agent.context.ChatOrigin;
|
||||||
|
import vip.mate.tool.guard.model.GuardEvaluation;
|
||||||
|
import vip.mate.tool.guard.model.ToolInvocationContext;
|
||||||
|
import vip.mate.tool.guard.service.ToolGuardService;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class ToolExecutionExecutorWorkspaceGuardContextTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("guard evaluation receives state workspaceBasePath when origin has no base path (#617)")
|
||||||
|
void guardUsesWorkspaceBasePathArgumentWhenOriginIsBlank(@TempDir Path workspaceRoot) {
|
||||||
|
ToolGuardService guardService = mock(ToolGuardService.class);
|
||||||
|
when(guardService.evaluate(any(ToolInvocationContext.class), eq(true)))
|
||||||
|
.thenReturn(GuardEvaluation.allow("write_file"));
|
||||||
|
ToolExecutionExecutor executor = new ToolExecutionExecutor(
|
||||||
|
AgentToolSet.fromCallbacks(List.of(), List.of(stub("write_file"))),
|
||||||
|
guardService,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
|
||||||
|
executor.execute(
|
||||||
|
List.of(new AssistantMessage.ToolCall(
|
||||||
|
"call_1", "function", "write_file",
|
||||||
|
"{\"filePath\":\"deck.md\",\"content\":\"# Deck\"}")),
|
||||||
|
"conv-617",
|
||||||
|
"agent-617",
|
||||||
|
false,
|
||||||
|
"alice",
|
||||||
|
workspaceRoot.toString(),
|
||||||
|
ChatOrigin.web("conv-617", "alice", 1L, null));
|
||||||
|
|
||||||
|
ArgumentCaptor<ToolInvocationContext> captor = ArgumentCaptor.forClass(ToolInvocationContext.class);
|
||||||
|
verify(guardService).evaluate(captor.capture(), eq(true));
|
||||||
|
assertThat(captor.getValue().workspaceBasePath()).isEqualTo(workspaceRoot.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ToolCallback stub(String name) {
|
||||||
|
ToolDefinition def = ToolDefinition.builder()
|
||||||
|
.name(name)
|
||||||
|
.description("test tool " + name)
|
||||||
|
.inputSchema("{\"type\":\"object\",\"properties\":{}}")
|
||||||
|
.build();
|
||||||
|
ToolMetadata md = ToolMetadata.builder().returnDirect(false).build();
|
||||||
|
return new ToolCallback() {
|
||||||
|
@Override public ToolDefinition getToolDefinition() { return def; }
|
||||||
|
@Override public ToolMetadata getToolMetadata() { return md; }
|
||||||
|
@Override public String call(String arguments) { return "ok"; }
|
||||||
|
@Override public String call(String arguments, ToolContext toolContext) { return "ok"; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user