diff --git a/apps/extension/src/content/HelpRequestOverlay.tsx b/apps/extension/src/content/HelpRequestOverlay.tsx index b72ce44..e3f867d 100644 --- a/apps/extension/src/content/HelpRequestOverlay.tsx +++ b/apps/extension/src/content/HelpRequestOverlay.tsx @@ -15,6 +15,8 @@ export interface HelpRequestData { prompt: string; /** Custom overlay title; falls back to i18n when omitted. */ title?: string; + /** Full task UI on the subject tab; compact status UI on related tabs. */ + displayMode?: "full" | "compact"; /** CSS selectors to scroll to + flash-highlight. */ selectors: string[]; onContinue: (note: string) => void; @@ -174,6 +176,8 @@ function measure(selectors: string[]): Box[] { export function HelpRequestOverlay({ request }: Props) { const { t } = useTranslation("extension"); + const isCompact = request?.displayMode === "compact"; + const effectiveSelectors = isCompact ? [] : (request?.selectors ?? []); const [note, setNote] = useState(""); const [collapsed, setCollapsed] = useState(false); const [boxes, setBoxes] = useState([]); @@ -207,8 +211,9 @@ export function HelpRequestOverlay({ request }: Props) { // Scroll the first matched target into view once per distinct request id. useLayoutEffect(() => { if (!request) return; + if (isCompact) return; if (lastScrolledIdRef.current === request.id) return; - for (const sel of request.selectors) { + for (const sel of effectiveSelectors) { const el = querySelectorSafe(sel); if (el) { lastScrolledIdRef.current = request.id; @@ -216,7 +221,7 @@ export function HelpRequestOverlay({ request }: Props) { break; } } - }, [request]); + }, [request, isCompact, effectiveSelectors]); // Keep highlight boxes aligned with the page as it scrolls / resizes. useEffect(() => { @@ -225,7 +230,7 @@ export function HelpRequestOverlay({ request }: Props) { return; } const update = () => { - const nextBoxes = measure(request.selectors); + const nextBoxes = measure(effectiveSelectors); setBoxes((prev) => (boxesEqual(prev, nextBoxes) ? prev : nextBoxes)); }; update(); @@ -237,7 +242,7 @@ export function HelpRequestOverlay({ request }: Props) { window.removeEventListener("resize", update); window.clearInterval(interval); }; - }, [request]); + }, [request, effectiveSelectors]); const onHeaderPointerDown = useCallback( (e: ReactPointerEvent) => { @@ -294,8 +299,12 @@ export function HelpRequestOverlay({ request }: Props) { setPanelPos(null); return; } + if (isCompact) { + setPanelPos(null); + return; + } if (userMovedRef.current) return; - const measured = boxes.length > 0 ? boxes : measure(request.selectors); + const measured = boxes.length > 0 ? boxes : measure(effectiveSelectors); const anchor = unionBox(measured); const banner = bannerRef.current; if (!anchor || !banner) { @@ -319,7 +328,7 @@ export function HelpRequestOverlay({ request }: Props) { } const nextPos = placePanel(anchor, panelW, panelH, placementRef.current.placement); setPanelPos((prev) => (panelPosEqual(prev, nextPos) ? prev : nextPos)); - }, [request, boxes, collapsed]); + }, [request, boxes, collapsed, isCompact, effectiveSelectors]); if (!request) return null; @@ -364,6 +373,13 @@ export function HelpRequestOverlay({ request }: Props) { padding: 12px; } + .bsk-help-banner[data-display-mode="compact"] { + width: min(360px, calc(100vw - ${VIEWPORT_MARGIN * 2}px)); + padding: 12px 14px; + gap: 0; + border-radius: 12px; + } + .bsk-help-drag-strip { position: absolute; top: 6px; @@ -409,6 +425,10 @@ export function HelpRequestOverlay({ request }: Props) { gap: 10px; } + .bsk-help-banner[data-display-mode="compact"] .bsk-help-header { + gap: 8px; + } + .bsk-help-title { flex: 1 1 auto; min-width: 0; @@ -419,6 +439,17 @@ export function HelpRequestOverlay({ request }: Props) { color: #111; } + .bsk-help-compact-title { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 13px; + font-weight: 500; + color: #374151; + } + .bsk-help-banner[data-collapsed="true"] .bsk-help-title { white-space: nowrap; } @@ -562,6 +593,17 @@ export function HelpRequestOverlay({ request }: Props) { max-width: 180px; } + .bsk-help-banner[data-display-mode="compact"] .bsk-help-header-actions { + display: flex; + max-width: none; + } + + .bsk-help-banner[data-display-mode="compact"] .bsk-help-header-actions .bsk-help-btn-cancel, + .bsk-help-banner[data-display-mode="compact"] .bsk-help-header-actions .bsk-help-btn-continue { + padding: 6px 10px; + font-size: 12px; + } + .bsk-help-footer-actions .bsk-help-btn-cancel { padding: 8px 16px; } @@ -584,29 +626,31 @@ export function HelpRequestOverlay({ request }: Props) { } `} - {boxes.map((b, i) => ( -
- ))} + {!isCompact && + boxes.map((b, i) => ( +
+ ))}
-
- -
+ {!isCompact && ( +
+ +
+ )}
browser-skill - {request.title ?? t("helpRequest.title")} -
+ {isCompact ? ( + {t("helpRequest.compactStatus")} + ) : ( + {request.title ?? t("helpRequest.title")} + )} + {!isCompact && ( +
+ + +
+ )} + {!isCompact && ( - -
- + )}
-
-
-
-

{request.prompt}

- setNote(e.target.value)} - /> -
- - + onChange={(e) => setNote(e.target.value)} + /> +
+ + +
-
+ )}
); diff --git a/apps/extension/src/content/__tests__/HelpRequestOverlay.test.tsx b/apps/extension/src/content/__tests__/HelpRequestOverlay.test.tsx index 1a1db9a..98eba65 100644 --- a/apps/extension/src/content/__tests__/HelpRequestOverlay.test.tsx +++ b/apps/extension/src/content/__tests__/HelpRequestOverlay.test.tsx @@ -38,6 +38,31 @@ describe("HelpRequestOverlay", () => { expect(screen.getByText("Please complete the captcha")).toBeTruthy(); }); + it("renders a compact status without full request controls", () => { + const el = document.createElement("div"); + el.id = "login"; + el.getBoundingClientRect = () => + ({ top: 10, left: 10, width: 100, height: 40, right: 110, bottom: 50 }) as DOMRect; + document.body.append(el); + + const { container } = renderOverlay( + baseRequest({ + displayMode: "compact", + selectors: ["#login"], + }), + ); + + expect(screen.getByText(i18n.t("helpRequest.compactStatus", { ns: "extension" }))).toBeTruthy(); + expect(screen.queryByText("Please complete the captcha")).toBeNull(); + expect(screen.queryByRole("textbox")).toBeNull(); + expect(screen.queryByLabelText(i18n.t("helpRequest.collapse", { ns: "extension" }))).toBeNull(); + expect(container.querySelector("[data-slot='help-continue-button']")).toBeNull(); + expect(container.querySelector("[data-slot='help-cancel-button']")).toBeNull(); + expect(document.querySelectorAll("[data-slot='help-highlight']").length).toBe(0); + + el.remove(); + }); + it("renders a custom title when provided", () => { renderOverlay(baseRequest({ title: "Verify your identity" })); expect(screen.getByText("Verify your identity")).toBeTruthy(); diff --git a/apps/extension/src/entrypoints/content.ts b/apps/extension/src/entrypoints/content.ts index edcf988..d92baa8 100644 --- a/apps/extension/src/entrypoints/content.ts +++ b/apps/extension/src/entrypoints/content.ts @@ -233,6 +233,7 @@ export default defineContentScript({ id: helpMsg.requestId, prompt: helpMsg.prompt, ...(helpMsg.title ? { title: helpMsg.title } : {}), + ...(helpMsg.displayMode ? { displayMode: helpMsg.displayMode } : {}), selectors: helpMsg.selectors, onContinue: (note: string) => void sendHelpFinish(helpMsg.requestId, "continued", note.trim() ? note : undefined), @@ -294,6 +295,7 @@ export default defineContentScript({ id: helpMsg.requestId, prompt: helpMsg.prompt, ...(helpMsg.title ? { title: helpMsg.title } : {}), + ...(helpMsg.displayMode ? { displayMode: helpMsg.displayMode } : {}), selectors: helpMsg.selectors, onContinue: (note: string) => void sendHelpFinish(helpMsg.requestId, "continued", note.trim() ? note : undefined), diff --git a/apps/extension/src/lib/__tests__/help-bridge.test.ts b/apps/extension/src/lib/__tests__/help-bridge.test.ts index 7db48cb..50b4b81 100644 --- a/apps/extension/src/lib/__tests__/help-bridge.test.ts +++ b/apps/extension/src/lib/__tests__/help-bridge.test.ts @@ -30,6 +30,7 @@ describe("help-bridge", () => { type: HELP_REQUEST, requestId: "r1", prompt: "log in", + displayMode: "compact", selectors: ["#login"], timeoutMs: 1000, }), diff --git a/apps/extension/src/lib/help-bridge.ts b/apps/extension/src/lib/help-bridge.ts index c0bee7d..b73b45c 100644 --- a/apps/extension/src/lib/help-bridge.ts +++ b/apps/extension/src/lib/help-bridge.ts @@ -22,6 +22,8 @@ export interface HelpRequestMessage { prompt: string; /** Custom overlay title; omitted when the extension should use its default. */ title?: string; + /** Full task UI on the subject tab; compact status UI on related tabs. */ + displayMode?: "full" | "compact"; /** CSS selectors to scroll to + flash-highlight (may be empty). */ selectors: string[]; timeoutMs: number; @@ -69,6 +71,7 @@ export function isHelpRequestMessage(msg: unknown): msg is HelpRequestMessage { typeof m.requestId === "string" && typeof m.prompt === "string" && (m.title === undefined || typeof m.title === "string") && + (m.displayMode === undefined || m.displayMode === "full" || m.displayMode === "compact") && Array.isArray(m.selectors) && m.selectors.every((selector) => typeof selector === "string") && typeof m.timeoutMs === "number" diff --git a/apps/extension/src/tools/__tests__/human-loop.test.ts b/apps/extension/src/tools/__tests__/human-loop.test.ts index b9b1c55..a1976bf 100644 --- a/apps/extension/src/tools/__tests__/human-loop.test.ts +++ b/apps/extension/src/tools/__tests__/human-loop.test.ts @@ -1,7 +1,53 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { SessionManager } from "@/session-manager/manager"; import type { RequestHelpParams } from "@/transport/types"; -import { handleRequestHelp, type RequestHelpDeps } from "../human-loop"; +import { handleRequestHelp, type RequestHelpDeps, resetHelpLifecycleForTests } from "../human-loop"; + +function chromeEvent unknown>() { + const listeners = new Set(); + return { + addListener: vi.fn((listener: T) => { + listeners.add(listener); + }), + removeListener: vi.fn((listener: T) => { + listeners.delete(listener); + }), + emit: (...args: Parameters) => { + for (const listener of [...listeners]) listener(...args); + }, + }; +} + +function installHelpLifecycleChrome() { + const runtimeOnMessage = + chromeEvent< + ( + message: unknown, + sender: chrome.runtime.MessageSender, + sendResponse: (response: unknown) => void, + ) => unknown + >(); + const tabsOnActivated = chromeEvent<(activeInfo: chrome.tabs.TabActiveInfo) => unknown>(); + const tabsOnCreated = chromeEvent<(tab: chrome.tabs.Tab) => unknown>(); + const tabsOnUpdated = + chromeEvent<(tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => unknown>(); + const webNavigationOnCompleted = + chromeEvent<(details: chrome.webNavigation.WebNavigationFramedCallbackDetails) => unknown>(); + + vi.stubGlobal("chrome", { + runtime: { onMessage: runtimeOnMessage }, + tabs: { onActivated: tabsOnActivated, onCreated: tabsOnCreated, onUpdated: tabsOnUpdated }, + webNavigation: { onCompleted: webNavigationOnCompleted }, + }); + + return { + runtimeOnMessage, + tabsOnActivated, + tabsOnCreated, + tabsOnUpdated, + webNavigationOnCompleted, + }; +} function fakeManager(sessionId: string, agentWindowId: number, tabId: number) { const mgr = { @@ -35,6 +81,11 @@ function baseDeps(over: Partial = {}): RequestHelpDeps { } describe("handleRequestHelp", () => { + afterEach(() => { + resetHelpLifecycleForTests(); + vi.unstubAllGlobals(); + }); + it("rejects unknown session", async () => { const res = await handleRequestHelp( fakeManager("abcd", 99, 5), @@ -146,6 +197,126 @@ 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 () => { + vi.useFakeTimers(); + const chromeEvents = installHelpLifecycleChrome(); + const sendToTab = vi.fn(async () => ({ type: "bsk-help-ack", ok: true })); + const deps = baseDeps({ + autoAttachLifecycle: undefined, + sendToTab, + tabsApi: { + get: vi.fn(async (tabId: number) => ({ + id: tabId, + windowId: 99, + active: tabId === 6, + url: tabId === 6 ? "https://app.example/reset/success" : "https://app.example/login", + })) as never, + query: vi.fn(async () => [{ id: 5, windowId: 99, active: true }] as never), + }, + }); + + try { + const pending = handleRequestHelp( + fakeManager("abcd", 99, 5), + baseParams({ + tab_id: 5, + timeout_ms: 1_000, + completion_criteria: { + any: [{ url_contains: "/reset/success" }], + stable_for_ms: 0, + }, + }), + deps, + ); + await vi.waitFor(() => expect(sendToTab).toHaveBeenCalledWith(5, expect.anything())); + + chromeEvents.tabsOnActivated.emit({ tabId: 6, windowId: 99 }); + await vi.advanceTimersByTimeAsync(200); + + await vi.waitFor(() => + expect(sendToTab).toHaveBeenCalledWith( + 6, + expect.objectContaining({ + type: "bsk-help-request", + displayMode: "compact", + selectors: [], + }), + ), + ); + + await vi.advanceTimersByTimeAsync(1_000); + await expect(pending).resolves.toMatchObject({ + outcome: "completed", + completed_by: "system", + tab_id: 6, + }); + expect(sendToTab).toHaveBeenCalledWith( + 6, + expect.objectContaining({ type: "bsk-help-cancel" }), + ); + } finally { + vi.useRealTimers(); + } + }); + + it("lets same-window tabs query active help after content-script load", async () => { + const chromeEvents = installHelpLifecycleChrome(); + const ac = new AbortController(); + 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: [] })), + } as unknown as RequestHelpDeps["cdp"], + }); + + const pending = handleRequestHelp( + fakeManager("abcd", 99, 5), + baseParams({ tab_id: 5, timeout_ms: 60_000 }), + deps, + ); + await vi.waitFor(() => expect(deps.sendToTab).toHaveBeenCalledWith(5, expect.anything())); + + const sameWindowResponse = vi.fn(); + chromeEvents.runtimeOnMessage.emit( + { type: "bsk-help-query" }, + { tab: { id: 6 } as chrome.tabs.Tab }, + sameWindowResponse, + ); + await vi.waitFor(() => + expect(sameWindowResponse).toHaveBeenCalledWith({ + active: true, + request: expect.objectContaining({ + requestId: expect.any(String), + prompt: "log in", + displayMode: "compact", + selectors: [], + }), + }), + ); + + const subjectResponse = vi.fn(); + chromeEvents.runtimeOnMessage.emit( + { type: "bsk-help-query" }, + { tab: { id: 5 } as chrome.tabs.Tab }, + subjectResponse, + ); + await vi.waitFor(() => + expect(subjectResponse).toHaveBeenCalledWith({ + active: true, + request: expect.objectContaining({ + requestId: expect.any(String), + prompt: "log in", + displayMode: "full", + }), + }), + ); + + ac.abort(); + await expect(pending).resolves.toMatchObject({ code: "cancelled" }); + }); + it("tags ref targets via CDP and reports them matched", async () => { const mgr = { get: (id: string) => diff --git a/apps/extension/src/tools/human-loop.ts b/apps/extension/src/tools/human-loop.ts index 1c20dec..6b8d66e 100644 --- a/apps/extension/src/tools/human-loop.ts +++ b/apps/extension/src/tools/human-loop.ts @@ -66,11 +66,11 @@ export interface RequestHelpDeps { } interface ActiveHelpRequest { - sessionId: string; ctx: SessionContext; requestId: string; - tabId: number; - agentWindowId: number; + subjectTabId: number; + primaryTabId: number; + overlayTabIds: Set; prompt: string; title?: string; targets: HelpTarget[]; @@ -127,12 +127,14 @@ function sleep(ms: number): Promise { } function helpRequestMessage(help: ActiveHelpRequest): HelpRequestMessage { + const isPrimaryTab = help.subjectTabId === help.primaryTabId; return { type: HELP_REQUEST, requestId: help.requestId, prompt: help.prompt, ...(help.title ? { title: help.title } : {}), - selectors: help.selectors, + displayMode: isPrimaryTab ? "full" : "compact", + selectors: isPrimaryTab ? help.selectors : [], timeoutMs: help.timeoutMs, }; } @@ -275,10 +277,10 @@ async function resolveHelpTargets( } async function refreshHelpTargets(help: ActiveHelpRequest): Promise { - await clearRefTags(help.deps.cdp, help.tabId); + await clearRefTags(help.deps.cdp, help.subjectTabId); const { selectors, resolvedTargets } = await resolveHelpTargets( help.ctx, - help.tabId, + help.subjectTabId, help.targets, help.deps, ); @@ -287,13 +289,16 @@ async function refreshHelpTargets(help: ActiveHelpRequest): Promise { } async function cleanupHelp(help: ActiveHelpRequest): Promise { - await clearRefTags(help.deps.cdp, help.tabId); + await clearRefTags(help.deps.cdp, help.subjectTabId); if (help.deps.notifications) { await help.deps.notifications.clear(help.notificationId).catch(() => {}); } - await help.deps - .sendToTab(help.tabId, { type: HELP_CANCEL, requestId: help.requestId }) - .catch(() => {}); + const tabsToCancel = new Set([help.subjectTabId, ...help.overlayTabIds]); + await Promise.all( + [...tabsToCancel].map((tabId) => + help.deps.sendToTab(tabId, { type: HELP_CANCEL, requestId: help.requestId }).catch(() => {}), + ), + ); } function finishHelp( @@ -307,14 +312,17 @@ function finishHelp( clearTimeout(help.timeoutTimer); if (help.completionTimer) clearInterval(help.completionTimer); if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler); - clearRearmTimer(help.tabId); + clearRearmTimer(help.subjectTabId); + for (const tabId of help.overlayTabIds) clearRearmTimer(tabId); if (notifyContent) void cleanupHelp(help); help.resolve(value); } -function findHelpByTabId(tabId: number): ActiveHelpRequest | null { +function findHelpBySubjectTabId(tabId: number): ActiveHelpRequest | null { for (const help of activeHelpRequests.values()) { - if (!help.settled && help.tabId === tabId) return help; + if (!help.settled && (help.subjectTabId === tabId || help.overlayTabIds.has(tabId))) { + return help; + } } return null; } @@ -323,7 +331,7 @@ async function findHelpForTab( tabId: number, deps: RequestHelpDeps, ): Promise { - const direct = findHelpByTabId(tabId); + const direct = findHelpBySubjectTabId(tabId); if (direct) return direct; try { @@ -331,7 +339,7 @@ async function findHelpForTab( const windowId = tab.windowId; if (typeof windowId !== "number") return null; for (const help of activeHelpRequests.values()) { - if (!help.settled && help.agentWindowId === windowId) return help; + if (!help.settled && help.ctx.agentWindowId === windowId) return help; } } catch { return null; @@ -346,18 +354,32 @@ function clearRearmTimer(tabId: number): void { helpRearmTimers.delete(tabId); } -async function rearmHelp(help: ActiveHelpRequest, targetTabId: number): Promise { +async function sendCurrentHelpOverlay(help: ActiveHelpRequest): Promise { + const legacyFinish = await sendHelpRequestWithAck( + help.subjectTabId, + helpRequestMessage(help), + help.deps, + ); + help.overlayTabIds.add(help.subjectTabId); + return legacyFinish; +} + +async function refreshAndSendHelpOverlay( + help: ActiveHelpRequest, +): Promise { + await refreshHelpTargets(help); + return sendCurrentHelpOverlay(help); +} + +async function rearmHelp(help: ActiveHelpRequest): Promise { for (let attempt = 0; attempt < HELP_REARM_MAX_ATTEMPTS; attempt += 1) { try { - help.tabId = targetTabId; - await refreshHelpTargets(help); - const msg = helpRequestMessage(help); - const legacyFinish = await sendHelpRequestWithAck(targetTabId, msg, help.deps); + const legacyFinish = await refreshAndSendHelpOverlay(help); if (legacyFinish && !help.settled) { finishHelp(help, { outcome: legacyFinish.outcome, ...(legacyFinish.note ? { note: legacyFinish.note } : {}), - tab_id: help.tabId, + tab_id: help.subjectTabId, resolved_targets: help.resolvedTargets, }); } @@ -378,7 +400,9 @@ function scheduleRearmForTab(tabId: number, deps: RequestHelpDeps): void { helpRearmTimers.delete(tabId); void (async () => { const help = await findHelpForTab(tabId, deps); - if (help) await rearmHelp(help, tabId); + if (!help) return; + help.subjectTabId = tabId; + await rearmHelp(help); })(); }, HELP_REARM_DEBOUNCE_MS), ); @@ -433,15 +457,17 @@ function attachHelpRuntimeListener(deps: RequestHelpDeps): () => void { sendResponse({ active: false }); return; } - help.tabId = tabId; + 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 } : {}), - selectors: help.selectors, + displayMode: tabId === help.primaryTabId ? "full" : "compact", + selectors: tabId === help.primaryTabId ? help.selectors : [], timeoutMs: help.timeoutMs, }, }); @@ -466,13 +492,14 @@ 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.tabId, + tab_id: help.subjectTabId, resolved_targets: help.resolvedTargets, }, false, @@ -481,15 +508,8 @@ async function finishHelpFromContent( function attachHelpTabListener(deps: RequestHelpDeps): () => void { const onCreated = (tab: chrome.tabs.Tab) => { - const tabId = tab.id; - const windowId = tab.windowId; - if (tabId === undefined || windowId === undefined) return; - for (const help of activeHelpRequests.values()) { - if (!help.settled && help.agentWindowId === windowId) { - scheduleRearmForTab(tabId, deps); - return; - } - } + if (typeof tab.id !== "number") return; + scheduleRearmForTab(tab.id, deps); }; const onActivated = (activeInfo: chrome.tabs.TabActiveInfo) => { scheduleRearmForTab(activeInfo.tabId, deps); @@ -531,7 +551,7 @@ async function evaluateCompletionCondition( if (condition.url_contains || condition.url_matches) { let url = ""; try { - const tab = await help.deps.tabsApi.get(help.tabId); + const tab = await help.deps.tabsApi.get(help.subjectTabId); url = tab.url ?? ""; } catch { return false; @@ -567,7 +587,7 @@ async function evaluateCompletionCondition( })()`; try { const result = await help.deps.cdp.send<{ result?: { value?: boolean } }>( - help.tabId, + help.subjectTabId, "Runtime.evaluate", { expression, returnByValue: true }, ); @@ -619,7 +639,7 @@ function startCompletionPolling(help: ActiveHelpRequest): void { finishHelp(help, { outcome: "completed", completed_by: "system", - tab_id: help.tabId, + tab_id: help.subjectTabId, resolved_targets: help.resolvedTargets, }); }; @@ -677,17 +697,17 @@ export async function handleRequestHelp( const timeoutTimer = setTimeout(() => { finishHelp(help, { outcome: "timed_out", - tab_id: help.tabId, + tab_id: help.subjectTabId, resolved_targets: help.resolvedTargets, }); }, timeoutMs); const help: ActiveHelpRequest = { - sessionId: params.session_id, ctx, requestId, - tabId, - agentWindowId: ctx.agentWindowId, + subjectTabId: tabId, + primaryTabId: tabId, + overlayTabIds: new Set(), prompt: params.prompt, ...(params.title ? { title: params.title } : {}), targets: initialTargets, @@ -715,13 +735,13 @@ export async function handleRequestHelp( deps.signal?.addEventListener("abort", onAbort, { once: true }); startCompletionPolling(help); - void sendHelpRequestWithAck(tabId, helpRequestMessage(help), deps) + void sendCurrentHelpOverlay(help) .then((legacyFinish) => { if (!legacyFinish || help.settled) return; finishHelp(help, { outcome: legacyFinish.outcome, ...(legacyFinish.note ? { note: legacyFinish.note } : {}), - tab_id: help.tabId, + tab_id: help.subjectTabId, resolved_targets: help.resolvedTargets, }); }) diff --git a/packages/i18n/src/locales/en-US/extension.json b/packages/i18n/src/locales/en-US/extension.json index 8997a4e..62f0fe0 100644 --- a/packages/i18n/src/locales/en-US/extension.json +++ b/packages/i18n/src/locales/en-US/extension.json @@ -74,6 +74,7 @@ }, "helpRequest": { "title": "Agent needs your help", + "compactStatus": "Agent is still waiting for you to finish logging in", "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 3e1fba6..9f33bb4 100644 --- a/packages/i18n/src/locales/zh-CN/extension.json +++ b/packages/i18n/src/locales/zh-CN/extension.json @@ -74,6 +74,7 @@ }, "helpRequest": { "title": "Agent 需要你的帮助", + "compactStatus": "当前 Agent 仍在等待你完成登录", "noteLabel": "给 Agent 的备注(可选)", "notePlaceholder": "给 Agent 留言(可选)", "continue": "完成并交还控制权",