Commit Graph

136 Commits

Author SHA1 Message Date
0118254046 Poll for submission success instead of single check
After clicking submit, poll up to 4 times (2.5s + 3x2s = ~8.5s total)
for success text or form disappearance. Handles invisible reCAPTCHA
verification delay. Stops early on validation errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:11:50 -08:00
5caa063175 Show apply_url in summary for external jobs, add Ashby closed detection
External ATS jobs now show their actual apply URL instead of the
LinkedIn listing URL. Also added Ashby-specific "job not found"
text to closed detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:08:50 -08:00
e4e3055bb1 Skip Phone/Name fieldsets that wrap already-handled input groups
Greenhouse wraps phone inputs in a fieldset with legend "Phone"
that gets picked up as an unanswered radio group. Skip these
since the actual phone input is handled separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:05:03 -08:00
c680105bc8 Fix phone country code search input being flagged as unknown
Skip inputs with placeholder="Search" or pre-filled values when
they have a "Phone" label — these are country code pickers, not
the actual phone input.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:01:53 -08:00
f586c6d091 Decouple form filler from LinkedIn modal selector
Form filler now defaults to page root instead of scoping to
[role="dialog"]. LinkedIn Easy Apply passes its modal selector
explicitly. Fixes external ATS forms being scoped to wrong
container. Also improved Greenhouse handler with targeted
resume upload and form detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:58:51 -08:00
a17886e58b Fix Ashby resume upload and add validation error logging
Ashby wraps file input inside #_systemfield_resume container — search
for the actual input[type="file"] element. Also capture and log
validation errors from the page when submit returns incomplete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:47:15 -08:00
ae797d73eb Refactor apply handlers: generic with extensions + domain auto-routing
Generic handler now accepts options (transformUrl, formDetector,
submitSelector, resumeSelector, beforeSubmit, verifySelector, etc.).
Each ATS handler passes its overrides instead of reimplementing.

Registry resolves handlers by: apply_type -> URL domain -> generic fallback.
New ATS handlers only need to export SUPPORTED_TYPES and an apply() that
calls genericApply with platform-specific options.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:41:54 -08:00
4f202a4e91 Build dedicated Ashby handler instead of generic delegation
Handles Ashby-specific quirks:
- Auto-appends /application to job URLs
- Targets #_systemfield_resume for resume upload (not autofill input)
- Clicks "Submit Application" specifically, avoiding "Upload file" buttons
- Checks for Ashby-specific form fields to verify submission

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:39:24 -08:00
6492444a3e Use job queue metadata instead of scraping page for title/company
Page scraping was grabbing wrong elements (e.g. "Location" instead of
company name on Ashby). Queue already has correct metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:37:31 -08:00
630996f643 Use DEFAULT_MODEL constant for Anthropic API calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:36:44 -08:00
e1a4ccf7a0 Fix model ID to use full claude-sonnet-4-6-20251101
Short alias was returning 404 from Anthropic API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:35:27 -08:00
a4f84cd4c3 Skip social media fields (Twitter, GitHub, etc.) in form filler
Return empty string to leave them blank without triggering needs_answer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:32:30 -08:00
73c23d9bf1 Improve generic applier: better closed/404 detection and submit selectors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:20:24 -08:00
1772229ae7 Fix CAPTCHA detection to allow invisible reCAPTCHA
Invisible reCAPTCHA (size=invisible) fires on submit and usually passes
automatically. Only block on visible CAPTCHA challenges. Also fix form
detection to work without <form> wrapper elements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:08:44 -08:00
260f996ebc Add generic external ATS applier
Best-effort form filler for any career page with standard HTML forms.
Handles single-page and multi-step flows, resume upload, login wall
and CAPTCHA detection. All ATS stub handlers now delegate to generic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:02:55 -08:00
b19c1ee77e Add ATS patterns for grnh.se, myworkdaysite, eightfold, comeet, gem, adp, dayforcehcm
Reclassifies 58 more unknown_external jobs using URLs already in queue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:58:47 -08:00
273a32e571 Add sidecar update pattern to prevent concurrent queue writes
Secondary processes (standalone classifier, ad-hoc scripts) now write
to queue_updates.jsonl via writePendingUpdate() instead of modifying
jobs_queue.json directly. Primary processes pick up updates on next
loadQueue() call using atomic rename-then-read.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:46:36 -08:00
de92ba85c7 Add more ATS patterns for external job classification
Adds applytojob.com (Jobvite), trakstar, resumator, breezy, jazz,
recruitee, applicantpro, paylocity, paycom, ultipro, successfactors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:56:53 -08:00
c33fb2ba0d Fix classifyExternalJobs to extract URL from redirect link
External Apply buttons are <a> tags with LinkedIn redirect URLs, not
<button> elements. Extract the real URL from the redirect's query
parameter instead of clicking and waiting for a new tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:51:38 -08:00
3a02c40b02 Move RATE_LIMIT_COOLDOWN_MS to constants file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:16:14 -08:00
69eb6b124f Classify unknown_external jobs by following Apply redirects
After LinkedIn search completes, visits each unknown_external job page,
clicks the Apply button, captures the redirect URL, and matches against
known ATS patterns to identify the actual application platform.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:13:32 -08:00
dfe6810acc Add direct URL rate limit text to detection
LinkedIn shows different text on direct /apply/ URL vs button click:
- Button: "Easy Apply limit"
- Direct URL: "limit daily submissions" / "apply tomorrow"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:58:37 -08:00
331408be41 Detect LinkedIn Easy Apply daily limit and stop run
When LinkedIn shows "Easy Apply limit" message, detect it in the
modal-open check, return rate_limited status, put job back in queue,
send Telegram alert, and stop the entire run. Prevents burning retries
and wasting browser sessions when rate limited.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:54:45 -08:00
814492d9b6 Skip file upload when input only accepts images, not PDF/DOC
Some LinkedIn listings have image-only upload fields (JPG/PNG).
Don't attempt to upload a PDF resume to these inputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:41:34 -08:00
38dabbd6dd Fix address hallucination and duplicate status messages
- answerFor returning '' (intentionally blank) was treated as falsy,
  falling through to AI which fabricated "123 Main Street". Now
  empty string skips the field without triggering AI or reporting unknown.
