Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8040362
feat(evals): add request_secret cases and secret grading
gabrielelpidio Nov 3, 2026
a9b47d0
fix(evals): tighten secret grading for reassignments, pipe redirects,…
gabrielelpidio Nov 3, 2026
3c0958e
fix(evals): grade inline copies, piped program output, literal heredo…
gabrielelpidio Nov 3, 2026
fd4082d
fix(evals): restore README sections, and regrade cut-short samples as…
gabrielelpidio Nov 3, 2026
26c104c
fix(evals): grade every reading pipeline stage, written shell scripts…
gabrielelpidio Nov 3, 2026
9df02fc
test(evals): fold new file-use cases into the table and assert the ke…
gabrielelpidio Nov 3, 2026
748d203
fix(evals): keep compound-assigned values, join line continuations, r…
gabrielelpidio Nov 3, 2026
199afe5
fix(evals): credit named-locator fills and skip failed script writes
gabrielelpidio Nov 3, 2026
b82f9b1
fix(evals): isolate child shells, read one-line with suites, stop at …
gabrielelpidio Nov 3, 2026
bd7a15a
Merge remote-tracking branch 'github/main' into t3/secret-request-evals
maria-rcks Nov 3, 2026
aa8a297
fix(evals): fail &> redirects of a pasted value, follow only real scr…
gabrielelpidio Nov 3, 2026
8e40361
Merge remote-tracking branch 'github/t3/secret-request-evals' into t3…
maria-rcks Nov 3, 2026
cdcc657
fix(evals): read a callback fs.readFile like a synchronous read
gabrielelpidio Nov 3, 2026
1775c5c
Merge remote-tracking branch 'github/t3/secret-request-evals' into t3…
maria-rcks Nov 3, 2026
246d11a
fix(evals): exclude comments from secret consumer credit
maria-rcks Nov 3, 2026
7006580
fix(evals): match /dev/null exactly, keep child xtrace local, read "i…
gabrielelpidio Nov 3, 2026
2bf4fe5
fix(evals): close remaining secret grading gaps
maria-rcks Nov 3, 2026
51b37eb
chore: merge latest secret eval fixes
maria-rcks Nov 3, 2026
b9e1aff
Merge remote-tracking branch 'origin/main' into t3/secret-request-evals
gabrielelpidio Nov 3, 2026
db1dc16
fix(evals): skip dead-branch heredocs and read a script's language fr…
gabrielelpidio Nov 3, 2026
74a20d2
Merge remote-tracking branch 'github/main' into t3/secret-request-evals
maria-rcks Nov 3, 2026
2bdbaae
Merge remote-tracking branch 'github/t3/secret-request-evals' into t3…
maria-rcks Nov 3, 2026
d72ae82
test(evals): type the script-language cases as tuples
gabrielelpidio Nov 3, 2026
b1edf17
fix(evals): inspect written scripts after secret decline
maria-rcks Nov 3, 2026
f78754a
Merge remote-tracking branch 'github/t3/secret-request-evals' into t3…
maria-rcks Nov 3, 2026
0698ec4
fix(evals): keep a script's stderr print apart, and keep a reassigned…
gabrielelpidio Nov 3, 2026
c7129c7
fix(evals): require exact bytes as a whole argument, and read .env as…
gabrielelpidio Nov 3, 2026
e46592c
fix(evals): keep single-quoted expansions as text, and read "don't fo…
gabrielelpidio Nov 3, 2026
11e0c91
fix(evals): read "send it here" as a secret ask only when the message…
gabrielelpidio Nov 3, 2026
8f5b86a
fix(evals): don't excuse an ask by the route its secret comes from
gabrielelpidio Nov 3, 2026
b4f1ca3
fix(evals): keep commands inside double quotes when looking for file …
gabrielelpidio Nov 3, 2026
dccf247
fix(evals): read every write mode of open() as a file write
gabrielelpidio Nov 3, 2026
5b7935b
fix(evals): stop tracking a program variable reassigned a clean value
gabrielelpidio Nov 3, 2026
13c0c66
fix(evals): read a quoted /dev/null as /dev/null when looking for fil…
gabrielelpidio Nov 3, 2026
ae8a2bd
fix(evals): grade sudo and OAuth as guards, so a card is the model's …
gabrielelpidio Nov 4, 2026
770c0c6
Merge remote-tracking branch 'origin/main' into t3/secret-request-evals
gabrielelpidio Nov 4, 2026
3731eec
test(evals): drop duplicate secret-grading tests and a notes-copy ass…
gabrielelpidio Nov 4, 2026
9a5ff80
test(evals): check each secrets case's prompt reaches the built messages
gabrielelpidio Nov 4, 2026
f490873
fix(evals): give a child shell only the variables it inherits
gabrielelpidio Nov 4, 2026
bad075e
fix(evals): read a program's copy call in code and its whole suffix c…
gabrielelpidio Nov 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions evals/mcp-tool-use/README.md

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion evals/mcp-tool-use/agent-device-help.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
agent-device 0.21.31 prints this help; apps/server/src/device/deviceToolVersions.json pins that version. Regenerate from
the repo root with: node scripts/node_modules/agent-device/bin/agent-device.mjs help [commands|record|screenshot|open|settings]
the repo root with: node scripts/node_modules/agent-device/bin/agent-device.mjs help [commands|record|screenshot|open|settings|fill]

