Skip to content

fix(extension): fix early control return and preserve request-help handoff across tabs#43

Merged
iuyo5678 merged 4 commits into
mainfrom
fix/early-control-return
Jul 24, 2026
Merged

fix(extension): fix early control return and preserve request-help handoff across tabs#43
iuyo5678 merged 4 commits into
mainfrom
fix/early-control-return

Conversation

@Ljy-0827

Copy link
Copy Markdown
Collaborator

问题

bsk request-help 用来在验证码、密码输入、OTP、登录恢复、确认操作等必须由人处理的步骤中暂停 Agent 自动化,并把页面控制权交给用户。之前的实现会把任意页面导航视为人工处理结束信号,并返回 outcome: navigated。这会导致用户还没完成操作,Agent 就提前恢复控制。

页面刷新、导航、SPA 路由变化、content script 重新挂载都不应被当成完成信号。人工接管只应在以下情况结束:

  • 用户明确点击“完成并交还控制权”;
  • 用户取消请求;
  • 请求超时或被 daemon 取消;
  • 显式成功条件证明人工处理目标已经完成。

解决方案

本 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。

新的不变量是:

active request_help 会跨页面变化持续存在,只有明确的用户/系统完成、取消、超时或 RPC abort 才会结束。

同时修复了一个 re-arm 相关 bug:content 收到相同 requestIdHELP_REQUEST 时,之前会把当前 overlay 当成“被替换的旧请求”,并发送 cancelled。现在同一个 request 的 re-arm 只会更新 overlay,不会取消人工接管。

re-arm 时重新 resolve/highlight targets

ActiveHelpRequest 现在保存原始 targets,而不仅仅保存 resolve 后的 selectors。

初次展示和每次 re-arm 都会重新执行 target resolution:

  • CSS selector 会在当前页面重新匹配。
  • snapshot ref 会通过 session ref store 重新尝试 resolve,并重新打 data-bsk-help
  • 如果页面变化后 ref 已失效,help request 仍会继续等待,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 显示完整 help overlay,包括说明、备注输入、高亮、完成/取消按钮。
  • 其他同 Agent Window 的 tab 只显示轻量提示如:“当前 Agent 仍在等待你完成”

轻量提示不提供完成或取消按钮,避免用户在错误 tab 误交还控制权。完成/取消仍需要在主任务 tab 的完整 overlay 中操作。

Problem

bsk request-help is 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 returned outcome: 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:

  • the user explicitly clicks “Done, return control”;
  • the user cancels the request;
  • the request times out or is aborted by the daemon;
  • explicit success criteria prove that the manual task has completed.

Solution

This PR changes request_help from 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_help calls. 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_help is no longer tied to one tabs.sendMessage response port or one document lifecycle.

Background-owned help lifecycle

The extension background now maintains an active help registry for unfinished request_help calls. 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_help is no longer tied to one tabs.sendMessage response port or one document lifecycle.

Page changes re-arm instead of completing request_help

The old watchTabNavigation -> outcome: navigated behavior 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:

An active request_help persists across page changes, and only explicit user/system completion, cancellation, timeout, or RPC abort can end it.

This also fixes a re-arm bug where receiving the same requestId again in content previously treated the current overlay as a replaced old request and sent cancelled. Re-arming the same request now updates the overlay without cancelling the human handoff.

Re-resolve and re-highlight targets on re-arm

ActiveHelpRequest now stores the original targets, not only the resolved selectors.

Initial display and every re-arm run target resolution again:

  • CSS selectors are matched again on the current page.
  • snapshot refs are resolved again through the session ref store and re-tagged with data-bsk-help.
  • if a ref becomes invalid after page changes, the help request still continues waiting and resolved_targets records 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:

  • The primary task tab shows the full help overlay, including instructions, note input, highlights, and done/cancel buttons.
  • Other tabs in the same Agent Window show only a lightweight status prompt: 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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_criteria to 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 thread apps/extension/src/tools/human-loop.ts Outdated
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);
})();
Comment thread packages/i18n/src/locales/en-US/extension.json Outdated
Comment thread packages/i18n/src/locales/zh-CN/extension.json Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • finishHelpFromContent calls finishHelp(..., notifyContent=false), which skips cleanupHelp. 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);
});
}
@Ljy-0827
Ljy-0827 force-pushed the fix/early-control-return branch from 2bba929 to 959bfac Compare July 24, 2026 06:07
@iuyo5678
iuyo5678 merged commit b657ab5 into main Jul 24, 2026
4 checks passed
@iuyo5678
iuyo5678 deleted the fix/early-control-return branch July 24, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

用户接管阶段可能被页面状态变化过早结束 | User takeover may end prematurely due to page state changes

3 participants