- status.mjs was printing to stdout AND sending via Telegram, causing
  OpenClaw to relay a duplicate plain-text copy. Now only prints to
  stdout when Telegram isn't configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:47:18 -08:00
84a98e7839 Treat number fields as required even if DOM doesn't mark them
LinkedIn validates number fields even when not marked required in the
DOM. Previously these were skipped (no AI call, no answer). Now number
fields always trigger AI fallback and are reported as unknown if empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:43:39 -08:00
aefd1f2023 Fix label dedup for space-separated duplicates
LinkedIn renders label text twice in nested spans, producing
"Question? Question?" instead of "Question?". The old dedup only
caught exact concatenation (ABCABC); now also handles space-separated
duplicates by comparing left/right halves at the midpoint space.

Applied to all 4 copies: extractLabel, _extractLabel, normalizeLegend,
_normalizeLegend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:28:51 -08:00
05739a455b Fix AI hallucinating street addresses and other facts
- Add street/address pattern to answerFor() — returns profile address or empty string
- Update AI prompt: return "UNKNOWN" instead of guessing facts
- Handle UNKNOWN response by treating it as no answer (triggers Telegram ask)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:25:32 -08:00
fd30646d8a Fix number field validation: strip text from numeric answers
Number inputs (type="number") were getting answers like "5 years"
instead of "5", causing LinkedIn validation errors. Now:
- AI gets "(must be a number, no text or units)" hint
- Answers are stripped to digits before filling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:21:37 -08:00
f248e8573f Auto-create claw-apply proxy if it doesn't exist
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:06:47 -08:00
df9c790a0e Look up proxy by name 'claw-apply' instead of stored ID
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:06:14 -08:00
80d2323a37 Dynamic profile lookup from auth connection
Profile name is returned by ensureAuth() from the auth connection
(looked up by domain). No more storing profile names in settings.json
for the applier flow. createBrowser() still supports legacy platform
keys as fallback for searcher/setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:02:19 -08:00
668a40a51a Look up auth connections by domain instead of stored ID
Connection IDs change when re-authing. Instead of storing IDs in
settings.json (which go stale), look up connections by domain at
runtime via kernel.auth.connections.list({ domain }). This keeps
the applier in sync regardless of connection recreation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:55:26 -08:00
59e410b9c4 Add auth health check before browser creation
- Rewrite session.mjs: check connection status via SDK before creating
  browser. If NEEDS_AUTH + can_reauth, auto re-auth with stored creds.
  If can't re-auth, send Telegram alert and skip platform.
