From c0951282183e863c05b94b451d49f9a19f428633 Mon Sep 17 00:00:00 2001 From: Ljy-0827 Date: Thu, 23 Jul 2026 21:07:46 +0800 Subject: [PATCH] fix(extension): CI problem fixed, tab logic fixed --- .../src/tools/__tests__/human-loop.test.ts | 20 ++++-- apps/extension/src/tools/human-loop.ts | 70 ++++++++++--------- crates/bsk-cli/src/cli/human_loop.rs | 2 +- .../i18n/src/locales/en-US/extension.json | 2 +- .../i18n/src/locales/zh-CN/extension.json | 2 +- 5 files changed, 56 insertions(+), 40 deletions(-) diff --git a/apps/extension/src/tools/__tests__/human-loop.test.ts b/apps/extension/src/tools/__tests__/human-loop.test.ts index a1976bf..9f6e9c7 100644 --- a/apps/extension/src/tools/__tests__/human-loop.test.ts +++ b/apps/extension/src/tools/__tests__/human-loop.test.ts @@ -197,7 +197,7 @@ describe("handleRequestHelp", () => { expect(res).toMatchObject({ outcome: "completed", completed_by: "system", tab_id: 5 }); }); - it("keeps user control active on new tabs opened in the agent window", async () => { + it("keeps user control active on new tabs without moving completion off the primary tab", async () => { vi.useFakeTimers(); const chromeEvents = installHelpLifecycleChrome(); const sendToTab = vi.fn(async () => ({ type: "bsk-help-ack", ok: true })); @@ -246,9 +246,8 @@ describe("handleRequestHelp", () => { await vi.advanceTimersByTimeAsync(1_000); await expect(pending).resolves.toMatchObject({ - outcome: "completed", - completed_by: "system", - tab_id: 6, + outcome: "timed_out", + tab_id: 5, }); expect(sendToTab).toHaveBeenCalledWith( 6, @@ -262,12 +261,13 @@ describe("handleRequestHelp", () => { it("lets same-window tabs query active help after content-script load", async () => { const chromeEvents = installHelpLifecycleChrome(); const ac = new AbortController(); + const cdpSend = vi.fn(async () => ({ root: { nodeId: 1 }, nodeIds: [] })); const deps = baseDeps({ autoAttachLifecycle: undefined, signal: ac.signal, sendToTab: vi.fn(async () => ({ type: "bsk-help-ack", ok: true })), cdp: { - send: vi.fn(async () => ({ root: { nodeId: 1 }, nodeIds: [] })), + send: cdpSend, } as unknown as RequestHelpDeps["cdp"], }); @@ -315,6 +315,16 @@ describe("handleRequestHelp", () => { ac.abort(); await expect(pending).resolves.toMatchObject({ code: "cancelled" }); + expect(cdpSend).toHaveBeenCalledWith( + 5, + "DOM.querySelectorAll", + expect.objectContaining({ selector: "[data-bsk-help]" }), + ); + expect(cdpSend).toHaveBeenCalledWith( + 6, + "DOM.querySelectorAll", + expect.objectContaining({ selector: "[data-bsk-help]" }), + ); }); it("tags ref targets via CDP and reports them matched", async () => { diff --git a/apps/extension/src/tools/human-loop.ts b/apps/extension/src/tools/human-loop.ts index 6b8d66e..136a6e3 100644 --- a/apps/extension/src/tools/human-loop.ts +++ b/apps/extension/src/tools/human-loop.ts @@ -68,7 +68,6 @@ export interface RequestHelpDeps { interface ActiveHelpRequest { ctx: SessionContext; requestId: string; - subjectTabId: number; primaryTabId: number; overlayTabIds: Set; prompt: string; @@ -126,8 +125,8 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -function helpRequestMessage(help: ActiveHelpRequest): HelpRequestMessage { - const isPrimaryTab = help.subjectTabId === help.primaryTabId; +function helpRequestMessage(help: ActiveHelpRequest, tabId: number): HelpRequestMessage { + const isPrimaryTab = tabId === help.primaryTabId; return { type: HELP_REQUEST, requestId: help.requestId, @@ -277,10 +276,10 @@ async function resolveHelpTargets( } async function refreshHelpTargets(help: ActiveHelpRequest): Promise { - await clearRefTags(help.deps.cdp, help.subjectTabId); + await clearRefTags(help.deps.cdp, help.primaryTabId); const { selectors, resolvedTargets } = await resolveHelpTargets( help.ctx, - help.subjectTabId, + help.primaryTabId, help.targets, help.deps, ); @@ -289,14 +288,18 @@ async function refreshHelpTargets(help: ActiveHelpRequest): Promise { } async function cleanupHelp(help: ActiveHelpRequest): Promise { - await clearRefTags(help.deps.cdp, help.subjectTabId); if (help.deps.notifications) { await help.deps.notifications.clear(help.notificationId).catch(() => {}); } - const tabsToCancel = new Set([help.subjectTabId, ...help.overlayTabIds]); + const tabsToCancel = new Set([help.primaryTabId, ...help.overlayTabIds]); await Promise.all( [...tabsToCancel].map((tabId) => - help.deps.sendToTab(tabId, { type: HELP_CANCEL, requestId: help.requestId }).catch(() => {}), + Promise.all([ + clearRefTags(help.deps.cdp, tabId), + help.deps + .sendToTab(tabId, { type: HELP_CANCEL, requestId: help.requestId }) + .catch(() => {}), + ]), ), ); } @@ -312,15 +315,15 @@ function finishHelp( clearTimeout(help.timeoutTimer); if (help.completionTimer) clearInterval(help.completionTimer); if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler); - clearRearmTimer(help.subjectTabId); + clearRearmTimer(help.primaryTabId); for (const tabId of help.overlayTabIds) clearRearmTimer(tabId); if (notifyContent) void cleanupHelp(help); help.resolve(value); } -function findHelpBySubjectTabId(tabId: number): ActiveHelpRequest | null { +function findHelpByKnownTabId(tabId: number): ActiveHelpRequest | null { for (const help of activeHelpRequests.values()) { - if (!help.settled && (help.subjectTabId === tabId || help.overlayTabIds.has(tabId))) { + if (!help.settled && (help.primaryTabId === tabId || help.overlayTabIds.has(tabId))) { return help; } } @@ -331,7 +334,7 @@ async function findHelpForTab( tabId: number, deps: RequestHelpDeps, ): Promise { - const direct = findHelpBySubjectTabId(tabId); + const direct = findHelpByKnownTabId(tabId); if (direct) return direct; try { @@ -356,30 +359,37 @@ function clearRearmTimer(tabId: number): void { async function sendCurrentHelpOverlay(help: ActiveHelpRequest): Promise { const legacyFinish = await sendHelpRequestWithAck( - help.subjectTabId, - helpRequestMessage(help), + help.primaryTabId, + helpRequestMessage(help, help.primaryTabId), help.deps, ); - help.overlayTabIds.add(help.subjectTabId); + help.overlayTabIds.add(help.primaryTabId); return legacyFinish; } async function refreshAndSendHelpOverlay( help: ActiveHelpRequest, + tabId: number, ): Promise { - await refreshHelpTargets(help); - return sendCurrentHelpOverlay(help); + if (tabId === help.primaryTabId) await refreshHelpTargets(help); + const legacyFinish = await sendHelpRequestWithAck( + tabId, + helpRequestMessage(help, tabId), + help.deps, + ); + help.overlayTabIds.add(tabId); + return legacyFinish; } -async function rearmHelp(help: ActiveHelpRequest): Promise { +async function rearmHelp(help: ActiveHelpRequest, tabId: number): Promise { for (let attempt = 0; attempt < HELP_REARM_MAX_ATTEMPTS; attempt += 1) { try { - const legacyFinish = await refreshAndSendHelpOverlay(help); + const legacyFinish = await refreshAndSendHelpOverlay(help, tabId); if (legacyFinish && !help.settled) { finishHelp(help, { outcome: legacyFinish.outcome, ...(legacyFinish.note ? { note: legacyFinish.note } : {}), - tab_id: help.subjectTabId, + tab_id: help.primaryTabId, resolved_targets: help.resolvedTargets, }); } @@ -401,8 +411,7 @@ function scheduleRearmForTab(tabId: number, deps: RequestHelpDeps): void { void (async () => { const help = await findHelpForTab(tabId, deps); if (!help) return; - help.subjectTabId = tabId; - await rearmHelp(help); + await rearmHelp(help, tabId); })(); }, HELP_REARM_DEBOUNCE_MS), ); @@ -457,8 +466,7 @@ function attachHelpRuntimeListener(deps: RequestHelpDeps): () => void { sendResponse({ active: false }); return; } - help.subjectTabId = tabId; - await refreshHelpTargets(help); + if (tabId === help.primaryTabId) await refreshHelpTargets(help); help.overlayTabIds.add(tabId); sendResponse({ active: true, @@ -492,14 +500,13 @@ async function finishHelpFromContent( if (tabId !== undefined) { const match = await findHelpForTab(tabId, deps); if (match !== help) return; - help.subjectTabId = tabId; } finishHelp( help, { outcome: message.outcome, ...(message.note ? { note: message.note } : {}), - tab_id: help.subjectTabId, + tab_id: help.primaryTabId, resolved_targets: help.resolvedTargets, }, false, @@ -551,7 +558,7 @@ async function evaluateCompletionCondition( if (condition.url_contains || condition.url_matches) { let url = ""; try { - const tab = await help.deps.tabsApi.get(help.subjectTabId); + const tab = await help.deps.tabsApi.get(help.primaryTabId); url = tab.url ?? ""; } catch { return false; @@ -587,7 +594,7 @@ async function evaluateCompletionCondition( })()`; try { const result = await help.deps.cdp.send<{ result?: { value?: boolean } }>( - help.subjectTabId, + help.primaryTabId, "Runtime.evaluate", { expression, returnByValue: true }, ); @@ -639,7 +646,7 @@ function startCompletionPolling(help: ActiveHelpRequest): void { finishHelp(help, { outcome: "completed", completed_by: "system", - tab_id: help.subjectTabId, + tab_id: help.primaryTabId, resolved_targets: help.resolvedTargets, }); }; @@ -697,7 +704,7 @@ export async function handleRequestHelp( const timeoutTimer = setTimeout(() => { finishHelp(help, { outcome: "timed_out", - tab_id: help.subjectTabId, + tab_id: help.primaryTabId, resolved_targets: help.resolvedTargets, }); }, timeoutMs); @@ -705,7 +712,6 @@ export async function handleRequestHelp( const help: ActiveHelpRequest = { ctx, requestId, - subjectTabId: tabId, primaryTabId: tabId, overlayTabIds: new Set(), prompt: params.prompt, @@ -741,7 +747,7 @@ export async function handleRequestHelp( finishHelp(help, { outcome: legacyFinish.outcome, ...(legacyFinish.note ? { note: legacyFinish.note } : {}), - tab_id: help.subjectTabId, + tab_id: help.primaryTabId, resolved_targets: help.resolvedTargets, }); }) diff --git a/crates/bsk-cli/src/cli/human_loop.rs b/crates/bsk-cli/src/cli/human_loop.rs index 15377ca..deda217 100644 --- a/crates/bsk-cli/src/cli/human_loop.rs +++ b/crates/bsk-cli/src/cli/human_loop.rs @@ -77,7 +77,7 @@ pub fn dispatch(args: RequestHelpArgs, format: Format) -> Result<(), CliError> { let completion_criteria = args .completion_criteria .as_deref() - .map(|raw| serde_json::from_str::(raw)) + .map(serde_json::from_str::) .transpose() .context("parse --completion-criteria JSON")?; let params = RequestHelpParams { diff --git a/packages/i18n/src/locales/en-US/extension.json b/packages/i18n/src/locales/en-US/extension.json index 62f0fe0..b3c5663 100644 --- a/packages/i18n/src/locales/en-US/extension.json +++ b/packages/i18n/src/locales/en-US/extension.json @@ -74,7 +74,7 @@ }, "helpRequest": { "title": "Agent needs your help", - "compactStatus": "Agent is still waiting for you to finish logging in", + "compactStatus": "Agent is still waiting for you to finish this step", "noteLabel": "Optional note back to the agent", "notePlaceholder": "Add a note for the agent (optional)", "continue": "Done, return control", diff --git a/packages/i18n/src/locales/zh-CN/extension.json b/packages/i18n/src/locales/zh-CN/extension.json index 9f33bb4..7f6f509 100644 --- a/packages/i18n/src/locales/zh-CN/extension.json +++ b/packages/i18n/src/locales/zh-CN/extension.json @@ -74,7 +74,7 @@ }, "helpRequest": { "title": "Agent 需要你的帮助", - "compactStatus": "当前 Agent 仍在等待你完成登录", + "compactStatus": "当前 Agent 仍在等待你完成此步骤", "noteLabel": "给 Agent 的备注(可选)", "notePlaceholder": "给 Agent 留言(可选)", "continue": "完成并交还控制权",