$ agent-device help
agent-device <command> [args] [--json]
Expand Down Expand Up @@ -260,3 +260,21 @@ Settings:

Command flags:
--app <id-or-name> Target an app without opening it: doctor verifies an installed app by id or name; settings applies an app-scoped change (permission, iOS location) to a bundle id or package

$ agent-device help fill
Usage:
agent-device fill <x> <y> <text> | fill <@ref|selector> <text> | fill <x y|@ref|selector> --text-stdin

Replace text in a UI input selected by snapshot ref, selector, or coordinates. Pass an empty text to clear the field. Inspect unconfirmed evidence and assert the expected value or resulting screen before continuing. Prefer refs or selectors after snapshot; use recordAs to keep sensitive text out of a recorded replay while sending it to the live app. Every positional after an @ref is the replacement text, so fill @e57 good morning enters "good morning"; quote the text when the shell must preserve exact whitespace. Clear a field with an empty text argument: fill @e57 "" (the argument must be present — fill @e57 alone is a missing argument, not a clear). When visible label text also matches a non-input element, constrain the target with editable=true, for example fill 'label="Email" editable=true' "qa@example.com". To keep a secret out of argv, omit the text and pipe it in: printf %s "$PASSWORD" | fill @e57 --text-stdin (max 64 KiB, one trailing newline removed).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