- Wire ensureAuth() into job_applier.mjs before createBrowser()
- Jobs are returned to queue (not failed) when auth is down

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:45:24 -08:00
6d69443f51 Pass placeholder format hints to AI for inputs and textareas
Captures placeholder from textareas in snapshot. When AI fallback is
used, includes placeholder as a format hint so the AI knows expected
format (dates, phone numbers, URLs, etc).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:12:35 -08:00
3bf1786895 Detect date format from placeholder (MM/DD/YYYY) instead of label matching
Reads input placeholder in snapshot. If it contains MM/DD/YYYY pattern,
auto-fills with today's date in the correct format. Generic — works
regardless of the field label.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:05:41 -08:00
7d81d59cb5 Broaden date field detection — match any label containing 'date'
Catches 'Date of Application', 'Available Start Date', and other
date fields that expect mm/dd/yyyy format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:04:10 -08:00
fdb0224226 Fix date inputs and dismiss date picker/sub-form popups
- Start date fields now return mm/dd/yyyy format instead of "Immediately"
- Cancel any open sub-forms or date pickers before filling (handles
  stacked popups with double-cancel check)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:02:35 -08:00
029360b118 Dismiss LinkedIn education/experience sub-forms before filling
LinkedIn sometimes opens an Add Education/Experience sub-form with
Save/Cancel buttons that blocks the main Next button. Detect and
cancel these before attempting to fill the step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:54:40 -08:00
d0a40e4654 Batch CDP calls: single-evaluate debug info, data-claw-idx tagging, field count logging
- getModalDebugInfo: one evaluate() for heading, buttons, errors (was N+2 calls)
- selectOptionFuzzy: batch-read option texts in one evaluate (was N calls)
- Tag elements with data-claw-idx during snapshot, query by attribute in fill()
  (fixes fragile positional index matching for checkboxes/inputs)
- Log field counts per fill step for debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:50:47 -08:00
a956b98941 Stop auto-checking 'top choice' and 'interested' checkboxes
Only auto-check confirm/agree/consent checkboxes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:46:03 -08:00
23eb5284fa Fix snapshot evaluate: scope queries to container element, handle Page fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:42:41 -08:00
aeb41b42c2 Optimize form_filler: batch DOM reads into single evaluate() call
Instead of 60+ sequential CDP round-trips per step (isVisible, getLabel,
inputValue, isRequired for each element), snapshot all form state in one
evaluate() call, do answer matching locally, then only make CDP calls to
fill/click elements that need action.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:39:18 -08:00
dc7113907b Fix Anthropic API 404 — correct model ID to claude-sonnet-4-6
The dated model ID claude-sonnet-4-6-20251101 doesn't exist, causing
keyword generation to fall back to static keywords.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:24:38 -08:00
c4e5dbc32a Fix Telegram code block rendering — remove leading spaces from messages
Telegram Markdown treats lines starting with spaces as preformatted code
blocks. Replaced leading spaces with bullet points and arrows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:14:47 -08:00
cd454f8cc2 Clean up status report and apply summary
Status: only show non-zero queue counts, compact breakdown line,
remove emoji clutter
Apply summary: remove misleading green check on header, show applied/total

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:05:45 -08:00
b0a2eb3746 Fix fieldset radios: validate answers against options, fix checkbox skip, fix authorization match
Tested against Webflow (4311307116) and Scout Global (4378934058):
- Webflow: reaches Review page with all radios checked and checkbox group selected
- Scout Global: already applied successfully with select fix

Three fixes:
1. Fieldset radio answers validated against available options - prevents
   experience pattern returning "7" when options are Yes/No
2. Checkbox groups no longer skipped when one checkbox already checked -
   was preventing multi-select from working on retry
3. "authoriz" pattern matches both "authorized" and "authorization"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:05:02 -08:00
053c0b1242 Fix select answer mismatch: validate answer against available options
Built-in answerFor() could return a number (e.g. "7" for years experience)
when the select only has Yes/No options, causing selectOptionFuzzy to fail
silently. Now checks if the answer matches any available option before using
it, falling through to AI with actual options if not.

Also added "right to work" to work authorization pattern matching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:44:49 -08:00
c99ea10585 Richer search and filter summaries
Search: show per-track breakdown (found/added per track name)
Filter: show top 5 scoring jobs with score, title, company and cost

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:34:21 -08:00
82fa2b3697 Rich apply summary with per-job details (title, company, link)
Each job now shows title @ company with a clickable link, grouped by
status category. Only non-empty categories are shown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:30:58 -08:00