From cc06ee41edba975a3ac24bd5601ec9c26bc83eee Mon Sep 17 00:00:00 2001 From: Federico Elia Date: Sat, 7 Nov 2026 13:04:34 +0100 Subject: [PATCH] The plugin guide says the one rule, and stops teaching what the model already gets The guide still described menus as if menus.md did not exist, told authors to name a sender domain for the mailbox, and quoted a Lever plugin three changes out of date. It now says the rule a sentence must pass, with one example that belongs and one that does not; the two things that are not road but boundary; and what the code already decides, which is the step budget, the two mailbox visits, and the rules every form gets. Its menus section points at menus.md and asks only for what is a site's own. The Lever example is the file itself. A dry run on a real posting is named as the first way to test a plugin. Co-Authored-By: Claude Opus 5 --- docs/plugins.md | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/docs/plugins.md b/docs/plugins.md index ac5a88d2..a3a08360 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -16,9 +16,9 @@ AIHawk reads the plugins when it starts, so restart it after you change one. | Field | What it does | |---|---| | `name` | The name AIHawk shows while it fills the form. Default: the file name without `.md`. | -| `hosts` | The sites the plugin is for, as a list, such as `[jobs.lever.co]`. A posting matches when its host is one of these or ends with `.` and one of them, so `eu.jobs.lever.co` matches too. Required unless `when` is `always`. | +| `hosts` | The sites the plugin is for, as a list, such as `[jobs.lever.co]`. A posting matches when its host is one of these or ends with `.` and one of them, so `jobs.eu.lever.co` matches too. Required unless `when` is `always`. | | `apply_path` | Added to the posting's path to reach the form, once: `/apply` turns `https://jobs.lever.co/acme/1` into `https://jobs.lever.co/acme/1/apply`. A link that already ends with it is left as it is. The query string is kept. Without it, the posting's own link is the form's link. | -| `when` | Only `always`. The body is then added to every form's instructions, beside the site's own plugin. Such a plugin has no `hosts` and does not make a site count as one AIHawk can apply to. Three are bundled: `gmail.md` and `outlook.md` for a code a site sends by email, and `menus.md`, which tells the model how each kind of list is worked on any site. Say in a site's plugin only what differs from those. | +| `when` | Only `always`. The body is then added to every form's instructions, beside the site's own plugin. Such a plugin has no `hosts` and does not make a site count as one AIHawk can apply to. Three are bundled: `gmail.md` and `outlook.md` for a code a site sends by email, and `menus.md`, which tells the model how each kind of list is worked on any site. Say in a site's plugin only what differs from those, and never repeat how a list is worked or where to look in a mailbox. | A file that does not parse (no front matter, `hosts` missing, a `when` other than `always`, empty instructions) stops AIHawk at start with a message that names the plugin. Save the file as UTF-8. A byte-order mark at its start, as some Windows editors write, is fine: AIHawk skips it. @@ -29,7 +29,7 @@ The bundled plugins follow roughly this order, which is the one to use: 1. How the site's pages are built: one page or several, whether the form is on the posting or at `/apply`, what loads late, what to read first, what blocks the form (a closed posting, a captcha, a sign-in). 2. The basic fields, by id or by label. 3. The CV upload: which input to use, and what not to click (for example "Apply with LinkedIn" or "Autofill"). -4. Questions the company adds, and the fields that are lists or menus: how to choose and how to check that the choice was taken. +4. Questions the company adds, and the lists that behave in a way `menus.md` does not cover. 5. The send control: its selector and its label. 6. `After sending:`, last. @@ -47,32 +47,41 @@ The last paragraph starts with `After sending:`. It says how the site shows that No code reads this paragraph; the model does. Say what the confirmation looks like for this site, and say that the form still being on the page means nothing was received. Do not rely on one exact phrase, because the wording is set per job board. -The model reports one of two results when it finishes a form: `ready` (every required field is filled) or `blocked` (the form cannot be filled, for example because of a captcha). Say in the plugin when `blocked` is the right answer. +The model reports one of two results when it finishes a form: `ready`, every required field filled, or `blocked`. Status `ready` needs the send control's selector, so always name that control. The rules every form gets already cover a captcha puzzle, a login wall, a closed posting and a required fact about the candidate that the CV does not give, so say `blocked` only for what is this site's own. ## When the model is stuck A plugin earns its keep in the moments the form does not behave. The model is told, for every site, never to repeat an action that left the page as it was; the plugin says what to do instead on this site. Write it as the failure the model will see, then the way out, and say where it was seen. What real forms have shown so far: -- A click "not actionable" or "would have landed elsewhere": something covers the control, or the option is out of view. Name the cover and how to dismiss it (a cookie banner: the "Decline all" button inside `div[aria-label='Cookie Consent']` on Workable), or the way to reach the option (ArrowDown as many times as its position, then Enter, on Paylocity's listboxes). +- A click "not actionable" or "would have landed elsewhere": something covers the control. Name the cover and how to dismiss it (a cookie banner: the "Decline all" button inside `div[aria-label='Cookie Consent']` on Workable). - browser_type refused with "not an input": the id sits on a wrapper. Name the control itself (`input#question_` or `textarea#question_` on SmartRecruiters). - An upload refused, "the click landed on an icon": name the input that took the file on a real form (`#description_input_` on Workable). - A key that scrolls nothing: the form scrolls inside its own container. Say to reach a field with `scrollIntoView()` through browser_evaluate (SmartRecruiters), not with PageDown. - A page that redraws itself after every click without changing address (Workday): say to read the page again after each one. -- A code or a link sent by email: say which sender domain to search for, and that the model has two looks at the mailbox before the form ends as blocked. +- A code or a link sent by email: say what this site's message looks like, its sender and its subject, if you have seen one. Where to look for it is the mailbox plugins' business, and the model's. Give the cost when you know it: "seen on a real form, 40 steps lost" tells the model how much the shortcut is worth. ## Menus and dropdowns -Most steps lost on real forms went into lists. Say, for each list on the site, which of these it is and what its options look like: +`menus.md` reaches the model on every form. It names the five kinds of list, the test that tells them apart, how to check that a choice was taken, and the two ways past a click that does not land. A site's plugin adds only what is that site's own: an id pattern such as Paylocity's `#__listbox__option__N`, or a list that hides its rows in a shadow root, as SmartRecruiters does. -- A native `