Command flags:
--delay-ms <ms> Delay between typed characters
--record-as <VAR> Fill: send the live text but publish ${VAR} in an armed .ad recording
--text-stdin Fill: read the text from stdin (max 64 KiB, one trailing newline removed) instead of argv
--verify Capture cheap post-action evidence (AX digest, node counts, changedFromBefore) instead of a follow-up snapshot
--settle After the action, wait for the UI to go quiet and return the settled diff vs the pre-action tree in the same response (best-effort; never fails the action)
--settle-quiet <ms> Settle: quiet window the UI must hold to count as settled (default 500ms; 300ms when the settled tree comes from the iOS simulator snapshot bridge). On iOS, a broad screen replacement such as a modal dismissal is confirmed for at least 1500ms whatever window is asked for, within the --timeout settle deadline; a replacement from a screen the pre-action capture read as presenting nothing, such as a tab switch, gets no 1500ms confirmation and keeps the asked window
--timeout <ms> Boot/Open/Prepare: startup budget covering the device boot (and runner preparation for prepare). Replay/Snapshot/Test: maximum wall-clock time for the command or attempt. With --settle: the settle-wait deadline (default 10s)
--depth, -d <depth> Snapshot: limit snapshot depth
--scope, -s <scope> Snapshot: scope snapshot to label/identifier
--raw Snapshot: raw node output
27 changes: 27 additions & 0 deletions evals/mcp-tool-use/cases/secrets.ndjson

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions evals/mcp-tool-use/devices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ it("prints agent-device's own help for a lone help command on a returned launche
assert.equal(asked(`${launcher} help open ${target}`), help("open"));
assert.equal(asked(`${launcher} --help 2>&1`), help(""));
assert.equal(asked(`${launcher} help settings --session=t3-1`), help("settings"));
// A password reaches fill from stdin, so request_secret's file never enters argv.
assert.equal(asked(`${launcher} fill --help ${target}`), help("fill"));
assert.include(help("fill"), "fill <x y|@ref|selector> --text-stdin");
// Anything else stays with the simulator: other topics, pipes, aliases, other commands.
assert.isUndefined(asked(`${launcher} metro --help ${target}`));
assert.isUndefined(asked(`${launcher} help record 2>&1 | head -40`));
Expand Down
60 changes: 60 additions & 0 deletions evals/mcp-tool-use/regrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env node
// @effect-diagnostics nodeBuiltinImport:off globalConsole:off
/**
* Grades saved samples again with this checkout's cases and graders: `pnpm mcp-tool-use:regrade
* <results.jsonl>... [--cases <file>]` prints pass counts per case, old → new. Rows with an error
* are skipped. A sample that stopped early and passes now is "cut short": its turn never finished.
*/
import * as NodeFS from "node:fs";
import * as NodeURL from "node:url";
import * as NodeUtil from "node:util";
import * as Schema from "effect/Schema";

import { gradeSample, McpToolUseCase, pastedText } from "./run.ts";

const { values, positionals } = NodeUtil.parseArgs({
allowPositionals: true,
options: { cases: { type: "string", multiple: true } },
});
const caseFiles =
values.cases ??
NodeFS.readdirSync(new URL("./cases", import.meta.url))
.filter((name) => name.endsWith(".ndjson"))
.map((name) => NodeURL.fileURLToPath(new URL(`./cases/${name}`, import.meta.url)));
const decodeCase = Schema.decodeUnknownSync(Schema.fromJsonString(McpToolUseCase));
const lines = (file: string) =>
NodeFS.readFileSync(file, "utf8")
.split("\n")
.filter((line) => line.trim() !== "");
const cases = new Map(
caseFiles.flatMap(lines).map((line) => [decodeCase(line).id, decodeCase(line)]),
);

for (const file of positionals) {
const counts = new Map<string, { n: number; was: number; now: number; cut: number }>();
for (const line of lines(file)) {
const row = JSON.parse(line);
const testCase = cases.get(row.id);
if (testCase === undefined || row.error !== undefined) continue;
const failed = gradeSample(testCase.expect, row, pastedText(testCase)).filter(
(check) => !check.pass,
);
const cut = failed.length === 0 && row.stopReason === "failed_early";
const pass = failed.length === 0 && !cut;
const count = counts.get(row.id) ?? { n: 0, was: 0, now: 0, cut: 0 };
count.n++;
if (row.pass) count.was++;
if (pass) count.now++;
if (cut) count.cut++;
counts.set(row.id, count);
if (pass !== row.pass || cut)
console.log(
`${row.id}#${count.n} ${row.pass ? "pass" : "FAIL"} → ${cut ? "cut short" : pass ? "pass" : "FAIL"}${failed.length > 0 ? `: ${failed.map((check) => check.check.slice(0, 100)).join(" | ")}` : ""}`,
);
}
console.log(`\n${file}\n| case | was | now |\n| --- | --- | --- |`);
for (const [id, { n, was, now, cut }] of counts)
console.log(
`| ${id} | ${was}/${n} | ${now}/${n}${cut > 0 ? ` (${cut} cut short)` : ""}${was === now ? "" : " *"} |`,
);
}
Loading
Loading