mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(extension): CI problem fixed, tab logic fixed
This commit is contained in:
@@ -197,7 +197,7 @@ describe("handleRequestHelp", () => {
|
|||||||
expect(res).toMatchObject({ outcome: "completed", completed_by: "system", tab_id: 5 });
|
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();
|
vi.useFakeTimers();
|
||||||
const chromeEvents = installHelpLifecycleChrome();
|
const chromeEvents = installHelpLifecycleChrome();
|
||||||
const sendToTab = vi.fn(async () => ({ type: "bsk-help-ack", ok: true }));
|
const sendToTab = vi.fn(async () => ({ type: "bsk-help-ack", ok: true }));
|
||||||
@@ -246,9 +246,8 @@ describe("handleRequestHelp", () => {
|
|||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1_000);
|
await vi.advanceTimersByTimeAsync(1_000);
|
||||||
await expect(pending).resolves.toMatchObject({
|
await expect(pending).resolves.toMatchObject({
|
||||||
outcome: "completed",
|
outcome: "timed_out",
|
||||||
completed_by: "system",
|
tab_id: 5,
|
||||||
tab_id: 6,
|
|
||||||
});
|
});
|
||||||
expect(sendToTab).toHaveBeenCalledWith(
|
expect(sendToTab).toHaveBeenCalledWith(
|
||||||
6,
|
6,
|
||||||
@@ -262,12 +261,13 @@ describe("handleRequestHelp", () => {
|
|||||||
it("lets same-window tabs query active help after content-script load", async () => {
|
it("lets same-window tabs query active help after content-script load", async () => {
|
||||||
const chromeEvents = installHelpLifecycleChrome();
|
const chromeEvents = installHelpLifecycleChrome();
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
|
const cdpSend = vi.fn(async () => ({ root: { nodeId: 1 }, nodeIds: [] }));
|
||||||
const deps = baseDeps({
|
const deps = baseDeps({
|
||||||
autoAttachLifecycle: undefined,
|
autoAttachLifecycle: undefined,
|
||||||
signal: ac.signal,
|
signal: ac.signal,
|
||||||
sendToTab: vi.fn(async () => ({ type: "bsk-help-ack", ok: true })),
|
sendToTab: vi.fn(async () => ({ type: "bsk-help-ack", ok: true })),
|
||||||
cdp: {
|
cdp: {
|
||||||
send: vi.fn(async () => ({ root: { nodeId: 1 }, nodeIds: [] })),
|
send: cdpSend,
|
||||||
} as unknown as RequestHelpDeps["cdp"],
|
} as unknown as RequestHelpDeps["cdp"],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -315,6 +315,16 @@ describe("handleRequestHelp", () => {
|
|||||||
|
|
||||||
ac.abort();
|
ac.abort();
|
||||||
await expect(pending).resolves.toMatchObject({ code: "cancelled" });
|
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 () => {
|
it("tags ref targets via CDP and reports them matched", async () => {
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export interface RequestHelpDeps {
|
|||||||
interface ActiveHelpRequest {
|
interface ActiveHelpRequest {
|
||||||
ctx: SessionContext;
|
ctx: SessionContext;
|
||||||
requestId: string;
|
requestId: string;
|
||||||
subjectTabId: number;
|
|
||||||
primaryTabId: number;
|
primaryTabId: number;
|
||||||
overlayTabIds: Set<number>;
|
overlayTabIds: Set<number>;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
@@ -126,8 +125,8 @@ function sleep(ms: number): Promise<void> {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
function helpRequestMessage(help: ActiveHelpRequest): HelpRequestMessage {
|
function helpRequestMessage(help: ActiveHelpRequest, tabId: number): HelpRequestMessage {
|
||||||
const isPrimaryTab = help.subjectTabId === help.primaryTabId;
|
const isPrimaryTab = tabId === help.primaryTabId;
|
||||||
return {
|
return {
|
||||||
type: HELP_REQUEST,
|
type: HELP_REQUEST,
|
||||||
requestId: help.requestId,
|
requestId: help.requestId,
|
||||||
@@ -277,10 +276,10 @@ async function resolveHelpTargets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshHelpTargets(help: ActiveHelpRequest): Promise<void> {
|
async function refreshHelpTargets(help: ActiveHelpRequest): Promise<void> {
|
||||||
await clearRefTags(help.deps.cdp, help.subjectTabId);
|
await clearRefTags(help.deps.cdp, help.primaryTabId);
|
||||||
const { selectors, resolvedTargets } = await resolveHelpTargets(
|
const { selectors, resolvedTargets } = await resolveHelpTargets(
|
||||||
help.ctx,
|
help.ctx,
|
||||||
help.subjectTabId,
|
help.primaryTabId,
|
||||||
help.targets,
|
help.targets,
|
||||||
help.deps,
|
help.deps,
|
||||||
);
|
);
|
||||||
@@ -289,14 +288,18 @@ async function refreshHelpTargets(help: ActiveHelpRequest): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupHelp(help: ActiveHelpRequest): Promise<void> {
|
async function cleanupHelp(help: ActiveHelpRequest): Promise<void> {
|
||||||
await clearRefTags(help.deps.cdp, help.subjectTabId);
|
|
||||||
if (help.deps.notifications) {
|
if (help.deps.notifications) {
|
||||||
await help.deps.notifications.clear(help.notificationId).catch(() => {});
|
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(
|
await Promise.all(
|
||||||
[...tabsToCancel].map((tabId) =>
|
[...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);
|
clearTimeout(help.timeoutTimer);
|
||||||
if (help.completionTimer) clearInterval(help.completionTimer);
|
if (help.completionTimer) clearInterval(help.completionTimer);
|
||||||
if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler);
|
if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler);
|
||||||
clearRearmTimer(help.subjectTabId);
|
clearRearmTimer(help.primaryTabId);
|
||||||
for (const tabId of help.overlayTabIds) clearRearmTimer(tabId);
|
for (const tabId of help.overlayTabIds) clearRearmTimer(tabId);
|
||||||
if (notifyContent) void cleanupHelp(help);
|
if (notifyContent) void cleanupHelp(help);
|
||||||
help.resolve(value);
|
help.resolve(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findHelpBySubjectTabId(tabId: number): ActiveHelpRequest | null {
|
function findHelpByKnownTabId(tabId: number): ActiveHelpRequest | null {
|
||||||
for (const help of activeHelpRequests.values()) {
|
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;
|
return help;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,7 +334,7 @@ async function findHelpForTab(
|
|||||||
tabId: number,
|
tabId: number,
|
||||||
deps: RequestHelpDeps,
|
deps: RequestHelpDeps,
|
||||||
): Promise<ActiveHelpRequest | null> {
|
): Promise<ActiveHelpRequest | null> {
|
||||||
const direct = findHelpBySubjectTabId(tabId);
|
const direct = findHelpByKnownTabId(tabId);
|
||||||
if (direct) return direct;
|
if (direct) return direct;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -356,30 +359,37 @@ function clearRearmTimer(tabId: number): void {
|
|||||||
|
|
||||||
async function sendCurrentHelpOverlay(help: ActiveHelpRequest): Promise<HelpFinishMessage | null> {
|
async function sendCurrentHelpOverlay(help: ActiveHelpRequest): Promise<HelpFinishMessage | null> {
|
||||||
const legacyFinish = await sendHelpRequestWithAck(
|
const legacyFinish = await sendHelpRequestWithAck(
|
||||||
help.subjectTabId,
|
help.primaryTabId,
|
||||||
helpRequestMessage(help),
|
helpRequestMessage(help, help.primaryTabId),
|
||||||
help.deps,
|
help.deps,
|
||||||
);
|
);
|
||||||
help.overlayTabIds.add(help.subjectTabId);
|
help.overlayTabIds.add(help.primaryTabId);
|
||||||
return legacyFinish;
|
return legacyFinish;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAndSendHelpOverlay(
|
async function refreshAndSendHelpOverlay(
|
||||||
help: ActiveHelpRequest,
|
help: ActiveHelpRequest,
|
||||||
|
tabId: number,
|
||||||
): Promise<HelpFinishMessage | null> {
|
): Promise<HelpFinishMessage | null> {
|
||||||
await refreshHelpTargets(help);
|
if (tabId === help.primaryTabId) await refreshHelpTargets(help);
|
||||||
return sendCurrentHelpOverlay(help);
|
const legacyFinish = await sendHelpRequestWithAck(
|
||||||
|
tabId,
|
||||||
|
helpRequestMessage(help, tabId),
|
||||||
|
help.deps,
|
||||||
|
);
|
||||||
|
help.overlayTabIds.add(tabId);
|
||||||
|
return legacyFinish;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rearmHelp(help: ActiveHelpRequest): Promise<boolean> {
|
async function rearmHelp(help: ActiveHelpRequest, tabId: number): Promise<boolean> {
|
||||||
for (let attempt = 0; attempt < HELP_REARM_MAX_ATTEMPTS; attempt += 1) {
|
for (let attempt = 0; attempt < HELP_REARM_MAX_ATTEMPTS; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
const legacyFinish = await refreshAndSendHelpOverlay(help);
|
const legacyFinish = await refreshAndSendHelpOverlay(help, tabId);
|
||||||
if (legacyFinish && !help.settled) {
|
if (legacyFinish && !help.settled) {
|
||||||
finishHelp(help, {
|
finishHelp(help, {
|
||||||
outcome: legacyFinish.outcome,
|
outcome: legacyFinish.outcome,
|
||||||
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
||||||
tab_id: help.subjectTabId,
|
tab_id: help.primaryTabId,
|
||||||
resolved_targets: help.resolvedTargets,
|
resolved_targets: help.resolvedTargets,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -401,8 +411,7 @@ function scheduleRearmForTab(tabId: number, deps: RequestHelpDeps): void {
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
const help = await findHelpForTab(tabId, deps);
|
const help = await findHelpForTab(tabId, deps);
|
||||||
if (!help) return;
|
if (!help) return;
|
||||||
help.subjectTabId = tabId;
|
await rearmHelp(help, tabId);
|
||||||
await rearmHelp(help);
|
|
||||||
})();
|
})();
|
||||||
}, HELP_REARM_DEBOUNCE_MS),
|
}, HELP_REARM_DEBOUNCE_MS),
|
||||||
);
|
);
|
||||||
@@ -457,8 +466,7 @@ function attachHelpRuntimeListener(deps: RequestHelpDeps): () => void {
|
|||||||
sendResponse({ active: false });
|
sendResponse({ active: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
help.subjectTabId = tabId;
|
if (tabId === help.primaryTabId) await refreshHelpTargets(help);
|
||||||
await refreshHelpTargets(help);
|
|
||||||
help.overlayTabIds.add(tabId);
|
help.overlayTabIds.add(tabId);
|
||||||
sendResponse({
|
sendResponse({
|
||||||
active: true,
|
active: true,
|
||||||
@@ -492,14 +500,13 @@ async function finishHelpFromContent(
|
|||||||
if (tabId !== undefined) {
|
if (tabId !== undefined) {
|
||||||
const match = await findHelpForTab(tabId, deps);
|
const match = await findHelpForTab(tabId, deps);
|
||||||
if (match !== help) return;
|
if (match !== help) return;
|
||||||
help.subjectTabId = tabId;
|
|
||||||
}
|
}
|
||||||
finishHelp(
|
finishHelp(
|
||||||
help,
|
help,
|
||||||
{
|
{
|
||||||
outcome: message.outcome,
|
outcome: message.outcome,
|
||||||
...(message.note ? { note: message.note } : {}),
|
...(message.note ? { note: message.note } : {}),
|
||||||
tab_id: help.subjectTabId,
|
tab_id: help.primaryTabId,
|
||||||
resolved_targets: help.resolvedTargets,
|
resolved_targets: help.resolvedTargets,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -551,7 +558,7 @@ async function evaluateCompletionCondition(
|
|||||||
if (condition.url_contains || condition.url_matches) {
|
if (condition.url_contains || condition.url_matches) {
|
||||||
let url = "";
|
let url = "";
|
||||||
try {
|
try {
|
||||||
const tab = await help.deps.tabsApi.get(help.subjectTabId);
|
const tab = await help.deps.tabsApi.get(help.primaryTabId);
|
||||||
url = tab.url ?? "";
|
url = tab.url ?? "";
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
@@ -587,7 +594,7 @@ async function evaluateCompletionCondition(
|
|||||||
})()`;
|
})()`;
|
||||||
try {
|
try {
|
||||||
const result = await help.deps.cdp.send<{ result?: { value?: boolean } }>(
|
const result = await help.deps.cdp.send<{ result?: { value?: boolean } }>(
|
||||||
help.subjectTabId,
|
help.primaryTabId,
|
||||||
"Runtime.evaluate",
|
"Runtime.evaluate",
|
||||||
{ expression, returnByValue: true },
|
{ expression, returnByValue: true },
|
||||||
);
|
);
|
||||||
@@ -639,7 +646,7 @@ function startCompletionPolling(help: ActiveHelpRequest): void {
|
|||||||
finishHelp(help, {
|
finishHelp(help, {
|
||||||
outcome: "completed",
|
outcome: "completed",
|
||||||
completed_by: "system",
|
completed_by: "system",
|
||||||
tab_id: help.subjectTabId,
|
tab_id: help.primaryTabId,
|
||||||
resolved_targets: help.resolvedTargets,
|
resolved_targets: help.resolvedTargets,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -697,7 +704,7 @@ export async function handleRequestHelp(
|
|||||||
const timeoutTimer = setTimeout(() => {
|
const timeoutTimer = setTimeout(() => {
|
||||||
finishHelp(help, {
|
finishHelp(help, {
|
||||||
outcome: "timed_out",
|
outcome: "timed_out",
|
||||||
tab_id: help.subjectTabId,
|
tab_id: help.primaryTabId,
|
||||||
resolved_targets: help.resolvedTargets,
|
resolved_targets: help.resolvedTargets,
|
||||||
});
|
});
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
@@ -705,7 +712,6 @@ export async function handleRequestHelp(
|
|||||||
const help: ActiveHelpRequest = {
|
const help: ActiveHelpRequest = {
|
||||||
ctx,
|
ctx,
|
||||||
requestId,
|
requestId,
|
||||||
subjectTabId: tabId,
|
|
||||||
primaryTabId: tabId,
|
primaryTabId: tabId,
|
||||||
overlayTabIds: new Set(),
|
overlayTabIds: new Set(),
|
||||||
prompt: params.prompt,
|
prompt: params.prompt,
|
||||||
@@ -741,7 +747,7 @@ export async function handleRequestHelp(
|
|||||||
finishHelp(help, {
|
finishHelp(help, {
|
||||||
outcome: legacyFinish.outcome,
|
outcome: legacyFinish.outcome,
|
||||||
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
||||||
tab_id: help.subjectTabId,
|
tab_id: help.primaryTabId,
|
||||||
resolved_targets: help.resolvedTargets,
|
resolved_targets: help.resolvedTargets,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub fn dispatch(args: RequestHelpArgs, format: Format) -> Result<(), CliError> {
|
|||||||
let completion_criteria = args
|
let completion_criteria = args
|
||||||
.completion_criteria
|
.completion_criteria
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|raw| serde_json::from_str::<HelpCompletionCriteria>(raw))
|
.map(serde_json::from_str::<HelpCompletionCriteria>)
|
||||||
.transpose()
|
.transpose()
|
||||||
.context("parse --completion-criteria JSON")?;
|
.context("parse --completion-criteria JSON")?;
|
||||||
let params = RequestHelpParams {
|
let params = RequestHelpParams {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
"helpRequest": {
|
"helpRequest": {
|
||||||
"title": "Agent needs your help",
|
"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",
|
"noteLabel": "Optional note back to the agent",
|
||||||
"notePlaceholder": "Add a note for the agent (optional)",
|
"notePlaceholder": "Add a note for the agent (optional)",
|
||||||
"continue": "Done, return control",
|
"continue": "Done, return control",
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
"helpRequest": {
|
"helpRequest": {
|
||||||
"title": "Agent 需要你的帮助",
|
"title": "Agent 需要你的帮助",
|
||||||
"compactStatus": "当前 Agent 仍在等待你完成登录",
|
"compactStatus": "当前 Agent 仍在等待你完成此步骤",
|
||||||
"noteLabel": "给 Agent 的备注(可选)",
|
"noteLabel": "给 Agent 的备注(可选)",
|
||||||
"notePlaceholder": "给 Agent 留言(可选)",
|
"notePlaceholder": "给 Agent 留言(可选)",
|
||||||
"continue": "完成并交还控制权",
|
"continue": "完成并交还控制权",
|
||||||
|
|||||||
Reference in New Issue
Block a user