mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
fix(goal): keep managed JSON checks repeatable in progress hints
This commit is contained in:
parent
e8a13b8d20
commit
6c0a60ce14
@ -61,7 +61,7 @@ public class ActionNode implements NodeAction {
|
||||
|
||||
/**
|
||||
* Tools whose results should NOT be auto-recorded into the ledger.
|
||||
* Two groups:
|
||||
* Three groups:
|
||||
* <ul>
|
||||
* <li><b>Meta-tools</b> (load_skill, enable_tool, progress_update,
|
||||
* skill helpers) — they either have their own ledger side-effects
|
||||
@ -72,6 +72,8 @@ public class ActionNode implements NodeAction {
|
||||
* answer follow-up questions from stale output instead of
|
||||
* re-checking, because the snapshot instructs "已完成的步骤不要
|
||||
* 重复执行".</li>
|
||||
* <li><b>Time-bound verification</b> — managed JSON bindings may need
|
||||
* a new check during the same loop after a goal definition changes.</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static final Set<String> AUTO_RECORD_SKIP = Set.of(
|
||||
@ -82,7 +84,9 @@ public class ActionNode implements NodeAction {
|
||||
"extract_document_text", "extract_pdf_text", "extract_docx_text",
|
||||
"detect_file_type",
|
||||
"getCurrentDateTime", "getCurrentDate", "getCurrentTime",
|
||||
"listSubagents", "getManagedGoalJsonSlots"
|
||||
"listSubagents", "getManagedGoalJsonSlots",
|
||||
// A binding is time-bound and can need refreshing during this same tool loop.
|
||||
"checkManagedGoalJson"
|
||||
);
|
||||
|
||||
private final ToolExecutionExecutor executor;
|
||||
|
||||
@ -149,6 +149,8 @@ class GoalJsonGraphIntegrationTest {
|
||||
}
|
||||
assertFalse(progress.load(conversation).asMap().containsKey("auto_getManagedGoalJsonSlots"),
|
||||
"Current acceptance reads must not become a permanent done step that discourages reloading");
|
||||
assertFalse(progress.load(conversation).asMap().containsKey("auto_checkManagedGoalJson"),
|
||||
"A time-bound JSON binding must not become a done step that discourages rechecking in this tool loop");
|
||||
assertTrue(calls.get() >= (automatic ? 5 : 6) && calls.get() <= 10, "Bounded scripted model calls: " + calls.get());
|
||||
}
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
@ -67,10 +67,11 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
@org.junit.jupiter.params.provider.CsvSource({"false,sync,true", "true,sync,true", "false,stream,true", "true,stream,true",
|
||||
"false,scheduled,true", "true,scheduled,true", "false,recovered,true", "true,recovered,true",
|
||||
"false,scheduled,false", "true,scheduled,false", "false,recovered,false", "true,recovered,false",
|
||||
"false,queued,true", "false,reuse,true", "true,reuse,true"})
|
||||
"false,queued,true", "false,reuse,true", "true,reuse,true", "false,recheck,true", "true,recheck,true"})
|
||||
void authenticatedGoalCompletesThroughHttpOrScheduledProductionRuntime(boolean plan, String entry, boolean accepted) throws Exception {
|
||||
boolean scheduled = entry.equals("scheduled") || entry.equals("recovered");
|
||||
boolean reuse = entry.equals("reuse");
|
||||
boolean recheck = entry.equals("recheck");
|
||||
boolean queued = entry.equals("queued");
|
||||
boolean recovered = entry.equals("recovered");
|
||||
String username = "http-json-" + UUID.randomUUID();
|
||||
@ -138,6 +139,7 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
ChatModel model = mock(ChatModel.class);
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
java.util.concurrent.atomic.AtomicReference<String> revision = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
java.util.concurrent.atomic.AtomicReference<String> originalCheck = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
org.mockito.stubbing.Answer<ChatResponse> script = invocation -> {
|
||||
Prompt prompt = invocation.getArgument(0);
|
||||
int step = calls.getAndIncrement();
|
||||
@ -161,6 +163,23 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
.flatMap(m -> m.getResponses().stream()).toList();
|
||||
JsonNode last = responses.isEmpty() ? null : json.readTree(responses.getLast().responseData());
|
||||
String name; String arguments = "{}";
|
||||
if (recheck && step >= 5 && step <= 7) {
|
||||
if (step == 5) {
|
||||
assertTrue(last.path("error").asBoolean(), String.valueOf(last));
|
||||
name = "getManagedGoalJsonSlots";
|
||||
} else if (step == 6) {
|
||||
assertEquals("GOAL_CHANGED", last.path("checks").get(0).path("status").asText(), String.valueOf(last));
|
||||
assertEquals(1, last.path("versionCount").asInt());
|
||||
name = "checkManagedGoalJson";
|
||||
arguments = originalCheck.get();
|
||||
assertNotNull(arguments);
|
||||
} else {
|
||||
assertTrue(last.path("acceptanceEligible").asBoolean(), String.valueOf(last));
|
||||
name = "completeGoal";
|
||||
}
|
||||
return new ChatResponse(List.of(new Generation(AssistantMessage.builder().content("")
|
||||
.toolCalls(List.of(new AssistantMessage.ToolCall("recheck-" + step, "function", name, arguments))).build())));
|
||||
}
|
||||
switch (step) {
|
||||
case 0 -> name = "completeGoal";
|
||||
case 1 -> { assertTrue(last.path("error").asBoolean(), String.valueOf(last)); name = "getManagedGoalJsonSlots"; }
|
||||
@ -187,10 +206,16 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
name = "checkManagedGoalJson";
|
||||
arguments = json.writeValueAsString(Map.of("criterionKey", "r", "expectedRequirementRevision", revision.get(),
|
||||
"artifactId", last.path("artifactId").asText(), "expectedGeneration", last.path("generation").asText()));
|
||||
originalCheck.set(arguments);
|
||||
}
|
||||
}
|
||||
case 4 -> {
|
||||
assertTrue((reuse ? last.path("checks").get(0) : last).path("acceptanceEligible").asBoolean(), String.valueOf(last));
|
||||
if (recheck) {
|
||||
// Simulate a user definition edit between the first check and completion.
|
||||
var edit = new GoalUpdateRequest(); edit.setDescription("Revised report context");
|
||||
goals.update(goal.getId(), edit, username);
|
||||
}
|
||||
name = "completeGoal";
|
||||
}
|
||||
default -> {
|
||||
@ -264,9 +289,11 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
}
|
||||
assertEquals(accepted ? GoalStatus.COMPLETED : GoalStatus.ACTIVE, goals.getById(goal.getId()).getStatus());
|
||||
assertEquals(accepted, bindings.state(goal.getId(), username).getFirst().acceptanceEligible());
|
||||
assertTrue(calls.get() >= (accepted ? 6 : 2) && calls.get() <= (accepted ? 10 : 4), "Bounded offline model calls: " + calls.get());
|
||||
assertTrue(calls.get() >= (accepted ? 6 : 2) && calls.get() <= (recheck ? 12 : accepted ? 10 : 4), "Bounded offline model calls: " + calls.get());
|
||||
if (!accepted) assertEquals(recovered ? 1 : 0,
|
||||
jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()));
|
||||
if (recheck) assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()),
|
||||
"A changed goal definition requires a fresh binding, not another publication of unchanged bytes");
|
||||
if (reuse) assertEquals(32, jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()),
|
||||
"Checking and completing a current version must not consume another publication");
|
||||
JsonNode currentRequirements = request("GET", "/api/v1/goals/" + goal.getId() + "/json-acceptance", token, null);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user