diff --git a/SKILL.md b/SKILL.md index 8994dac..cd1b069 100644 --- a/SKILL.md +++ b/SKILL.md @@ -31,7 +31,7 @@ Gemini 页面的操作按钮(`.send-button-container` 内)通过 `aria-label | 发送 / Send | `ready` | 输入框有内容,可发送 | | 停止 / Stop | `loading` | 已发送,正在生成回答 | -可通过 `GeminiOps.getStatus()` 获取当前状态,通过 `GeminiOps.waitForComplete()` 轮询等待生成完毕。 +可通过 `GeminiOps.getStatus()` 获取当前状态,通过 `GeminiOps.pollStatus()` 分段轮询等待生成完毕。 ### A. 文本问答 1. 打开 `https://gemini.google.com`。 @@ -39,7 +39,7 @@ Gemini 页面的操作按钮(`.send-button-container` 内)通过 `aria-label 3. 新建会话:`click('newChatBtn')`,确保干净上下文。 4. 选择最强可用模型(优先 Gemini 3.1 Pro)。 5. 将用户问题原样输入并发送。 -6. 调用 `waitForComplete()` 等待状态从 `loading` 变回 `idle`。 +6. **分段轮询等待**(见下方"CDP 保活轮询策略")。 7. 等待完整输出,提炼后回传(必要时附原文要点)。 ### B. 生图流程 @@ -48,12 +48,28 @@ Gemini 页面的操作按钮(`.send-button-container` 内)通过 `aria-label 3. 选择最强可用模型(优先 Gemini 3.1 Pro)。 4. 将用户提示词原样输入。 5. 发送后立即通知用户:正在绘图中。 -6. 调用 `waitForComplete()` 等待生成完毕(生图默认超时 120s)。 +6. **分段轮询等待**(见下方"CDP 保活轮询策略",生图超时上限 120s)。 7. 结果出现后: - 优先用"下载原图"按钮获取原图。 - 若无下载按钮或失败,可对图片右键另存(通常是标清图)。 8. 把图片返回用户;若有多张,按顺序全部回传。 +## CDP 保活轮询策略 + +> **核心原则**:绝不在页面内做长时间 Promise 等待。每次 `evaluate` 必须毫秒级返回,由调用端控制循环。 + +生图/问答发送后,按以下方式等待结果: + +1. 每隔 **8~10 秒**调用一次 `GeminiOps.pollStatus()`。 +2. 该函数立即返回 `{status, label, pageVisible, ts}`。 +3. 调用端根据 `status` 判断: + - `loading` → 继续等待,累计已耗时。 + - `idle` → 生成完毕,进入结果获取阶段。 + - `unknown` → 页面可能异常,做一次 snapshot 兜底排查。 +4. 累计耗时超过上限(文本 60s / 生图 120s)→ 走超时回退逻辑。 + +**为什么这样做**:OpenClaw 通过 CDP(Chrome DevTools Protocol)WebSocket 控制浏览器。若长时间(>30s)无消息往来,网关/代理可能判定连接空闲并断开。分段短轮询保证 CDP 通道始终有心跳流量。 + ## 失败回退 1. 元素定位失败:刷新页面后重试一次。 diff --git a/references/gemini-flow.md b/references/gemini-flow.md index 6b7f83e..208cb96 100644 --- a/references/gemini-flow.md +++ b/references/gemini-flow.md @@ -26,7 +26,14 @@ 使用方式: - `GeminiOps.getStatus()` → 返回 `{status: 'idle'|'ready'|'loading', label, disabled}` -- `GeminiOps.waitForComplete(timeout, interval)` → 返回 Promise,状态脱离 `loading` 后 resolve +- `GeminiOps.pollStatus()` → 返回 `{status, label, pageVisible, ts}`,**毫秒级返回**,供调用端分段轮询 + +### CDP 保活轮询(重要) + +**禁止**在页面内做长 Promise 等待(旧版 `waitForComplete` 已移除)。 + +正确做法:调用端每 8~10 秒 evaluate 一次 `GeminiOps.pollStatus()`,自行累计耗时并判断超时。 +这确保 CDP WebSocket 通道持续有消息流量,避免被网关判定空闲而断开连接。 ## 4) 生图结果获取 diff --git a/scripts/gemini_ui_shortcuts.js b/scripts/gemini_ui_shortcuts.js index 2553269..b7d6e25 100644 --- a/scripts/gemini_ui_shortcuts.js +++ b/scripts/gemini_ui_shortcuts.js @@ -81,25 +81,15 @@ return {status:'idle',label,disabled}; } - function waitForComplete(timeout,interval){ - timeout=timeout||120000; - interval=interval||2000; - return new Promise(function(resolve){ - var elapsed=0; - var timer=setInterval(function(){ - elapsed+=interval; - var s=getStatus(); - if(s.status!=='loading'){ - clearInterval(timer); - resolve({ok:true,status:s.status,elapsed}); - return; - } - if(elapsed>=timeout){ - clearInterval(timer); - resolve({ok:false,status:'timeout',elapsed}); - } - },interval); - }); + /* ── 保活式轮询 ── + * 不在页面内做长 Promise 等待(会导致 CDP 连接因长时间无消息被网关判定空闲断开)。 + * 改为:调用端每 8-10s evaluate 一次 GeminiOps.pollStatus(),立即拿到结果。 + * 调用端自行累计耗时并判断超时。 + */ + function pollStatus(){ + var s=getStatus(); + // 顺便返回页面可见性,帮助调用端判断 tab 是否还活着 + return {status:s.status, label:s.label, pageVisible:!document.hidden, ts:Date.now()}; } function probe(){ @@ -113,5 +103,5 @@ }; } - window.GeminiOps = {probe, click, fillPrompt, getStatus, waitForComplete, selectors:S, version:'0.3.0'}; + window.GeminiOps = {probe, click, fillPrompt, getStatus, pollStatus, selectors:S, version:'0.4.0'}; })();