@bastani/atomic 0.9.56-alpha.1 → 0.9.57-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +24 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/agent-desktop/SKILL.md +83 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +12 -0
- package/dist/builtin/workflows/builtin/adversarial-verification.js +3 -3
- package/dist/builtin/workflows/builtin/{chunk-49kkbdpr.js → chunk-2f1shjah.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-dcxscfnb.js → chunk-37gm3bzk.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-2jfst7e4.js → chunk-45fn5q5p.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-hx8v4p97.js → chunk-4ek3v2wm.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-z3pajrfs.js → chunk-8d40nyz9.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-qhm35ntx.js → chunk-g3b2zf8r.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-6jpwc09k.js → chunk-h6bt0r0g.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-9cm2ckq2.js → chunk-x25rjw4y.js} +4 -4
- package/dist/builtin/workflows/builtin/{chunk-apagcs4r.js → chunk-xqcy5nve.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-w4xp1w1f.js → chunk-yb55w6me.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-k5q3t7je.js → chunk-yjgfnbgd.js} +1 -1
- package/dist/builtin/workflows/builtin/classify-and-act.js +3 -3
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.js +3 -3
- package/dist/builtin/workflows/builtin/generate-and-filter.js +3 -3
- package/dist/builtin/workflows/builtin/goal.js +3 -3
- package/dist/builtin/workflows/builtin/index.js +11 -11
- package/dist/builtin/workflows/builtin/loop-until-done.js +3 -3
- package/dist/builtin/workflows/builtin/open-claude-design.js +3 -3
- package/dist/builtin/workflows/builtin/ralph.js +3 -3
- package/dist/builtin/workflows/builtin/steering-context.js +1 -1
- package/dist/builtin/workflows/builtin/tournament.js +3 -3
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +7 -7
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +4 -4
- package/dist/builtin/workflows/src/index.js +4 -4
- package/dist/builtin/workflows/src/local-worker-entry.mjs +4 -4
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +2 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/docs/computer-use.md +119 -98
- package/docs/getting-started/first-session.md +1 -1
- package/docs/skills.md +2 -2
- package/docs/usage.md +1 -1
- package/docs/workflows/authoring.md +105 -141
- package/docs/workflows/verification.md +14 -13
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
- package/dist/builtin/subagents/skills/cua-driver/BROWSER.md +0 -562
- package/dist/builtin/subagents/skills/cua-driver/EMBEDDING.md +0 -685
- package/dist/builtin/subagents/skills/cua-driver/LINUX.md +0 -412
- package/dist/builtin/subagents/skills/cua-driver/MACOS.md +0 -996
- package/dist/builtin/subagents/skills/cua-driver/README.md +0 -123
- package/dist/builtin/subagents/skills/cua-driver/RECORDING.md +0 -132
- package/dist/builtin/subagents/skills/cua-driver/RUNTIME.md +0 -115
- package/dist/builtin/subagents/skills/cua-driver/SKILL.md +0 -108
- package/dist/builtin/subagents/skills/cua-driver/VISUAL.md +0 -125
- package/dist/builtin/subagents/skills/cua-driver/WINDOWS.md +0 -832
- package/dist/builtin/subagents/skills/cua-driver/WORKFLOW.md +0 -226
|
@@ -519,169 +519,96 @@ After exit is selected, new tracked work and retained stage operations are refus
|
|
|
519
519
|
|
|
520
520
|
On resume of an unfinished tool, a successful exit cannot skip that tool. Restore its matching call before new tracked work; otherwise Atomic reports `insufficient_state: replay topology mismatch`. Intentional failed, blocked, cancelled, or skipped exits remain available.
|
|
521
521
|
|
|
522
|
-
<a id="desktop-verification-with-
|
|
522
|
+
<a id="desktop-verification-with-agent-desktop-in-ctx-tool" />
|
|
523
523
|
|
|
524
|
-
### Desktop verification with
|
|
524
|
+
### Desktop verification with agent-desktop in `ctx.tool`
|
|
525
525
|
|
|
526
|
-
When a model chooses each desktop action, an authored model stage loads the bundled `
|
|
526
|
+
When a model chooses each desktop action, an authored model stage loads the bundled `agent-desktop` skill and runs one-shot `agent-desktop` commands; see [Computer use](/computer-use#desktop-automation-with-agent-desktop) for the rule, installation, and readiness. When your workflow's TypeScript owns the sequence and the postcondition, call the `agent-desktop` CLI from inside `ctx.tool(name, args, fn, { timeoutMs })` so the observe → act → verify loop is a durable, replayable node rather than a transcript. agent-desktop has no TypeScript SDK: every command prints one JSON envelope (`ok`, then `data` or `error`), and that envelope is the contract.
|
|
527
527
|
|
|
528
|
-
|
|
528
|
+
The only prerequisite is the CLI on the host: `npm install -g agent-desktop` (Node 18 or later), approving its install script if npm blocks it. Do not install it from inside the run. The readiness preflight below reports a missing install as a limitation instead. The executable carries its own guides (`agent-desktop skills get desktop`), and Atomic bundles the skill, so skip `npx skills add` and leave existing user-level skills alone.
|
|
529
529
|
|
|
530
|
-
|
|
531
|
-
- The `cua-driver` executable (`cua-driver --version`), installed with upstream's one-line installer if missing, followed by `cua-driver telemetry disable` once.
|
|
532
|
-
- The SDK installed at the exact driver version into a scratch directory outside the user's repository, for example `npm install @trycua/cua-driver@0.36.0 --prefix ~/.cache/atomic-cua`. The package ships per-platform native optional dependencies. Daemon-backed clients verify contract, tool-schema, capability, and protocol versions before each action and refuse on mismatch, so the pin must match `cua-driver --version`.
|
|
530
|
+
The workflow below checks readiness in its own `ctx.tool` first, so a missing install, a missing macOS permission, or an unreachable Linux accessibility bus ends the run with `ctx.exit({ status: "blocked", reason })`. A blocked author exit is terminal and not resumable (see [Early exit with `ctx.exit()`](#early-exit-with-ctx-exit)), and the preflight's `{ ready: false }` result is a completed, cached checkpoint, so after the user fixes the problem start a new run: its fresh preflight checks again instead of replaying the cached result. The scenario tool spawns one `agent-desktop` process per command without a shell (on Windows it starts the package's native executable, not npm's `.cmd` shim, so no argument is parsed by `cmd.exe`), saves each result's JSON and both screenshots as evidence, forwards `signal` so a quit or targeted abort kills the child, and throws anything but `verified`, so a failed scenario fails the node.
|
|
533
531
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
The scenario itself is a small script in that scratch directory. It acquires the driver with `CuaDriver.connect(undefined)` when a daemon is running, so it reuses the daemon's permission identity (on macOS, `CuaDriver.app`'s Accessibility and Screen Recording grants) and the persisted telemetry-off preference; upstream describes `connect()` as a compatibility and app-hosting path, which is exactly the role it plays here. Only when no daemon is reachable does it fall back to `CuaDriver.create(undefined)`, which loads the runtime into the node process. On macOS that in-process fallback attributes permissions to the node host, normally the terminal app, as a **separate grant** from the app's; `checkPermissions` is then read-only, and the host must fully quit and relaunch after granting. Save it as `~/.cache/atomic-cua/verify-counter.mjs`:
|
|
537
|
-
|
|
538
|
-
```js
|
|
539
|
-
// Reads {"daemon":true|false,"artifactsDir":"..."} from argv[2]; prints one JSON result line.
|
|
540
|
-
// Replace the app name, window title, element labels, and postcondition with ones you control.
|
|
532
|
+
```ts
|
|
533
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
541
534
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
542
535
|
import { join } from "node:path";
|
|
543
|
-
import {
|
|
544
|
-
|
|
545
|
-
GetWindowStateInput, InputDeliveryMode, ListAppsInput, ListWindowsInput,
|
|
546
|
-
} from "@trycua/cua-driver";
|
|
547
|
-
|
|
548
|
-
const { daemon, artifactsDir } = JSON.parse(process.argv[2]);
|
|
549
|
-
mkdirSync(artifactsDir, { recursive: true });
|
|
550
|
-
const unique = (items, what) => {
|
|
551
|
-
if (items.length !== 1) throw new Error(`Expected one ${what}, found ${items.length}`);
|
|
552
|
-
return items[0];
|
|
553
|
-
};
|
|
554
|
-
const finish = (result, detail) => {
|
|
555
|
-
process.stdout.write(`${JSON.stringify({ result, detail })}\n`);
|
|
556
|
-
process.exitCode = result === "verified" ? 0 : 1;
|
|
557
|
-
};
|
|
536
|
+
import { workflow } from "@bastani/atomic/workflows";
|
|
537
|
+
import { Type } from "typebox";
|
|
558
538
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const target = new ActionTarget.Window({ pid: app.pid, windowId: window.windowId });
|
|
568
|
-
const snapshot = async (label) => {
|
|
569
|
-
const state = await driver.getWindowState(
|
|
570
|
-
GetWindowStateInput.new({
|
|
571
|
-
pid: app.pid, windowId: window.windowId,
|
|
572
|
-
includeAccessibilityTree: true, includeScreenshot: true,
|
|
573
|
-
screenshotOutFile: join(artifactsDir, `${label}.png`),
|
|
574
|
-
}),
|
|
575
|
-
);
|
|
576
|
-
if (state.degraded || state.truncated) throw new Error(`${label} snapshot is degraded or truncated`);
|
|
577
|
-
// Keep the whole result (snapshot id, screenshot reference, degraded/truncated flags), not just
|
|
578
|
-
// the elements, so the retained tree is provably from a complete snapshot. Ids are bigint.
|
|
579
|
-
const json = JSON.stringify(state, (_key, value) => (typeof value === "bigint" ? value.toString() : value), 2);
|
|
580
|
-
writeFileSync(join(artifactsDir, `${label}.json`), json);
|
|
581
|
-
return state;
|
|
539
|
+
type Envelope = {
|
|
540
|
+
ok: boolean;
|
|
541
|
+
data?: {
|
|
542
|
+
permissions?: Record<string, { state?: string }>;
|
|
543
|
+
supported_surfaces?: string[];
|
|
544
|
+
matches?: { ref_id?: string }[];
|
|
545
|
+
value?: string | null;
|
|
546
|
+
found?: boolean;
|
|
582
547
|
};
|
|
548
|
+
error?: { code: string; message: string; disposition?: { delivery: string; retry: string } };
|
|
549
|
+
};
|
|
583
550
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
await driver.click(ClickInput.new({
|
|
592
|
-
target,
|
|
593
|
-
position: new ClickPosition.Element({ elementToken: button.elementToken }),
|
|
594
|
-
deliveryMode: InputDeliveryMode.Background,
|
|
595
|
-
button: ClickButton.Left, count: 1,
|
|
596
|
-
}));
|
|
597
|
-
clicked = true;
|
|
598
|
-
} catch (error) {
|
|
599
|
-
// A timeout or interrupted action before its response is "unknown": the postcondition below
|
|
600
|
-
// decides, and the click is never replayed.
|
|
601
|
-
if (!/timed out|ActionInterrupted/u.test(String(error))) throw error;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Bounded poll on fresh snapshots; never a fixed sleep, never a second click.
|
|
605
|
-
const deadline = Date.now() + 10_000;
|
|
606
|
-
let after = await snapshot("after");
|
|
607
|
-
let updated = unique((after.elements ?? []).filter((e) => e.label === "Count"), "counter");
|
|
608
|
-
while (updated.value === counter.value && Date.now() < deadline) {
|
|
609
|
-
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
610
|
-
after = await snapshot("after");
|
|
611
|
-
updated = unique((after.elements ?? []).filter((e) => e.label === "Count"), "counter");
|
|
612
|
-
}
|
|
613
|
-
if (updated.value !== undefined && updated.value !== counter.value) {
|
|
614
|
-
finish("verified", `Count ${counter.value} -> ${updated.value}`);
|
|
615
|
-
} else {
|
|
616
|
-
finish(clicked ? "refuted" : "unknown", `Count stayed ${counter.value}`);
|
|
617
|
-
}
|
|
618
|
-
} catch (error) {
|
|
619
|
-
finish("unknown", String(error));
|
|
620
|
-
} finally {
|
|
621
|
-
try {
|
|
622
|
-
await driver.shutdown();
|
|
623
|
-
} finally {
|
|
624
|
-
if ("uniffiDestroy" in driver && typeof driver.uniffiDestroy === "function") driver.uniffiDestroy();
|
|
625
|
-
}
|
|
551
|
+
// On Windows npm's `agent-desktop` command is a .cmd shim that only runs through cmd.exe, which
|
|
552
|
+
// would split an argument at a space and run whatever follows an `&`. Start the native executable
|
|
553
|
+
// the package ships instead, so Node passes every argument literally.
|
|
554
|
+
function agentDesktopCommand(): string {
|
|
555
|
+
if (process.platform !== "win32") return "agent-desktop";
|
|
556
|
+
const root = execFileSync("npm", ["root", "-g"], { encoding: "utf8", shell: true }).trim();
|
|
557
|
+
return join(root, "agent-desktop", "bin", `agent-desktop-win32-${process.arch}.exe`);
|
|
626
558
|
}
|
|
627
|
-
```
|
|
628
559
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
import { join } from "node:path";
|
|
635
|
-
import { workflow } from "@bastani/atomic/workflows";
|
|
636
|
-
import { Type } from "typebox";
|
|
637
|
-
|
|
638
|
-
const sdkDir = join(homedir(), ".cache", "atomic-cua");
|
|
639
|
-
const cuaEnv = { ...process.env, CUA_DRIVER_RS_TELEMETRY_ENABLED: "false" };
|
|
640
|
-
|
|
641
|
-
function run(command: string, args: string[], signal: AbortSignal, cwd?: string) {
|
|
642
|
-
return new Promise<{ code: number | null; stdout: string; stderr: string }>((resolve) => {
|
|
643
|
-
const child = spawn(command, args, { cwd, env: cuaEnv, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
560
|
+
// One process per command, never through a shell. Every command prints one JSON envelope; branch
|
|
561
|
+
// on `ok`, `error.code` and `disposition`, never on message text.
|
|
562
|
+
function agentDesktop(args: string[], signal: AbortSignal) {
|
|
563
|
+
return new Promise<Envelope>((resolve) => {
|
|
564
|
+
const child = spawn(agentDesktopCommand(), args, { signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
644
565
|
let stdout = "";
|
|
645
566
|
let stderr = "";
|
|
646
567
|
child.stdout.on("data", (chunk: Buffer) => { stdout += chunk.toString(); });
|
|
647
568
|
child.stderr.on("data", (chunk: Buffer) => { stderr += chunk.toString(); });
|
|
648
|
-
// A missing executable (ENOENT) or an abort arrives here rather than on "close";
|
|
569
|
+
// A missing executable (ENOENT) or an abort arrives here rather than on "close"; report it as a
|
|
649
570
|
// failed command so the preflight can return its remediation instead of throwing.
|
|
650
|
-
child.on("error", (error) =>
|
|
651
|
-
|
|
571
|
+
child.on("error", (error) =>
|
|
572
|
+
resolve({ ok: false, error: { code: "SPAWN_FAILED", message: error.message } }));
|
|
573
|
+
child.on("close", () => {
|
|
574
|
+
try {
|
|
575
|
+
resolve(JSON.parse(stdout) as Envelope);
|
|
576
|
+
} catch {
|
|
577
|
+
resolve({ ok: false, error: { code: "NO_ENVELOPE", message: stderr.trim() || stdout.trim() } });
|
|
578
|
+
}
|
|
579
|
+
});
|
|
652
580
|
});
|
|
653
581
|
}
|
|
654
582
|
|
|
655
583
|
export default workflow({
|
|
656
584
|
name: "verify-counter",
|
|
657
585
|
description: "Prove the Increment button changes the Count field in the Counter window.",
|
|
658
|
-
inputs: { artifactsDir: Type.String({ description: "Directory for before/after
|
|
586
|
+
inputs: { artifactsDir: Type.String({ description: "Directory for before/after screenshots" }) },
|
|
659
587
|
outputs: { result: Type.String(), detail: Type.String() },
|
|
660
588
|
run: async (ctx) => {
|
|
661
589
|
const preflight = await ctx.tool(
|
|
662
|
-
"
|
|
590
|
+
"agent-desktop-preflight",
|
|
663
591
|
{ platform: process.platform },
|
|
664
592
|
async ({ signal }) => {
|
|
665
|
-
const version = await
|
|
666
|
-
if (version.
|
|
667
|
-
return { ready: false, reason: "
|
|
593
|
+
const version = await agentDesktop(["version"], signal);
|
|
594
|
+
if (!version.ok) {
|
|
595
|
+
return { ready: false, reason: "agent-desktop is not installed or not on PATH; run `npm install -g agent-desktop` (approve its install script if npm blocks it), then `agent-desktop version`" };
|
|
668
596
|
}
|
|
669
|
-
const status = await
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (
|
|
677
|
-
return { ready: false, reason:
|
|
597
|
+
const status = await agentDesktop(["status"], signal);
|
|
598
|
+
if (!status.ok) {
|
|
599
|
+
return { ready: false, reason: `agent-desktop status failed: ${status.error?.message ?? "no envelope"}` };
|
|
600
|
+
}
|
|
601
|
+
const permissions = status.data?.permissions ?? {};
|
|
602
|
+
for (const name of ["accessibility", "screen_recording"] as const) {
|
|
603
|
+
const state = permissions[name]?.state;
|
|
604
|
+
if (state !== "granted" && state !== "not_required") {
|
|
605
|
+
return { ready: false, reason: `Missing ${name} permission (${state}): run \`agent-desktop permissions --guide\` and have the user grant it to the app that launches the command` };
|
|
678
606
|
}
|
|
679
607
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
return { ready: false, reason: `cua-driver call list_apps failed; start the daemon (macOS: open -n -g -a CuaDriver --args serve; Windows: cua-driver serve in the interactive session; Linux: cua-driver serve inside the graphical session): ${apps.stderr.trim()}` };
|
|
608
|
+
if (!status.data?.supported_surfaces?.length) {
|
|
609
|
+
return { ready: false, reason: "No readable surfaces: run inside the user's graphical session with a running AT-SPI bus, then rerun `agent-desktop status`" };
|
|
683
610
|
}
|
|
684
|
-
return { ready: true
|
|
611
|
+
return { ready: true };
|
|
685
612
|
},
|
|
686
613
|
{ timeoutMs: 60_000 },
|
|
687
614
|
);
|
|
@@ -691,17 +618,54 @@ export default workflow({
|
|
|
691
618
|
|
|
692
619
|
const scenario = await ctx.tool(
|
|
693
620
|
"verify-counter-scenario",
|
|
694
|
-
{ artifactsDir: ctx.inputs.artifactsDir
|
|
621
|
+
{ artifactsDir: ctx.inputs.artifactsDir },
|
|
695
622
|
async ({ signal }) => {
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
623
|
+
// Replace the app name, element names, and postcondition with ones you control.
|
|
624
|
+
const app = "Window Demo";
|
|
625
|
+
mkdirSync(ctx.inputs.artifactsDir, { recursive: true });
|
|
626
|
+
// Keep every result as evidence beside the screenshots, and fail rather than report
|
|
627
|
+
// `verified` when a piece of evidence is missing.
|
|
628
|
+
const save = (step: string, envelope: Envelope) => {
|
|
629
|
+
writeFileSync(join(ctx.inputs.artifactsDir, `${step}.json`), JSON.stringify(envelope, null, 2));
|
|
630
|
+
return envelope;
|
|
631
|
+
};
|
|
632
|
+
const capture = async (step: string) => {
|
|
633
|
+
const shot = save(step, await agentDesktop(["screenshot", "--app", app, join(ctx.inputs.artifactsDir, `${step}.png`)], signal));
|
|
634
|
+
if (!shot.ok) throw new Error(`Could not capture the ${step} screenshot: ${shot.error?.code ?? "no envelope"}`);
|
|
635
|
+
};
|
|
636
|
+
const one = async (role: string, name: string) => {
|
|
637
|
+
const found = await agentDesktop(["find", "--app", app, "--role", role, "--name", name, "--exact", "--limit", "2"], signal);
|
|
638
|
+
const matches = found.data?.matches ?? [];
|
|
639
|
+
if (!found.ok || matches.length !== 1 || !matches[0].ref_id) {
|
|
640
|
+
throw new Error(`Expected one ${role} named ${name}: ${found.error?.code ?? `${matches.length} matches`}`);
|
|
641
|
+
}
|
|
642
|
+
return matches[0].ref_id;
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const button = await one("button", "Increment");
|
|
646
|
+
const field = await one("textfield", "Count");
|
|
647
|
+
const read = save("read-before", await agentDesktop(["get", field, "--property", "value"], signal));
|
|
648
|
+
const before = Number(read.data?.value);
|
|
649
|
+
if (!read.ok || Number.isNaN(before)) throw new Error("Count has no numeric value to start from");
|
|
650
|
+
await capture("before");
|
|
651
|
+
|
|
652
|
+
// Act once. An error whose disposition is `retry: "safe"` changed nothing, so the node fails
|
|
653
|
+
// and a new run starts fresh; any other failure may have landed, so the wait below decides.
|
|
654
|
+
const click = save("click", await agentDesktop(["click", button], signal));
|
|
655
|
+
const notDelivered = click.error?.disposition?.retry === "safe";
|
|
656
|
+
if (!click.ok && notDelivered) throw new Error(`Click was not delivered: ${click.error?.code}`);
|
|
657
|
+
|
|
658
|
+
// Verify with a bounded wait on the postcondition, never a sleep and never a second click.
|
|
659
|
+
const expected = String(before + 1);
|
|
660
|
+
const waited = save("wait", await agentDesktop(["wait", "--element", field, "--predicate", "value", "--value", expected, "--timeout", "10000"], signal));
|
|
661
|
+
await capture("after");
|
|
662
|
+
if (waited.ok && waited.data?.found) {
|
|
663
|
+
return { result: "verified", detail: `Count ${before} -> ${expected}; evidence in ${ctx.inputs.artifactsDir}` };
|
|
703
664
|
}
|
|
704
|
-
|
|
665
|
+
// A click that errored without a safe disposition, or a wait that timed out, is not proof
|
|
666
|
+
// either way: report it as unknown or refuted and let the next run start from fresh state.
|
|
667
|
+
const result = click.ok ? "refuted" : "unknown";
|
|
668
|
+
throw new Error(`desktop verification ${result}: Count did not become ${expected} (${waited.error?.code ?? "no match"})`);
|
|
705
669
|
},
|
|
706
670
|
{ timeoutMs: 5 * 60_000 },
|
|
707
671
|
);
|
|
@@ -711,7 +675,7 @@ export default workflow({
|
|
|
711
675
|
});
|
|
712
676
|
```
|
|
713
677
|
|
|
714
|
-
A `verified` result is cached durably, so a resumed run does not click again. A thrown `refuted
|
|
678
|
+
A `verified` result is cached durably, so a resumed run does not click again. A thrown `refuted` or `unknown` result fails the node and is not replayed from cache; a new run executes the preflight and the scenario again from fresh refs, and a durable retry of a resumable failure replays the completed preflight checkpoint and re-runs only the scenario. Refs belong to the snapshot that produced them, so look an element up again after any UI change instead of reusing a ref from an earlier step. Keep the artifacts directory outside the repository unless the evidence belongs in the deliverable. Safety is the same on both paths: one controller per desktop, a dedicated session or account where practical, and an explicit stopping point before destructive or publishing actions.
|
|
715
679
|
|
|
716
680
|
### Classifier and image models in `ctx.tool`
|
|
717
681
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Verify the behavior that changed, then give the reviewer enough evidence to understand the result. A small fix may need a focused test and a short explanation. An interactive change usually needs a real user scenario as well. A video is useful when it shows something a test log or screenshot cannot; it is not required for every PR.
|
|
4
4
|
|
|
5
|
-
For tool installation, automation techniques, platform permissions, and general work in applications, read [Computer use](/computer-use). That guide covers Herdr for terminals, agent-browser for browsers, Mobile Safari, and CDP-exposing apps, and
|
|
5
|
+
For tool installation, automation techniques, platform permissions, and general work in applications, read [Computer use](/computer-use). That guide covers Herdr for terminals, agent-browser for browsers, Mobile Safari, and CDP-exposing apps, and agent-desktop for native desktop apps and OS dialogs on macOS, Linux, and Windows. It also covers native accessibility and application tools when they are easier or more reliable.
|
|
6
6
|
|
|
7
7
|
<a id="select-the-verification-environment" />
|
|
8
8
|
|
|
@@ -13,12 +13,13 @@ Start with the project's existing tests, build, typecheck, and lint commands. Ad
|
|
|
13
13
|
| Changed behavior | Preferred mechanism | What to check |
|
|
14
14
|
| --- | --- | --- |
|
|
15
15
|
| Interactive terminal or TUI | **Herdr**, under its explicit-request and managed-pane requirements; tmux or native Windows psmux as fallbacks | Actual input, rendered output, navigation, resizing, and exit behavior relevant to the change. |
|
|
16
|
-
| Browser/frontend in Chrome/Chromium, Mobile Safari in the iOS Simulator, Electron desktop app or another CDP-exposing app, Slack, or cloud browser | **agent-browser**, for everything in [its scope](/computer-use#what-agent-browser-covers);
|
|
17
|
-
| Native desktop app,
|
|
16
|
+
| Browser/frontend in Chrome/Chromium, Mobile Safari in the iOS Simulator, Electron desktop app or another CDP-exposing app, Slack, or cloud browser | **agent-browser**, for everything in [its scope](/computer-use#what-agent-browser-covers); agent-desktop when agent-browser hits a limitation | The user flow and its visible result, with DOM or network assertions where useful. |
|
|
17
|
+
| Native desktop app, menus and menu bars, desktop Safari or another non-Chromium browser, or OS dialogs and permission prompts | **agent-desktop**: the bundled `agent-desktop` skill and one-shot `agent-desktop` commands when a model chooses actions, or the `agent-desktop` CLI called from `ctx.tool` when workflow code owns the scenario; supplemented by native/app APIs | The exact app's before/after accessibility state from the JSON results, screenshots, a checked postcondition, and the saved or exported result. |
|
|
18
|
+
| Native iOS app in the Simulator or Android app in an emulator | **Native mobile tooling**: `xcrun simctl`, Appium with XCUITest, or `adb` with UI Automator. agent-desktop sees a simulator or emulator only as a desktop window ([coverage and limits](/computer-use#coverage-and-limits)) | The device or emulator actually exercised, its screenshots or recording, and the checked postcondition. |
|
|
18
19
|
| API, library, script, or other non-UI behavior | Existing test runner and shell commands | Inputs, outputs, error handling, and relevant build/type contracts. |
|
|
19
20
|
| Documentation | Documentation checks and example review | Links, navigation, command accuracy, and whether a reader can follow the instructions. |
|
|
20
21
|
|
|
21
|
-
See [tool selection](/computer-use#choose-the-right-tool) before operating a session. Reach for agent-browser first whenever the target is in its scope: its text snapshots cost far fewer tokens than
|
|
22
|
+
See [tool selection](/computer-use#choose-the-right-tool) before operating a session. Reach for agent-browser first whenever the target is in its scope: its text snapshots cost far fewer tokens than desktop accessibility trees and screenshots. Herdr is the terminal priority, but the preference does not authorize control from outside a Herdr-managed pane. Install missing tools when permitted; otherwise use the available fallback and describe any coverage gap.
|
|
22
23
|
|
|
23
24
|
### Reuse past verification
|
|
24
25
|
|
|
@@ -26,7 +27,7 @@ When a shell tool is available, the agent records each check it ran or attempted
|
|
|
26
27
|
|
|
27
28
|
```text
|
|
28
29
|
Assistant-verification: herdr terminal E2E passed: /tasks lists running tasks first at 80x24
|
|
29
|
-
Assistant-verification:
|
|
30
|
+
Assistant-verification: agent-desktop unavailable: Accessibility permission not granted
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
Before choosing checks for a change, the agent looks up these lines for comparable changes (for example with `git log --format='%h %s%n%(trailers:key=Assistant-verification)' -- <path>`) and reuses the commands and scenarios that proved them. A setup recorded as `unavailable` is not retried without a reason to expect a different result. Past records guide the choice; they do not replace the project's required checks. See [execution history](/usage#system-prompt-files) to turn these records off.
|
|
@@ -99,19 +100,19 @@ See [terminal setup and capture commands](/computer-use#terminal-automation-with
|
|
|
99
100
|
|
|
100
101
|
<a id="desktop-safety" />
|
|
101
102
|
|
|
102
|
-
### Native desktop
|
|
103
|
+
### Native desktop changes
|
|
103
104
|
|
|
104
|
-
Use
|
|
105
|
+
Use agent-desktop in a dedicated graphical session, following [the desktop guide](/computer-use#desktop-automation-with-agent-desktop). When a language model chooses each action, whether in an interactive session or in any workflow stage acting outside `ctx.tool`, load the bundled `agent-desktop` skill, read `agent-desktop skills get desktop` and `agent-desktop skills get platform`, and drive the app with one-shot `agent-desktop` commands. When a custom workflow's TypeScript owns the scenario and its postcondition, call the `agent-desktop` CLI from `ctx.tool` as shown in [workflow authoring](/workflows/authoring#desktop-verification-with-agent-desktop-in-ctx-tool). Either way the loop is the same: snapshot the app, act on a ref from that snapshot, then `wait` for the postcondition with a bounded timeout and read it back. Application APIs can still give the strongest assertion, for example checking a saved document through the app API after exercising the visible Save flow.
|
|
105
106
|
|
|
106
|
-
Evidence is the structured
|
|
107
|
+
Evidence is the structured accessibility state plus screenshots, not a screenshot alone. Save the JSON results of the snapshot, the action and the `wait` that verified it, and an `agent-desktop screenshot` before and after the action, to the artifacts directory, and state the postcondition that was checked. A snapshot whose `data.complete` is `false` is a partial observation: drill further before acting. An action result is not proof that the UI changed; only the read that follows is.
|
|
107
108
|
|
|
108
|
-
Capture the actual application
|
|
109
|
+
Capture the actual application. A Chrome recording is not desktop or native iOS evidence, and an agent-browser `-p ios` session proves Mobile Safari behavior, not a native iOS app. agent-desktop sees an iOS Simulator or Android emulator only as a desktop window and may not see the simulated device's UI, so use native mobile tooling (`xcrun simctl`, Appium with XCUITest, or `adb` with UI Automator) for native mobile apps, and name the device or emulator actually exercised. If none is available, report the gap instead of claiming coverage.
|
|
109
110
|
|
|
110
|
-
Missing readiness is a blocked finding, not a failure to work around. Before the first action the stage checks `
|
|
111
|
+
Missing readiness is a blocked finding, not a failure to work around. Before the first action the stage checks `agent-desktop version` (one bounded `npm install -g agent-desktop` attempt if it is missing, approving the package's install script when npm blocks it) and `agent-desktop status`. A builtin Goal or Ralph stage reports a missing macOS Accessibility permission (or Screen Recording when it needs screenshots), a non-interactive Windows session, no graphical Linux session with a running AT-SPI bus (an empty `supported_surfaces`), or a refused or failed install as `blocked`/`needs_human` with the exact remediation (for macOS: `agent-desktop permissions --guide`, then grant the terminal or editor that launches the command), and re-runs `agent-desktop status` when the run resumes. A custom workflow does the same from a preflight `ctx.tool` that calls `ctx.exit({ status: "blocked", reason })`. Unlike a builtin stage, a blocked author exit is terminal and not resumable: after the user grants the permission, start a new run, whose fresh preflight checks again rather than replaying a cached result.
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
Atomic bundles the `agent-desktop` skill, so skip `npx skills add` for it, and leave existing user-level skills alone. The detailed guides come from the installed CLI (`agent-desktop skills get ...`), so they always match its version.
|
|
113
114
|
|
|
114
|
-
Inspect saved files and exports rather than assuming a returned action means a completed operation. An action that times out before its response is `unknown`; resolve it with a fresh
|
|
115
|
+
Inspect saved files and exports rather than assuming a returned action means a completed operation. Retry or substitute an action only when its `disposition.retry` is `safe`. An action that times out before its response, or whose delivery is anything but `not_delivered`, is `unknown`; resolve it with a fresh read and the postcondition, never by replaying the action.
|
|
115
116
|
|
|
116
117
|
## Use evidence in a workflow
|
|
117
118
|
|
|
@@ -156,7 +157,7 @@ Give the reviewer a short summary before linking logs or media. For example, rep
|
|
|
156
157
|
- Limits: <checks/platforms not exercised>
|
|
157
158
|
```
|
|
158
159
|
|
|
159
|
-
Attach a screenshot for a visual state, a short recording for an interaction, or logs for command behavior. When a run used
|
|
160
|
+
Attach a screenshot for a visual state, a short recording for an interaction, or logs for command behavior. When a run used agent-desktop, the evidence is the before/after `agent-desktop screenshot` PNGs, one pair per verified postcondition, with the snapshot, action and `wait` JSON kept locally and cited by name; when it used agent-browser, the evidence is its `screenshot` PNGs and `record` recordings (for an iOS Simulator session, its screenshots plus any `simctl` recording). Put them in the PR body next to the scenario they prove. Include reproduction steps even when media is available. Label local-only paths as local; reviewers cannot open a file on your machine. CI artifact links are useful when readers have access, but note retention limits where relevant.
|
|
160
161
|
|
|
161
162
|
Only upload to an authorized repository or destination. Inspect and redact secrets, personal information, private source content, and unrelated windows before attaching files. Permission to collect local evidence is not permission to publish it.
|
|
162
163
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.57-alpha.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bastani/atomic",
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.57-alpha.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bastani/atomic-natives": "0.9.
|
|
13
|
-
"@bastani/pi-ai": "0.9.
|
|
12
|
+
"@bastani/atomic-natives": "0.9.57-alpha.1",
|
|
13
|
+
"@bastani/pi-ai": "0.9.57-alpha.1",
|
|
14
14
|
"@earendil-works/pi-agent-core": "1.4.0",
|
|
15
15
|
"@earendil-works/pi-codemode": "1.4.0",
|
|
16
16
|
"@earendil-works/pi-tui": "1.4.0",
|
|
@@ -427,18 +427,18 @@
|
|
|
427
427
|
}
|
|
428
428
|
},
|
|
429
429
|
"node_modules/@bastani/atomic-natives": {
|
|
430
|
-
"version": "0.9.
|
|
431
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.
|
|
430
|
+
"version": "0.9.57-alpha.1",
|
|
431
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.57-alpha.1.tgz",
|
|
432
432
|
"license": "MIT",
|
|
433
433
|
"optionalDependencies": {
|
|
434
|
-
"@bastani/atomic-natives-darwin-arm64": "0.9.
|
|
435
|
-
"@bastani/atomic-natives-darwin-x64": "0.9.
|
|
436
|
-
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.
|
|
437
|
-
"@bastani/atomic-natives-linux-arm64-musl": "0.9.
|
|
438
|
-
"@bastani/atomic-natives-linux-x64-gnu": "0.9.
|
|
439
|
-
"@bastani/atomic-natives-linux-x64-musl": "0.9.
|
|
440
|
-
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.
|
|
441
|
-
"@bastani/atomic-natives-win32-x64-msvc": "0.9.
|
|
434
|
+
"@bastani/atomic-natives-darwin-arm64": "0.9.57-alpha.1",
|
|
435
|
+
"@bastani/atomic-natives-darwin-x64": "0.9.57-alpha.1",
|
|
436
|
+
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.57-alpha.1",
|
|
437
|
+
"@bastani/atomic-natives-linux-arm64-musl": "0.9.57-alpha.1",
|
|
438
|
+
"@bastani/atomic-natives-linux-x64-gnu": "0.9.57-alpha.1",
|
|
439
|
+
"@bastani/atomic-natives-linux-x64-musl": "0.9.57-alpha.1",
|
|
440
|
+
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.57-alpha.1",
|
|
441
|
+
"@bastani/atomic-natives-win32-x64-msvc": "0.9.57-alpha.1"
|
|
442
442
|
},
|
|
443
443
|
"engines": {
|
|
444
444
|
"bun": ">=1.4.2",
|
|
@@ -446,8 +446,8 @@
|
|
|
446
446
|
}
|
|
447
447
|
},
|
|
448
448
|
"node_modules/@bastani/atomic-natives-darwin-arm64": {
|
|
449
|
-
"version": "0.9.
|
|
450
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.
|
|
449
|
+
"version": "0.9.57-alpha.1",
|
|
450
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.57-alpha.1.tgz",
|
|
451
451
|
"license": "MIT",
|
|
452
452
|
"os": [
|
|
453
453
|
"darwin"
|
|
@@ -458,8 +458,8 @@
|
|
|
458
458
|
"optional": true
|
|
459
459
|
},
|
|
460
460
|
"node_modules/@bastani/atomic-natives-darwin-x64": {
|
|
461
|
-
"version": "0.9.
|
|
462
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.
|
|
461
|
+
"version": "0.9.57-alpha.1",
|
|
462
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.57-alpha.1.tgz",
|
|
463
463
|
"license": "MIT",
|
|
464
464
|
"os": [
|
|
465
465
|
"darwin"
|
|
@@ -470,8 +470,8 @@
|
|
|
470
470
|
"optional": true
|
|
471
471
|
},
|
|
472
472
|
"node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
|
|
473
|
-
"version": "0.9.
|
|
474
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.
|
|
473
|
+
"version": "0.9.57-alpha.1",
|
|
474
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.57-alpha.1.tgz",
|
|
475
475
|
"license": "MIT",
|
|
476
476
|
"os": [
|
|
477
477
|
"linux"
|
|
@@ -485,8 +485,8 @@
|
|
|
485
485
|
"optional": true
|
|
486
486
|
},
|
|
487
487
|
"node_modules/@bastani/atomic-natives-linux-arm64-musl": {
|
|
488
|
-
"version": "0.9.
|
|
489
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.
|
|
488
|
+
"version": "0.9.57-alpha.1",
|
|
489
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.57-alpha.1.tgz",
|
|
490
490
|
"license": "MIT",
|
|
491
491
|
"os": [
|
|
492
492
|
"linux"
|
|
@@ -500,8 +500,8 @@
|
|
|
500
500
|
"optional": true
|
|
501
501
|
},
|
|
502
502
|
"node_modules/@bastani/atomic-natives-linux-x64-gnu": {
|
|
503
|
-
"version": "0.9.
|
|
504
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.
|
|
503
|
+
"version": "0.9.57-alpha.1",
|
|
504
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.57-alpha.1.tgz",
|
|
505
505
|
"license": "MIT",
|
|
506
506
|
"os": [
|
|
507
507
|
"linux"
|
|
@@ -515,8 +515,8 @@
|
|
|
515
515
|
"optional": true
|
|
516
516
|
},
|
|
517
517
|
"node_modules/@bastani/atomic-natives-linux-x64-musl": {
|
|
518
|
-
"version": "0.9.
|
|
519
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.
|
|
518
|
+
"version": "0.9.57-alpha.1",
|
|
519
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.57-alpha.1.tgz",
|
|
520
520
|
"license": "MIT",
|
|
521
521
|
"os": [
|
|
522
522
|
"linux"
|
|
@@ -530,8 +530,8 @@
|
|
|
530
530
|
"optional": true
|
|
531
531
|
},
|
|
532
532
|
"node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
|
|
533
|
-
"version": "0.9.
|
|
534
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.
|
|
533
|
+
"version": "0.9.57-alpha.1",
|
|
534
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.57-alpha.1.tgz",
|
|
535
535
|
"license": "MIT",
|
|
536
536
|
"os": [
|
|
537
537
|
"win32"
|
|
@@ -542,8 +542,8 @@
|
|
|
542
542
|
"optional": true
|
|
543
543
|
},
|
|
544
544
|
"node_modules/@bastani/atomic-natives-win32-x64-msvc": {
|
|
545
|
-
"version": "0.9.
|
|
546
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.
|
|
545
|
+
"version": "0.9.57-alpha.1",
|
|
546
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.57-alpha.1.tgz",
|
|
547
547
|
"license": "MIT",
|
|
548
548
|
"os": [
|
|
549
549
|
"win32"
|
|
@@ -554,8 +554,8 @@
|
|
|
554
554
|
"optional": true
|
|
555
555
|
},
|
|
556
556
|
"node_modules/@bastani/pi-ai": {
|
|
557
|
-
"version": "0.9.
|
|
558
|
-
"resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.
|
|
557
|
+
"version": "0.9.57-alpha.1",
|
|
558
|
+
"resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.57-alpha.1.tgz",
|
|
559
559
|
"license": "MIT",
|
|
560
560
|
"dependencies": {
|
|
561
561
|
"@anthropic-ai/sdk": "0.129.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.57-alpha.1",
|
|
4
4
|
"description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"atomicConfig": {
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
"prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
92
|
-
"@bastani/atomic-natives": "0.9.
|
|
93
|
-
"@bastani/pi-ai": "0.9.
|
|
92
|
+
"@bastani/atomic-natives": "0.9.57-alpha.1",
|
|
93
|
+
"@bastani/pi-ai": "0.9.57-alpha.1",
|
|
94
94
|
"@earendil-works/pi-agent-core": "1.4.0",
|
|
95
95
|
"@earendil-works/pi-codemode": "1.4.0",
|
|
96
96
|
"@earendil-works/pi-tui": "1.4.0",
|