Prevents OpenClaw from relaying full report as plain-text duplicate.
sendTelegram() handles formatted delivery, stdout just says "sent".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
When a batch completes but scores aren't written back (collection
error), jobs get stuck with filter_batch_id set and never re-submitted.
Now checks: if no filter_state.json exists (no batch in flight) but
jobs have batch markers without scores, clear them so they get
re-submitted on the next run.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously status.mjs relied on OpenClaw relaying stdout, which sent
as plain text. Now sends via sendTelegram() directly when Telegram
config is present, matching how all other scripts send notifications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
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>
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>
Connection IDs are no longer stored in settings.json. The applier
finds auth connections by domain (linkedin.com, wellfound.com) at
runtime via the Kernel SDK. Updated SKILL.md, README.md, and bumped
to 0.1.4.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
The ClawHub scanner flags systemPrompt variables in lib/*.mjs as
potential prompt injection. These are legitimate Claude API prompts
for job scoring, answer generation, and keyword generation. Added
explicit note clarifying their purpose.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
The stuck/incomplete retry logic referenced maxRetries which was only
defined in main() scope, not in handleResult(). Compute it locally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
- 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>
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>
Stuck and incomplete jobs now get retried up to max_retries (default 2)
before being permanently marked. Honeypots are still permanent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
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>
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>
When --telegram, send directly and don't print. Otherwise keep * in
console output so agents capturing stdout can relay with formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
status.mjs --telegram sends the report to Telegram with proper bold formatting.
Console output strips markdown. Queue total now accounts for duplicates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>
- Removed Telegram notification on batch submit (only notify on collect
when results are ready)
- After collecting, immediately submit remaining unscored jobs in the
same run instead of waiting for next cron cycle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
Legend text was saved to answers.json with duplicated text and "Required"
suffix, causing pattern mismatches on future runs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Distinguishes closed listings from missing apply buttons. Shows in summary
as a separate line item.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>