724ba5581ce4bf5115a631222053b8306f190a0f
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
22db6eae8d |
Add repeat blocks, per-run inputs, skipIfNotFound, and orphaned-run recovery
Repeat. A `repeat` block runs its steps several times, with the count either fixed in the config or taken from an input the user sets on the dashboard. The block is unrolled in resolveSteps before the runner sees it, so the runner needs no loop, the run's total step count stays honest, and every iteration appears in the log as its own line — a failure on the third purchase reads as "(3/5)" rather than as an indistinguishable repeat of the first. Counts are clamped server-side against the automation's declared min/max, and expansion is capped at 400 steps and three levels of nesting. Each iteration can be a purchase, so the number is not taken on trust from the client, and the confirmation dialog names it before anything runs. skipIfNotFound on a click or type step tolerates an element that is not on the page — a cookie banner, a modal that only sometimes appears. Only absence is tolerated. That distinction needed a new NotFoundError: previously a missing element, an unreachable dashboard, a missing tab and a covered button all surfaced as the same DashboardError, and skipping that whole class would mean a step quietly passing while the extension was down. Orphaned runs are now reaped. Only one run executes at a time, so a run left in 'running' when its runner went away blocked every future run — restarting the daemon mid-run deadlocked the queue, which is exactly what happened. The heartbeat decides: a runner that is gone, or up and reporting idle, is not driving that run whatever the status column says. Gated on the busy flag rather than elapsed time alone, since a run sitting in a waitFor gate or a sign-in wait can legitimately go minutes without progress. Lucid Trading is scaffolded with no automations yet. One match pattern covers both its hosts — `*.` matches the apex as well as subdomains, confirmed against a live tab. Its signed-out pattern is `//lucidtrading.com/` rather than `lucidtrading.com/dashboard`: the leading slashes anchor it to the start of the host, and without them the substring also matches dash.lucidtrading.com, which would abort every step while properly signed in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
36c5b69550 |
Add session handling, waitFor gating, and the Tradeify purchase flow
Step vocabulary gains `waitFor`: block until a selector exists, then continue. Nothing is clicked or typed — it is a gate for conditions something outside the run has to satisfy. Unlike every other step it carries no signed-out guard, because the things worth gating on often sit on the login page, where that guard would abort the run at exactly the wrong moment. Honours the dashboard's Stop button, since a two-minute gate that ignored it would be worse than no gate. Session handling. A run that lands on the login page must not continue: once redirected, every selector resolves against a login form, so a click aimed at "Add Account" hits whatever that form renders in the same place. Runs now detect the redirect and stop before sending any input, with a distinct SignedOutError rather than a generic failure. Two ways out of that state, in order: a firm's `authSteps` run and the failed step is retried, or — when none are defined — the run pauses for signedOutWaitSeconds so a human can sign in, then resumes. Auth steps are verified rather than trusted: they can all "succeed" while the site still rejects the sign-in, so the session is re-checked before the retry, and the run stops with "auth steps ran but the session is still signed out" if it did not take. That check polls for up to 20s instead of reading once. Submitting a login form starts a network round trip and then a redirect, so the tab still shows the login URL for a second or two afterwards; checking immediately failed a sign-in that was merely in flight, killing run #15 nine seconds after it had actually worked. Third instance of the same mistake in this system — reading page state immediately after an action that triggers async navigation. The runner reports its version in the heartbeat and the dashboard blocks the buttons when it is behind. A running Python process does not reload when the source changes, so a stale runner fails on step types it predates; that cost a debugging round when a navigate step reached a runner that had never heard of one. lib/automations.ts carries the Tradeify buy-accounts flow: navigate to the dashboard, open Add Account, pick the account type and size, enter the account name, and work through the challenge widget before submitting. Selectors are authored by hand against the live page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b748f95372 |
Add automation framework, typing, and runner for the autobuyer
Turns the autobuyer from a page scraper into something that acts. A dashboard button queues a run; a desktop process executes it against the real browser. lib/automations.ts — automations are declarative step lists nested inside the firm whose site they drive. Steps are click / type / wait / navigate, and they inherit the firm's tab pattern and URL, so one firm's automation can't act on another's tab. Adding a button means adding an entry here; the page renders buttons from the API and the runner receives steps from the server, so neither needs editing. Runs key on firm:automation — every firm will plausibly have its own "buy-accounts", and a bare id would resolve to the wrong one. clicker/runner.py — the daemon behind the buttons. Claims a queued run, works through the steps, reports each one back for the page's live log. Only one run executes at a time: two processes driving one physical mouse would interleave clicks. Heartbeats on its own thread, because a step can block for tens of seconds and folding the beat into the main loop would show the runner as offline in the middle of the run it was executing. clicker/actions.py — one implementation of the safety checks, shared by the CLI and the runner. Refuses to act when the element is covered by an overlay, when coordinates fall off-screen, when the browser can't be confirmed frontmost, or (for type) when the target isn't an editable field. Typing: uneven human cadence, and the field is read back afterwards and compared against what was typed — a field that never took focus fails silently and looks identical to success otherwise. Non-ASCII is rejected because pyautogui skips those characters without complaint, and newlines because Enter may submit the form. Typos are deliberately not simulated: a mistyped digit in a trading form is a real loss, and the correction is the part that can go wrong. Extension: opens the firm's page when no tab matches, navigates to a specific page for a navigate step (skipped when already there, so page state survives), and retries the locate while a freshly loaded React app mounts — `complete` only means the document loaded. Staleness reporting, after it cost three debugging rounds: Chrome doesn't reload an unpacked extension and Python doesn't reload a running process, so both now report their version. A stale runner gets a red banner naming both versions and the automation buttons are disabled, rather than failing mid-run on a step type it predates. Scale detection is now conservative: a raw OS/browser width ratio is only trusted when it lands on a real scaling factor. On this multi-monitor desktop the previous logic would have silently halved every coordinate. Verified end to end against the live browser: navigate, locate, and a real click (run #12, all three steps). API round-trips, claim-once semantics, run cancellation, the heartbeat online/offline lifecycle, motion geometry and timing, focus activation, and typing verification all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |