fix(extension): fix early control return and preserve request-help handoff across tabs#43
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors bsk request-help so the human-handoff lifecycle is owned by the extension background (instead of a single long-lived content-script response), allowing help requests to persist across navigations, content-script remounts, and related tabs in the same Agent Window.
Changes:
- Introduces a background-owned help lifecycle (
HELP_REQUEST/ACK/FINISH/QUERY) and re-arm behavior instead of completing on navigation. - Adds optional
completion_criteriato auto-complete when explicit success conditions are met. - Updates overlay UI/i18n to support a compact “waiting” status in related tabs and updates docs/protocol/schema/CLI accordingly.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| skill/SKILL.md | Updates request-help docs (Done wording, completion criteria, legacy navigated outcome deprecation). |
| crates/bsk-cli/skill/SKILL.md | Mirrors CLI skill docs updates for request-help. |
| packages/i18n/src/locales/zh-CN/extension.json | Updates help overlay button labels and adds compact status string. |
| packages/i18n/src/locales/en-US/extension.json | Updates help overlay button labels and adds compact status string. |
| crates/bsk-protocol/src/tools/human_loop.rs | Extends protocol types: completion criteria, new completed outcome, completed_by. |
| crates/bsk-protocol/schema/tool_request_help_result.json | Updates JSON schema for new outcome and completed_by. |
| crates/bsk-protocol/schema/tool_request_help_params.json | Updates JSON schema to include completion_criteria. |
| crates/bsk-cli/src/cli/human_loop.rs | Adds --completion-criteria flag and JSON parsing into protocol type. |
| apps/extension/src/transport/types.ts | Extends extension transport types for completion criteria and new outcome fields. |
| apps/extension/src/tools/human-loop.ts | Major refactor: active help registry, re-arm across tabs/navigation, completion polling, lifecycle listeners. |
| apps/extension/src/tools/dispatcher.ts | Removes navigation watcher wiring for request-help; uses new lifecycle approach. |
| apps/extension/src/tools/tests/human-loop.test.ts | Updates/adds tests for non-navigation completion, completion criteria, same-window tab behavior, query restore. |
| apps/extension/src/lib/help-bridge.ts | Expands help bridge protocol with ACK/FINISH/QUERY and type guards. |
| apps/extension/src/lib/tests/help-bridge.test.ts | Adds coverage for new lifecycle message type guards/constants. |
| apps/extension/src/entrypoints/content.ts | Updates content script handling: ACK on request, FINISH via runtime message, QUERY restore on mount. |
| apps/extension/src/content/HelpRequestOverlay.tsx | Adds compact display mode and adjusts layout/highlight behavior accordingly. |
| apps/extension/src/content/tests/HelpRequestOverlay.test.tsx | Adds tests for compact rendering and updated layout behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+497
to
+506
| finishHelp( | ||
| help, | ||
| { | ||
| outcome: message.outcome, | ||
| ...(message.note ? { note: message.note } : {}), | ||
| tab_id: help.subjectTabId, | ||
| resolved_targets: help.resolvedTargets, | ||
| }, | ||
| false, | ||
| ); |
Comment on lines
+454
to
+474
| void (async () => { | ||
| const help = await findHelpForTab(tabId, deps); | ||
| if (!help) { | ||
| sendResponse({ active: false }); | ||
| return; | ||
| } | ||
| help.subjectTabId = tabId; | ||
| await refreshHelpTargets(help); | ||
| help.overlayTabIds.add(tabId); | ||
| sendResponse({ | ||
| active: true, | ||
| request: { | ||
| requestId: help.requestId, | ||
| prompt: help.prompt, | ||
| ...(help.title ? { title: help.title } : {}), | ||
| displayMode: tabId === help.primaryTabId ? "full" : "compact", | ||
| selectors: tabId === help.primaryTabId ? help.selectors : [], | ||
| timeoutMs: help.timeoutMs, | ||
| }, | ||
| }); | ||
| })(); |
Comment on lines
+401
to
+406
| void (async () => { | ||
| const help = await findHelpForTab(tabId, deps); | ||
| if (!help) return; | ||
| help.subjectTabId = tabId; | ||
| await rearmHelp(help); | ||
| })(); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
apps/extension/src/tools/human-loop.ts:513
finishHelpFromContentcallsfinishHelp(..., notifyContent=false), which skipscleanupHelp. That leaves OS notifications uncleared, ref tags not removed, and (critically) compact overlays in other same-window tabs may remain visible after the user clicks Done/Cancel in the primary tab. The help request should still broadcast HELP_CANCEL + cleanup when completion comes from content.
tab_id: help.primaryTabId,
resolved_targets: help.resolvedTargets,
},
false,
);
Comment on lines
+286
to
+291
| overlays.clearAgentHelpRequest(requestId); | ||
| renderOverlay(); | ||
| await chrome.runtime.sendMessage(msg).catch((err) => { | ||
| console.debug("[bsk overlay] help finish failed", err); | ||
| }); | ||
| } |
added 4 commits
July 24, 2026 14:07
Ljy-0827
force-pushed
the
fix/early-control-return
branch
from
July 24, 2026 06:07
2bba929 to
959bfac
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
bsk request-help用来在验证码、密码输入、OTP、登录恢复、确认操作等必须由人处理的步骤中暂停 Agent 自动化,并把页面控制权交给用户。之前的实现会把任意页面导航视为人工处理结束信号,并返回outcome: navigated。这会导致用户还没完成操作,Agent 就提前恢复控制。页面刷新、导航、SPA 路由变化、content script 重新挂载都不应被当成完成信号。人工接管只应在以下情况结束:
解决方案
本 PR
request_help从“一次长生命周期 content-script 响应”改为由 background 持有的人工接管生命周期。background 持有 help 生命周期
扩展 background 现在维护 active help registry,用于保存未完成的
request_help。content-script 协议拆成了更明确的生命周期消息:HELP_REQUEST:background 请求 tab 展示 help overlay。HELP_ACK:content script 确认 overlay 已展示。HELP_FINISH:content script 上报用户点击完成或取消。HELP_QUERY:新加载的 content script 查询当前是否有 active help request 需要恢复。这样
request_help不再绑定某一次tabs.sendMessage的响应端口,也不再绑定某一个 document 的生命周期。页面变化只触发 re-arm,不再结束 request_help
移除了旧的
watchTabNavigation -> outcome: navigated行为。现在导航、刷新、tab 激活、content script 重新挂载只会重新投递当前 active help request,而不会 resolve 原始 RPC。
新的不变量是:
同时修复了一个 re-arm 相关 bug:content 收到相同
requestId的HELP_REQUEST时,之前会把当前 overlay 当成“被替换的旧请求”,并发送cancelled。现在同一个 request 的 re-arm 只会更新 overlay,不会取消人工接管。re-arm 时重新 resolve/highlight targets
ActiveHelpRequest现在保存原始targets,而不仅仅保存 resolve 后的 selectors。初次展示和每次 re-arm 都会重新执行 target resolution:
data-bsk-help。resolved_targets会记录 unmatched。这样在验证码刷新、页面跳转、content script 重新加载后,高亮会尽量恢复;即使无法恢复高亮,也不会因此把控制权还给 Agent。
同一 Agent Window 内的新 tab 仍保持人工接管
人工接管期间,用户可能会在当前流程中打开新 tab,例如右键“在新标签页中打开”、进入忘记密码或登录恢复相关页面等。现在 active help 不再只绑定最初的单个 tab,而是识别同一个 Agent Window 内的相关 tab。新 tab 激活或 content script 加载时,会查询并继承当前 active help 状态,避免 Agent 在用户仍在处理同一人工流程时提前接管新 tab。
为避免完整 help overlay 在用户打开的每个 tab 中重复出现,UI 区分了主任务 tab 和同窗口相关 tab:
轻量提示不提供完成或取消按钮,避免用户在错误 tab 误交还控制权。完成/取消仍需要在主任务 tab 的完整 overlay 中操作。
Problem
bsk request-helpis used to pause Agent automation and hand browser control to the user for steps that must be handled manually, such as captchas, password entry, OTP, login recovery, or confirmation flows. The previous implementation treated arbitrary page navigation as the end of the human step and returnedoutcome: navigated. This allowed the Agent to resume control before the user had actually finished.Page refreshes, navigations, SPA route changes, content-script remounts, and tab switches should not be treated as completion signals. Human handoff should only end when one of the following happens:
Solution
This PR changes
request_helpfrom a single long-lived content-script response into a background-owned human handoff lifecycle.Background-owned help lifecycle
The extension background now maintains an active help registry for unfinished
request_helpcalls. The content-script protocol is split into clearer lifecycle messages:HELP_REQUEST: background asks a tab to show the help overlay.HELP_ACK: content script confirms the overlay is shown.HELP_FINISH: content script reports that the user clicked done or cancel.HELP_QUERY: a newly loaded content script asks whether there is an active help request to restore.This means
request_helpis no longer tied to onetabs.sendMessageresponse port or one document lifecycle.Background-owned help lifecycle
The extension background now maintains an active help registry for unfinished
request_helpcalls. The content-script protocol is split into clearer lifecycle messages:HELP_REQUEST: background asks a tab to show the help overlay.HELP_ACK: content script confirms the overlay is shown.HELP_FINISH: content script reports that the user clicked done or cancel.HELP_QUERY: a newly loaded content script asks whether there is an active help request to restore.This means
request_helpis no longer tied to onetabs.sendMessageresponse port or one document lifecycle.Page changes re-arm instead of completing request_help
The old
watchTabNavigation -> outcome: navigatedbehavior was removed.Navigation, refresh, tab activation, and content-script remount now only re-deliver the current active help request. They no longer resolve the original RPC.
The new invariant is:
This also fixes a re-arm bug where receiving the same
requestIdagain in content previously treated the current overlay as a replaced old request and sentcancelled. Re-arming the same request now updates the overlay without cancelling the human handoff.Re-resolve and re-highlight targets on re-arm
ActiveHelpRequestnow stores the originaltargets, not only the resolved selectors.Initial display and every re-arm run target resolution again:
data-bsk-help.resolved_targetsrecords it as unmatched.This lets highlights recover after captcha refreshes, navigations, and content-script reloads. Even when highlighting cannot be restored, control is not returned to the Agent.
New tabs in the same Agent Window stay in human handoff
During human handoff, the user may open new tabs as part of the same flow, such as opening a link in a new tab or entering a forgot-password/login-recovery page.
Active help is no longer bound only to the initial tab. It now recognizes related tabs in the same Agent Window. When a new tab is activated or its content script loads, it can query and inherit the current active help state, preventing the Agent from taking over while the user is still handling the same manual flow.
To avoid showing the full help overlay in every tab the user opens, the UI distinguishes the primary task tab from related tabs in the same window:
Agent is still waiting for you to finish this step.The lightweight prompt has no done or cancel buttons, so users cannot accidentally return control from the wrong tab. Done/cancel actions remain available only in the primary task tab’s full overlay.