chore: auto release commit (2026-09-15 01:07:11)
Some checks failed
技能自动化发布 / release (push) Failing after 4s
Some checks failed
技能自动化发布 / release (push) Failing after 4s
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
本文件对应技能市场 **「更新日志」** Tab。
|
||||
|
||||
## 1.1.22
|
||||
|
||||
- **登录流程接入 sandbox 自动登录**:root 登录页(https://sandbox.jc2009.com/)的 #jc-user / #jc-pass 默认有 demo/123456 预填
|
||||
- 新增 `_try_auto_sign_in(page, wait_input_sec=2)`:等 input 出现 2 秒 → 检查是否有值 → 有则自动点 Sign in
|
||||
- 新增 `_wait_for_login_inputs_filled(page, wait_sec)`:input 为空时等用户输入 → 两个 input 都有值后自动点 Sign in
|
||||
- 已登录态(profile cookie 复用)直接跳过
|
||||
- 输入超时 fallback 到 `_wait_for_manual_login`(多信号判定)
|
||||
|
||||
## 1.1.21
|
||||
|
||||
- **修复登录完成检测盲区**:`_wait_for_manual_login` 现在监听 3 个信号判断「登录已完成」,任一触发即继续
|
||||
|
||||
2
SKILL.md
2
SKILL.md
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 合同到期智能预警与续聘辅助
|
||||
description: "当用户说要扫描即将到期的合同、查 90/60/30 天预警名单、给某位教师生成续聘任务书、下发单位版续聘清单、从 SmartCampus HR 平台抓合同数据、解析续聘材料 PDF/Word、查看本月合同到期人员清单时,加载本技能。支持定时扫描(每日凌晨)+ 数据管理 + Agent 三入口,覆盖合同到期智能预警与续聘辅助全流程。"
|
||||
version: 1.1.21
|
||||
version: 1.1.22
|
||||
author: 深圳匠厂科技有限公司
|
||||
metadata:
|
||||
openclaw:
|
||||
|
||||
@@ -167,8 +167,127 @@ def _is_logged_in(page) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _try_auto_sign_in(page, wait_input_sec: int = 2) -> bool:
|
||||
"""v1.1.21:检测登录页 demo/123456 是否已预填,有则自动点 Sign in。
|
||||
|
||||
适用场景:sandbox root 登录页(https://sandbox.jc2009.com/)
|
||||
默认有 demo + 123456 预填在 #jc-user / #jc-pass,脚本无需 fill 直接点。
|
||||
|
||||
行为:
|
||||
1. 等 #jc-user 出现,最长 wait_input_sec 秒(默认 2s)
|
||||
2. 检查 #jc-user 和 #jc-pass 是否都有值
|
||||
3. 有值 → 点 Sign in → return True
|
||||
4. 无值 → return False(交给 _wait_for_manual_login 接管)
|
||||
|
||||
不会自动 fill(如果没值就放弃,让用户自己输)。
|
||||
"""
|
||||
try:
|
||||
page.wait_for_selector("#jc-user, #jc-pass", timeout=wait_input_sec * 1000)
|
||||
except Exception:
|
||||
logger.info(
|
||||
"platform_collector: %ds 内未出现登录 input,跳过自动登录",
|
||||
wait_input_sec,
|
||||
)
|
||||
return False
|
||||
|
||||
user_input = page.locator("#jc-user").first
|
||||
pass_input = page.locator("#jc-pass").first
|
||||
if user_input.count() == 0 or pass_input.count() == 0:
|
||||
return False
|
||||
|
||||
user_val = (user_input.input_value() or "").strip()
|
||||
pass_val = (pass_input.input_value() or "").strip()
|
||||
|
||||
if not user_val or not pass_val:
|
||||
logger.info(
|
||||
"platform_collector: 登录页 input 为空(user=%r, pass=%r),"
|
||||
"等用户输入",
|
||||
bool(user_val), bool(pass_val),
|
||||
)
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
"platform_collector: 登录页已预填账号(user=%r),自动点 Sign in",
|
||||
user_val,
|
||||
)
|
||||
btn = page.locator('button[type="submit"]:has-text("Sign in")').first
|
||||
if btn.count() == 0:
|
||||
logger.warning("platform_collector: 未找到 Sign in 按钮")
|
||||
return False
|
||||
btn.click()
|
||||
return True
|
||||
|
||||
|
||||
def _wait_for_login_inputs_filled(page, wait_sec: int = HUMAN_WAIT_TIMEOUT) -> bool:
|
||||
"""v1.1.21:等用户输入完账号密码,然后自动点 Sign in。
|
||||
|
||||
- 每 HUMAN_POLL_INTERVAL 秒检查 #jc-user / #jc-pass 是否有值
|
||||
- 两个都有值 → 自动点 Sign in → return True
|
||||
- 超时未填完 → return False
|
||||
"""
|
||||
deadline = time.monotonic() + wait_sec
|
||||
last_prompt_at = time.monotonic()
|
||||
last_state_log = time.monotonic()
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
cur_url = page.url
|
||||
user_input = page.locator("#jc-user").first
|
||||
pass_input = page.locator("#jc-pass").first
|
||||
|
||||
# 已不在登录页(用户可能手动点了别的)
|
||||
if not _is_login_page_url_for_user(cur_url):
|
||||
logger.info(
|
||||
"platform_collector: URL 已离开登录页(%s),停止等输入", cur_url,
|
||||
)
|
||||
return True
|
||||
|
||||
if user_input.count() > 0 and pass_input.count() > 0:
|
||||
user_val = (user_input.input_value() or "").strip()
|
||||
pass_val = (pass_input.input_value() or "").strip()
|
||||
now = time.monotonic()
|
||||
if now - last_state_log >= HUMAN_PROMPT_INTERVAL:
|
||||
last_state_log = now
|
||||
logger.info(
|
||||
"platform_collector: input 状态 user=%r pass=%r",
|
||||
bool(user_val), bool(pass_val),
|
||||
)
|
||||
if user_val and pass_val:
|
||||
logger.info(
|
||||
"platform_collector: ✅ 用户输入完成(user=%r),自动点 Sign in",
|
||||
user_val,
|
||||
)
|
||||
btn = page.locator('button[type="submit"]:has-text("Sign in")').first
|
||||
if btn.count() > 0:
|
||||
btn.click()
|
||||
return True
|
||||
|
||||
# 提醒用户
|
||||
now = time.monotonic()
|
||||
if now - last_prompt_at >= HUMAN_PROMPT_INTERVAL:
|
||||
last_prompt_at = now
|
||||
remaining = int(deadline - now)
|
||||
print(f"[登录] 等待用户输入账号密码... 剩余 {remaining}s")
|
||||
|
||||
time.sleep(HUMAN_POLL_INTERVAL)
|
||||
|
||||
logger.warning(
|
||||
"platform_collector: %ds 内用户未完成输入", wait_sec,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _is_login_page_url_for_user(url: str) -> bool:
|
||||
"""判断 URL 是否还在登录页(用于 _wait_for_login_inputs_filled 退出条件)。"""
|
||||
u = url.lower()
|
||||
if "/login" in u or "/signin" in u or "/auth/" in u:
|
||||
return True
|
||||
# 根域本身也是登录页(demo 数据未提交前的初始态)
|
||||
return u.rstrip("/").endswith("sandbox.jc2009.com")
|
||||
|
||||
|
||||
def _wait_for_manual_login(page, wait_sec: int = HUMAN_WAIT_TIMEOUT) -> bool:
|
||||
"""手工登录 fallback(v1.1.19 重写,v1.1.20 加强):等用户在浏览器完成登录。
|
||||
"""手工登录 fallback(v1.1.19 重写,v1.1.20 加强,v1.1.21 修订):
|
||||
等用户在浏览器完成登录。
|
||||
|
||||
判定「登录完成」的多个信号(满足任一即视为完成):
|
||||
1. URL 含 `/app/` 且稳定 URL_STABLE_REQUIRED_S 秒(v1.1.19 旧逻辑)
|
||||
@@ -493,19 +612,14 @@ def collect_all(
|
||||
# 所有 wait_for_selector 默认 30s,调试时不直观)
|
||||
page.set_default_timeout(DEFAULT_PAGE_TIMEOUT_MS)
|
||||
|
||||
# 1. v1.1.19:先 goto **根域**(用户最初看到的页面),**等 URL 稳定**,
|
||||
# 避免被 sandbox 中间页误导或打断已登录态。
|
||||
# 已登录:根域会自动跳到 /app/home(满足 _is_logged_in)
|
||||
# 未登录:根域就是登录页,用户在浏览器输入账号密码
|
||||
# 1. v1.1.21:先 goto **根域**(sandbox 登录页),让 SPA hydrate 2 秒
|
||||
logger.info(
|
||||
"platform_collector: 启动,goto ROOT_URL=%s (当前 URL=%s)",
|
||||
ROOT_URL, page.url,
|
||||
)
|
||||
_goto(page, ROOT_URL)
|
||||
page.wait_for_timeout(SPA_HYDRATION_MS)
|
||||
# 2. 等 URL 稳定:已登录→/app/xxx,未登录→根域或 /login
|
||||
# **不做任何操作**,避免打断浏览器 session
|
||||
_wait_for_url_stable(page, "/", max_wait_sec=10)
|
||||
# 给 React/Vue 框架 2 秒 hydrate 时间,看登录 input 是否就绪
|
||||
page.wait_for_timeout(2_000)
|
||||
|
||||
if _is_logged_in(page):
|
||||
logger.info(
|
||||
@@ -513,13 +627,25 @@ def collect_all(
|
||||
page.url,
|
||||
)
|
||||
else:
|
||||
# 2. 等 #jc-user / #jc-pass 出现(最多 2 秒)
|
||||
# 若 sandbox 已预填 demo/123456 → 自动点 Sign in
|
||||
# 若 input 为空 → 等用户输入完成后再自动点 Sign in
|
||||
auto_clicked = _try_auto_sign_in(page, wait_input_sec=2)
|
||||
if auto_clicked:
|
||||
logger.info(
|
||||
"platform_collector: 未登录态(URL=%s),等用户在浏览器输入账号密码",
|
||||
page.url,
|
||||
"platform_collector: ✅ 自动登录成功(预填 demo),等 URL 跳到 /app/"
|
||||
)
|
||||
elif not headless:
|
||||
# 3. input 为空:等用户输入 → 自动点 Sign in
|
||||
if _wait_for_login_inputs_filled(page, wait_sec=human_wait_timeout):
|
||||
logger.info(
|
||||
"platform_collector: ✅ 用户输入完成并点击,等 URL 跳到 /app/"
|
||||
)
|
||||
else:
|
||||
# 超时未输入 → fallback 到原来的多信号等待
|
||||
logger.warning(
|
||||
"platform_collector: 用户未输入完成,fallback 到 _wait_for_manual_login"
|
||||
)
|
||||
# 3. v1.1.19 用户反馈:不要自动 fill demo/123456,**纯等用户手工输入**。
|
||||
# 不强制 goto SPA_LOGIN_URL(避免打断根域的输入流程)
|
||||
if not headless:
|
||||
_wait_for_manual_login(page, wait_sec=human_wait_timeout)
|
||||
else:
|
||||
raise LoginTimeoutError(
|
||||
@@ -527,6 +653,15 @@ def collect_all(
|
||||
f"(请用 --show-browser 重试)"
|
||||
)
|
||||
|
||||
# 4. 登录完成 → 等 URL 真正稳定到 /app/(最多 30s,**不做任何操作**)
|
||||
# 之前 retry loop 会强制 goto home / fill login,**打断用户刚登录态**。
|
||||
if not _wait_for_url_stable(page, "/app/", max_wait_sec=30):
|
||||
logger.warning(
|
||||
"platform_collector: 登录后 30s 内 URL 未稳定到 /app/(当前 URL=%s)",
|
||||
page.url,
|
||||
)
|
||||
# 不强行 reload —— 让用户决定是否手工刷新
|
||||
|
||||
# 5. 登录完成 → 等 URL 真正稳定到 /app/(最多 30s,**不做任何操作**)
|
||||
# 之前 retry loop 会强制 goto home / fill login,**打断用户刚登录态**。
|
||||
if not _wait_for_url_stable(page, "/app/", max_wait_sec=30):
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"""
|
||||
|
||||
SKILL_SLUG = "alert-contract-expiring-hr"
|
||||
SKILL_VERSION = "1.1.21"
|
||||
SKILL_VERSION = "1.1.22"
|
||||
LOG_LOGGER_NAME = "openclaw.skill.alert_contract_expiring_hr"
|
||||
PLATFORM_KIT_MIN_VERSION = "1.2.11"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user