fix(agent): harden runner delivery validation

This commit is contained in:
huanghuoguoguo
2026-07-27 16:01:57 +08:00
parent 7d3b53bbce
commit 9c32ea4063
11 changed files with 403 additions and 47 deletions
+46
View File
@@ -60,6 +60,7 @@ import {
} from "../scripts/e2e/lib/debug-chat.mjs";
import {
beginBackendLogCapture,
clickFirstVisible,
ensureAuthenticatedBrowser,
finishBackendLogCapture,
resolveLangBotRepo,
@@ -68,6 +69,32 @@ import {
const root = process.cwd();
test("clickFirstVisible waits for a later visible DOM match", async () => {
let pollCount = 0;
let clickedIndex = -1;
const emptyLocator = {
count: async () => 0,
nth: () => { throw new Error("empty locator has no children"); },
};
const textLocator = {
count: async () => 2,
nth: (index: number) => ({
isVisible: async () => index === 1 && pollCount >= 1,
click: async () => { clickedIndex = index; },
}),
};
const page = {
getByRole: () => emptyLocator,
getByText: () => textLocator,
waitForTimeout: async () => { pollCount += 1; },
};
const clicked = await clickFirstVisible(page, ["Debug Chat"], 1_000);
assert.equal(clicked, "Debug Chat");
assert.equal(clickedIndex, 1);
});
test("repo root detects the skills tree before generated bin exists", () => {
const tmp = mkdtempSync(join(tmpdir(), "lbs-root-no-bin-"));
try {
@@ -2064,6 +2091,25 @@ test("debug chat classifier passes when expected text appears in a new assistant
assert.equal(result.new_assistant_message_count, 1);
});
test("debug chat classifier rejects a matching assistant message that is not final", () => {
const result = classifyDebugChatResult({
beforeText: "",
afterText: "Bot: E2E_OK:skill",
expectedText: "E2E_OK:skill",
prompt: "Run the task",
latestExpectedLeaf: "E2E_OK:skill",
latestFailureLeaf: "",
beforeMessages: [],
afterMessages: [{ role: "assistant", text: "E2E_OK:skill" }],
latestAssistantText: "E2E_OK:skill",
latestAssistantIsFinal: false,
});
assert.equal(result.status, "fail");
assert.match(result.reason, /was not final/);
assert.equal(result.latest_assistant_is_final, false);
});
test("debug chat classifier accepts formatted responses containing every required fragment", () => {
const expectedTexts = ["MULTITOOL_COMBO_FINAL", "passcode-6718", "rag-7421", "tool-a", "tool-b"];
const result = classifyDebugChatResult({
@@ -9,6 +9,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts" / "e2e"))
from agent_run_ledger_policy import ( # noqa: E402
classify_invalid_tool_argument_errors,
classify_tool_authorization,
invalid_tool_argument_error_signal,
load_ledger_json,
)
@@ -64,6 +66,34 @@ class AgentRunLedgerPolicyTests(unittest.TestCase):
self.assertEqual(failures[0]["kind"], "invalid_json")
self.assertEqual(failures[0]["field"], "agent_run_event[10].data_json")
def test_detects_pydantic_tool_argument_validation_error(self) -> None:
signal = invalid_tool_argument_error_signal(
"1 validation error for RetrieveKnowledgeArgs\n"
"kb_id\n Field required [type=missing, input_value={'top_k': 3}, input_type=dict]"
)
self.assertEqual(signal, "1 validation error for RetrieveKnowledgeArgs")
def test_ignores_unrelated_tool_execution_error(self) -> None:
self.assertEqual(invalid_tool_argument_error_signal("Exit code 1"), "")
def test_strict_mode_rejects_tool_absent_from_host_authorization(self) -> None:
call = {"tool_call_id": "call-1", "tool_name": "Read"}
failures, warnings = classify_tool_authorization([call], authorization_mode="strict")
self.assertEqual(failures, [{"kind": "unauthorized_tool_calls", "calls": [call]}])
self.assertEqual(warnings, [])
def test_runner_native_mode_records_non_host_tool_as_warning(self) -> None:
call = {"tool_call_id": "call-1", "tool_name": "Read"}
failures, warnings = classify_tool_authorization([call], authorization_mode="runner-native")
self.assertEqual(failures, [])
self.assertEqual(warnings[0]["kind"], "runner_native_tool_calls")
self.assertEqual(warnings[0]["calls"], [call])
if __name__ == "__main__":
unittest.main()