Skip to content

Commit a679dad

Browse files
author
Ljy-0827
committed
fix(extension): CI problem fixed, tab logic fixed
1 parent 1ee1612 commit a679dad

5 files changed

Lines changed: 56 additions & 40 deletions

File tree

‎apps/extension/src/tools/__tests__/human-loop.test.ts‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ describe("handleRequestHelp", () => {
197197
expect(res).toMatchObject({ outcome: "completed", completed_by: "system", tab_id: 5 });
198198
});
199199

200-
it("keeps user control active on new tabs opened in the agent window", async () => {
200+
it("keeps user control active on new tabs without moving completion off the primary tab", async () => {
201201
vi.useFakeTimers();
202202
const chromeEvents = installHelpLifecycleChrome();
203203
const sendToTab = vi.fn(async () => ({ type: "bsk-help-ack", ok: true }));
@@ -246,9 +246,8 @@ describe("handleRequestHelp", () => {
246246

247247
await vi.advanceTimersByTimeAsync(1_000);
248248
await expect(pending).resolves.toMatchObject({
249-
outcome: "completed",
250-
completed_by: "system",
251-
tab_id: 6,
249+
outcome: "timed_out",
250+
tab_id: 5,
252251
});
253252
expect(sendToTab).toHaveBeenCalledWith(
254253
6,
@@ -262,12 +261,13 @@ describe("handleRequestHelp", () => {
262261
it("lets same-window tabs query active help after content-script load", async () => {
263262
const chromeEvents = installHelpLifecycleChrome();
264263
const ac = new AbortController();
264+
const cdpSend = vi.fn(async () => ({ root: { nodeId: 1 }, nodeIds: [] }));
265265
const deps = baseDeps({
266266
autoAttachLifecycle: undefined,
267267
signal: ac.signal,
268268
sendToTab: vi.fn(async () => ({ type: "bsk-help-ack", ok: true })),
269269
cdp: {
270-
send: vi.fn(async () => ({ root: { nodeId: 1 }, nodeIds: [] })),
270+
send: cdpSend,
271271
} as unknown as RequestHelpDeps["cdp"],
272272
});
273273

@@ -315,6 +315,16 @@ describe("handleRequestHelp", () => {
315315

316316
ac.abort();
317317
await expect(pending).resolves.toMatchObject({ code: "cancelled" });
318+
expect(cdpSend).toHaveBeenCalledWith(
319+
5,
320+
"DOM.querySelectorAll",
321+
expect.objectContaining({ selector: "[data-bsk-help]" }),
322+
);
323+
expect(cdpSend).toHaveBeenCalledWith(
324+
6,
325+
"DOM.querySelectorAll",
326+
expect.objectContaining({ selector: "[data-bsk-help]" }),
327+
);
318328
});
319329

320330
it("tags ref targets via CDP and reports them matched", async () => {

‎apps/extension/src/tools/human-loop.ts‎

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export interface RequestHelpDeps {
6868
interface ActiveHelpRequest {
6969
ctx: SessionContext;
7070
requestId: string;
71-
subjectTabId: number;
7271
primaryTabId: number;
7372
overlayTabIds: Set<number>;
7473
prompt: string;
@@ -126,8 +125,8 @@ function sleep(ms: number): Promise<void> {
126125
return new Promise((resolve) => setTimeout(resolve, ms));
127126
}
128127

129-
function helpRequestMessage(help: ActiveHelpRequest): HelpRequestMessage {
130-
const isPrimaryTab = help.subjectTabId === help.primaryTabId;
128+
function helpRequestMessage(help: ActiveHelpRequest, tabId: number): HelpRequestMessage {
129+
const isPrimaryTab = tabId === help.primaryTabId;
131130
return {
132131
type: HELP_REQUEST,
133132
requestId: help.requestId,
@@ -277,10 +276,10 @@ async function resolveHelpTargets(
277276
}
278277

279278
async function refreshHelpTargets(help: ActiveHelpRequest): Promise<void> {
280-
await clearRefTags(help.deps.cdp, help.subjectTabId);
279+
await clearRefTags(help.deps.cdp, help.primaryTabId);
281280
const { selectors, resolvedTargets } = await resolveHelpTargets(
282281
help.ctx,
283-
help.subjectTabId,
282+
help.primaryTabId,
284283
help.targets,
285284
help.deps,
286285
);
@@ -289,14 +288,18 @@ async function refreshHelpTargets(help: ActiveHelpRequest): Promise<void> {
289288
}
290289

291290
async function cleanupHelp(help: ActiveHelpRequest): Promise<void> {
292-
await clearRefTags(help.deps.cdp, help.subjectTabId);
293291
if (help.deps.notifications) {
294292
await help.deps.notifications.clear(help.notificationId).catch(() => {});
295293
}
296-
const tabsToCancel = new Set([help.subjectTabId, ...help.overlayTabIds]);
294+
const tabsToCancel = new Set([help.primaryTabId, ...help.overlayTabIds]);
297295
await Promise.all(
298296
[...tabsToCancel].map((tabId) =>
299-
help.deps.sendToTab(tabId, { type: HELP_CANCEL, requestId: help.requestId }).catch(() => {}),
297+
Promise.all([
298+
clearRefTags(help.deps.cdp, tabId),
299+
help.deps
300+
.sendToTab(tabId, { type: HELP_CANCEL, requestId: help.requestId })
301+
.catch(() => {}),
302+
]),
300303
),
301304
);
302305
}
@@ -312,15 +315,15 @@ function finishHelp(
312315
clearTimeout(help.timeoutTimer);
313316
if (help.completionTimer) clearInterval(help.completionTimer);
314317
if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler);
315-
clearRearmTimer(help.subjectTabId);
318+
clearRearmTimer(help.primaryTabId);
316319
for (const tabId of help.overlayTabIds) clearRearmTimer(tabId);
317320
if (notifyContent) void cleanupHelp(help);
318321
help.resolve(value);
319322
}
320323

321-
function findHelpBySubjectTabId(tabId: number): ActiveHelpRequest | null {
324+
function findHelpByKnownTabId(tabId: number): ActiveHelpRequest | null {
322325
for (const help of activeHelpRequests.values()) {
323-
if (!help.settled && (help.subjectTabId === tabId || help.overlayTabIds.has(tabId))) {
326+
if (!help.settled && (help.primaryTabId === tabId || help.overlayTabIds.has(tabId))) {
324327
return help;
325328
}
326329
}
@@ -331,7 +334,7 @@ async function findHelpForTab(
331334
tabId: number,
332335
deps: RequestHelpDeps,
333336
): Promise<ActiveHelpRequest | null> {
334-
const direct = findHelpBySubjectTabId(tabId);
337+
const direct = findHelpByKnownTabId(tabId);
335338
if (direct) return direct;
336339

337340
try {
@@ -356,30 +359,37 @@ function clearRearmTimer(tabId: number): void {
356359

357360
async function sendCurrentHelpOverlay(help: ActiveHelpRequest): Promise<HelpFinishMessage | null> {
358361
const legacyFinish = await sendHelpRequestWithAck(
359-
help.subjectTabId,
360-
helpRequestMessage(help),
362+
help.primaryTabId,
363+
helpRequestMessage(help, help.primaryTabId),
361364
help.deps,
362365
);
363-
help.overlayTabIds.add(help.subjectTabId);
366+
help.overlayTabIds.add(help.primaryTabId);
364367
return legacyFinish;
365368
}
366369

367370
async function refreshAndSendHelpOverlay(
368371
help: ActiveHelpRequest,
372+
tabId: number,
369373
): Promise<HelpFinishMessage | null> {
370-
await refreshHelpTargets(help);
371-
return sendCurrentHelpOverlay(help);
374+
if (tabId === help.primaryTabId) await refreshHelpTargets(help);
375+
const legacyFinish = await sendHelpRequestWithAck(
376+
tabId,
377+
helpRequestMessage(help, tabId),
378+
help.deps,
379+
);
380+
help.overlayTabIds.add(tabId);
381+
return legacyFinish;
372382
}
373383

374-
async function rearmHelp(help: ActiveHelpRequest): Promise<boolean> {
384+
async function rearmHelp(help: ActiveHelpRequest, tabId: number): Promise<boolean> {
375385
for (let attempt = 0; attempt < HELP_REARM_MAX_ATTEMPTS; attempt += 1) {
376386
try {
377-
const legacyFinish = await refreshAndSendHelpOverlay(help);
387+
const legacyFinish = await refreshAndSendHelpOverlay(help, tabId);
378388
if (legacyFinish && !help.settled) {
379389
finishHelp(help, {
380390
outcome: legacyFinish.outcome,
381391
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
382-
tab_id: help.subjectTabId,
392+
tab_id: help.primaryTabId,
383393
resolved_targets: help.resolvedTargets,
384394
});
385395
}
@@ -401,8 +411,7 @@ function scheduleRearmForTab(tabId: number, deps: RequestHelpDeps): void {
401411
void (async () => {
402412
const help = await findHelpForTab(tabId, deps);
403413
if (!help) return;
404-
help.subjectTabId = tabId;
405-
await rearmHelp(help);
414+
await rearmHelp(help, tabId);
406415
})();
407416
}, HELP_REARM_DEBOUNCE_MS),
408417
);
@@ -457,8 +466,7 @@ function attachHelpRuntimeListener(deps: RequestHelpDeps): () => void {
457466
sendResponse({ active: false });
458467
return;
459468
}
460-
help.subjectTabId = tabId;
461-
await refreshHelpTargets(help);
469+
if (tabId === help.primaryTabId) await refreshHelpTargets(help);
462470
help.overlayTabIds.add(tabId);
463471
sendResponse({
464472
active: true,
@@ -492,14 +500,13 @@ async function finishHelpFromContent(
492500
if (tabId !== undefined) {
493501
const match = await findHelpForTab(tabId, deps);
494502
if (match !== help) return;
495-
help.subjectTabId = tabId;
496503
}
497504
finishHelp(
498505
help,
499506
{
500507
outcome: message.outcome,
501508
...(message.note ? { note: message.note } : {}),
502-
tab_id: help.subjectTabId,
509+
tab_id: help.primaryTabId,
503510
resolved_targets: help.resolvedTargets,
504511
},
505512
false,
@@ -551,7 +558,7 @@ async function evaluateCompletionCondition(
551558
if (condition.url_contains || condition.url_matches) {
552559
let url = "";
553560
try {
554-
const tab = await help.deps.tabsApi.get(help.subjectTabId);
561+
const tab = await help.deps.tabsApi.get(help.primaryTabId);
555562
url = tab.url ?? "";
556563
} catch {
557564
return false;
@@ -587,7 +594,7 @@ async function evaluateCompletionCondition(
587594
})()`;
588595
try {
589596
const result = await help.deps.cdp.send<{ result?: { value?: boolean } }>(
590-
help.subjectTabId,
597+
help.primaryTabId,
591598
"Runtime.evaluate",
592599
{ expression, returnByValue: true },
593600
);
@@ -639,7 +646,7 @@ function startCompletionPolling(help: ActiveHelpRequest): void {
639646
finishHelp(help, {
640647
outcome: "completed",
641648
completed_by: "system",
642-
tab_id: help.subjectTabId,
649+
tab_id: help.primaryTabId,
643650
resolved_targets: help.resolvedTargets,
644651
});
645652
};
@@ -697,15 +704,14 @@ export async function handleRequestHelp(
697704
const timeoutTimer = setTimeout(() => {
698705
finishHelp(help, {
699706
outcome: "timed_out",
700-
tab_id: help.subjectTabId,
707+
tab_id: help.primaryTabId,
701708
resolved_targets: help.resolvedTargets,
702709
});
703710
}, timeoutMs);
704711

705712
const help: ActiveHelpRequest = {
706713
ctx,
707714
requestId,
708-
subjectTabId: tabId,
709715
primaryTabId: tabId,
710716
overlayTabIds: new Set(),
711717
prompt: params.prompt,
@@ -741,7 +747,7 @@ export async function handleRequestHelp(
741747
finishHelp(help, {
742748
outcome: legacyFinish.outcome,
743749
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
744-
tab_id: help.subjectTabId,
750+
tab_id: help.primaryTabId,
745751
resolved_targets: help.resolvedTargets,
746752
});
747753
})

‎crates/bsk-cli/src/cli/human_loop.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub fn dispatch(args: RequestHelpArgs, format: Format) -> Result<(), CliError> {
7777
let completion_criteria = args
7878
.completion_criteria
7979
.as_deref()
80-
.map(|raw| serde_json::from_str::<HelpCompletionCriteria>(raw))
80+
.map(serde_json::from_str::<HelpCompletionCriteria>)
8181
.transpose()
8282
.context("parse --completion-criteria JSON")?;
8383
let params = RequestHelpParams {

‎packages/i18n/src/locales/en-US/extension.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
},
7575
"helpRequest": {
7676
"title": "Agent needs your help",
77-
"compactStatus": "Agent is still waiting for you to finish logging in",
77+
"compactStatus": "Agent is still waiting for you to finish this step",
7878
"noteLabel": "Optional note back to the agent",
7979
"notePlaceholder": "Add a note for the agent (optional)",
8080
"continue": "Done, return control",

‎packages/i18n/src/locales/zh-CN/extension.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
},
7575
"helpRequest": {
7676
"title": "Agent 需要你的帮助",
77-
"compactStatus": "当前 Agent 仍在等待你完成登录",
77+
"compactStatus": "当前 Agent 仍在等待你完成此步骤",
7878
"noteLabel": "给 Agent 的备注(可选)",
7979
"notePlaceholder": "给 Agent 留言(可选)",
8080
"continue": "完成并交还控制权",

0 commit comments

Comments
 (0)