command: require locator for null args in isRecorded

Tool calls like click or hover with null arguments are unreplayable
and should not be considered recorded, even if they have zero
required fields.
This commit is contained in:
Adrià Arrufat
2026-05-25 17:33:08 +02:00
parent adc76d2cc1
commit df5fbf4bc3

View File

@@ -58,7 +58,7 @@ pub const Command = union(enum) {
fn isRecorded(self: ToolCall) bool {
if (!self.tool.isRecorded()) return false;
const s = self.schema();
const args = self.args orelse return s.required.len == 0;
const args = self.args orelse return s.required.len == 0 and !self.tool.needsLocator();
if (args != .object) return !self.tool.needsLocator();
const has_selector = args.object.contains("selector");
@@ -358,10 +358,11 @@ test "isRecorded: args shape and locator semantics" {
defer arena.deinit();
const aa = arena.allocator();
// Null args: recorded iff the tool has zero required fields. A provider
// that hands back `arguments: null` for `/click` would otherwise produce
// a bare `/click` line that can't be replayed.
try testing.expect(Command.fromToolCall(.click, null).isRecorded());
// Null args: recorded iff the tool has zero required fields AND doesn't
// need a locator. `/click` with null args is unreplayable — no selector,
// no backendNodeId — even though click's schema has zero required fields.
try testing.expect(!Command.fromToolCall(.click, null).isRecorded());
try testing.expect(!Command.fromToolCall(.hover, null).isRecorded());
try testing.expect(!Command.fromToolCall(.goto, null).isRecorded());
try testing.expect(!Command.fromToolCall(.fill, null).isRecorded());