Files
callstack__agent-device/scripts/help-conformance-sample-outputs.mjs
T
Michał Pierzchała 2d1d70613f feat(bench): renderer-pinned samples, topic-coverage gate, error-recovery quizzes; trim skillgym to agentic checks (#1411)
* feat(bench): renderer-pinned samples, topic-coverage gate, error quizzes; trim skillgym to agentic checks

The help conformance bench's quoted CLI output is now sourced from
scripts/help-conformance-sample-outputs.mjs, and every sample is rebuilt
through the real production renderers (settle output formatters,
printHumanError, formatSnapshotText, refMutationAdmissionResponse) by
scripts/__tests__/help-conformance-sample-outputs.test.ts — a rendering or
message change fails deterministically instead of leaving the bench grading
against output the CLI no longer prints. This retires the fabricated
recoverable-failure envelope (production never throws a textual settle
timeout; that case is replaced by a real DEVICE_IN_USE recovery quiz).

Bench cases move to scripts/help-conformance-cases.mjs and are enumerated
against the help-topic registry: helpTopicIds() is exported from cli-help,
and scripts/__tests__/help-conformance-topic-coverage.test.ts fails when a
help topic has neither a bench case nor an explicit waiver. New case
families: error-envelope recovery quizzes (device-in-use, stale pinned ref,
ambiguous find match, app-not-installed) pinned to real error text, topic
coverage for tv/web/react-native/debugging/workflow, and a metamorphic twin
of the settled-diff quiz.

The skillgym smoke suite shrinks from 119 cases to the 5 that measure what
only an agentic runner can show: skill routing plus output interpretation
with a proven local CLI help probe (local-cli-help-policy). Its embedded
samples now import the same pinned constants, replacing hand-transcribed
output that had already drifted from the renderer. Knowledge checks belong
to the bench; live fixture behavior belongs to the iOS simulator e2e suite.

* review: drive error samples through the real producers; enforce local-help on the routing smoke

The DEVICE_IN_USE, AMBIGUOUS_MATCH, and APP_NOT_INSTALLED parity tests no
longer hand-author the producer message before rendering: each drives the
actual producer — buildDeviceInUseBySessionError (extracted in
session-open.ts and called by the handler), buildAmbiguousMatchError (now
exported from find.ts), and buildAppNotInstalledError (extracted in
app-resolution.ts and thrown by the resolver). Because each factory is
exported from its producer file and called by the production path, dropping
the production call would make it test-only and fail
check:production-exports — the wiring is gate-enforced, not conventional.

open-and-snapshot now sets requireLocalCliHelp and
allowOnlyLocalCliHelpCommands, so the 'skill plus local help' claim is
observed rather than assumed; without them the case can pass on model prior
alone.
2026-07-27 14:24:36 +02:00

106 lines
5.1 KiB
JavaScript

// Captured agent-device output embedded in benchmark quiz cases. Each sample
// is { command, output }: the command line the prompt claims was run, and the
// exact text the CLI would print for it. Every `output` is pinned to the real
// renderer by scripts/__tests__/help-conformance-sample-outputs.test.ts, which
// rebuilds it through src/commands/interaction/output.ts or
// src/utils/output.ts printHumanError — a rendering change fails that test
// instead of silently leaving the benchmark grading against stale output.
export function sampleText(sample) {
return `${sample.command}\n${sample.output}`;
}
// Settled press whose diff carried no added ref, so the response appended the
// unchanged-interactive tail (#1167/#1172). The response carried a diff plus
// refsGeneration, so the tail refs render pinned (ADR 0014).
export const SETTLE_TAIL_SAMPLE = {
command: 'agent-device press @e37 --settle',
output: `Tapped @e37 (203, 88)
settled after 540ms: +0 -1 (~15 unchanged)
- @e50 [text] "Suggested for you"
unchanged interactive (4):
= @e64~s5 [text-field] "Search"
= @e65~s5 [text] "Recent searches"
= @e12~s5 [tab] "Home"
= @e40~s5 [tab] "Profile"`,
};
// Settled fill whose diff exposes the next target directly.
export const SETTLE_DIFF_SAMPLE = {
command: `agent-device fill 'id="account-search"' "callstack" --settle`,
output: `Filled 9 chars
settled after 610ms: +2 -0 (~18 unchanged)
+ @e64 [button] "@callstack.com"
+ @e65 [text] "Callstack"`,
};
// Metamorphic twin of SETTLE_DIFF_SAMPLE on a different app and ref range, so
// a pass cannot come from memorizing the callstack sample.
export const SETTLE_DIFF_SAMPLE_NOTES = {
command: `agent-device fill 'id="notes-search"' "groceries" --settle`,
output: `Filled 9 chars
settled after 480ms: +2 -0 (~11 unchanged)
+ @e21 [button] "Groceries list"
+ @e22 [text] "3 items"`,
};
// Never-settled press: success response, no diff, NEVER_SETTLED_HINT attached.
export const NOT_SETTLED_SAMPLE = {
command: 'agent-device press @e12 --settle',
output: `Tapped @e12 (166, 240)
not settled after 10000ms
hint: The UI kept changing for the whole settle budget (animation, carousel, or ticker?), so no settled diff is shown. Raise --timeout, wait for specific content, or take a fresh snapshot.`,
};
// Recovered snapshot: the private-ax fallback fired but still exposed
// actionable refs. Warning wording is renderSnapshotQualityWarnings
// (src/snapshot/snapshot-quality.ts); lines are the structured snapshot
// renderer (src/utils/output.ts formatSnapshotText).
export const PRIVATE_AX_RECOVERY_SAMPLE = {
command: 'agent-device snapshot -i',
output: `Snapshot: 2 nodes
Detected an overly complex or slow accessibility tree. Fell back to the private-ax snapshot backend. It is OK to continue; use --json to inspect snapshotQuality.reason if you need recovery details.
@e5 [button] "Search"
@e8 [tab] "Home" [selected]`,
};
// DEVICE_IN_USE from buildDeviceInUseBySessionError
// (src/daemon/handlers/session-open.ts) — the parity test drives that exact
// producer.
export const DEVICE_IN_USE_SAMPLE = {
command: `agent-device press 'label="Place order"' --settle`,
output: `Error (DEVICE_IN_USE): Device is already in use by session "checkout".
Hint: Run agent-device session list to inspect active sessions. To reuse this device, rerun the command with --session checkout. To open a new session on this device, first run agent-device close --session checkout.`,
};
// ADR 0014 mutation rejection from
// src/daemon/handlers/interaction-ref-policy.ts: a pinned ref minted from a
// superseded generation is rejected before dispatch. The daemon strips the
// `~s5` pin at the boundary (interaction-touch-targets.ts), so the message
// names the plain ref; the hint is the precise resolveRefStalenessWarning.
export const STALE_REF_SAMPLE = {
command: 'agent-device press @e12~s5 --settle',
output: `Error (COMMAND_FAILED): Ref @e12 was minted from a superseded snapshot generation
Hint: Ref @e12 was minted from snapshot s5 but the session's ref frame is now s7 — re-run snapshot -i.`,
};
// AMBIGUOUS_MATCH from buildAmbiguousMatchError (src/daemon/handlers/find.ts)
// — the parity test drives that exact producer. The by-design rejection
// instead of silent disambiguation: candidate refs live in details, which the
// human rendering does not print, so the agent must re-observe or narrow, not
// guess a ref it never saw.
export const AMBIGUOUS_MATCH_SAMPLE = {
command: 'agent-device find text "Follow" press',
output: `Error (AMBIGUOUS_MATCH): find matched 3 elements for text "Follow". Use a more specific locator or selector.
Hint: Multiple candidates matched. Narrow the query or pass an exact identifier.`,
};
// APP_NOT_INSTALLED from buildAppNotInstalledError
// (src/platforms/apple/core/app-resolution.ts) — the parity test drives that
// exact producer; the hint is defaultHintForCode('APP_NOT_INSTALLED').
export const APP_NOT_INSTALLED_SAMPLE = {
command: 'agent-device open Shoply --platform ios',
output: `Error (APP_NOT_INSTALLED): No app found matching "Shoply"
Hint: Run apps to discover the exact installed package or bundle id, or install the app before open.`,
};