























@@ -113,7 +113,12 @@ const gatewayMocks = vi.hoisted(() => ({
113113vi.mock("../../../src/agents/tools/gateway.js", () => gatewayMocks);
114114115115const configMocks = vi.hoisted(() => ({
116-loadConfig: vi.fn(() => ({ browser: {} })),
116+loadConfig: vi.fn<
117+() => {
118+browser: Record<string, unknown>;
119+gateway?: { nodes?: { browser?: { node?: string } } };
120+}
121+>(() => ({ browser: {} })),
117122}));
118123vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
119124const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
@@ -340,7 +345,7 @@ describe("browser tool snapshot maxChars", () => {
340345expect(opts?.mode).toBeUndefined();
341346});
342347343-it("defaults to host when using profile=user (even in sandboxed sessions)", async () => {
348+it("keeps profile=user off the sandbox browser when no node is selected", async () => {
344349setResolvedBrowserProfiles({
345350user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
346351});
@@ -360,7 +365,7 @@ describe("browser tool snapshot maxChars", () => {
360365);
361366});
362367363-it("defaults to host for custom existing-session profiles too", async () => {
368+it("keeps custom existing-session profiles off the sandbox browser too", async () => {
364369setResolvedBrowserProfiles({
365370"chrome-live": { driver: "existing-session", attachOnly: true, color: "#00AA00" },
366371});
@@ -470,14 +475,121 @@ describe("browser tool snapshot maxChars", () => {
470475expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
471476});
472477473-it("keeps user profile on host when node proxy is available", async () => {
478+it("routes profile=user through the node proxy when one is available", async () => {
474479mockSingleBrowserProxyNode();
475480setResolvedBrowserProfiles({
476481user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
477482});
478483const tool = createBrowserTool();
479484await tool.execute?.("call-1", { action: "status", profile: "user" });
480485486+expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith(
487+"node.invoke",
488+{ timeoutMs: 25000 },
489+expect.objectContaining({
490+nodeId: "node-1",
491+command: "browser.proxy",
492+params: expect.objectContaining({
493+profile: "user",
494+path: "/",
495+method: "GET",
496+timeoutMs: 20000,
497+}),
498+}),
499+);
500+expect(browserClientMocks.browserStatus).not.toHaveBeenCalled();
501+});
502+503+it("falls back to the host for profile=user when node discovery errors", async () => {
504+nodesUtilsMocks.listNodes.mockRejectedValueOnce(new Error("gateway unavailable"));
505+setResolvedBrowserProfiles({
506+user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
507+});
508+const tool = createBrowserTool();
509+await tool.execute?.("call-1", { action: "status", profile: "user" });
510+511+expect(browserClientMocks.browserStatus).toHaveBeenCalledWith(
512+undefined,
513+expect.objectContaining({ profile: "user" }),
514+);
515+expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
516+});
517+518+it("preserves configured node pins when profile=user node discovery errors", async () => {
519+nodesUtilsMocks.listNodes.mockRejectedValueOnce(new Error("gateway unavailable"));
520+configMocks.loadConfig.mockReturnValue({
521+browser: {},
522+gateway: { nodes: { browser: { node: "node-1" } } },
523+});
524+setResolvedBrowserProfiles({
525+user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
526+});
527+const tool = createBrowserTool();
528+529+await expect(tool.execute?.("call-1", { action: "status", profile: "user" })).rejects.toThrow(
530+/gateway unavailable/i,
531+);
532+533+expect(browserClientMocks.browserStatus).not.toHaveBeenCalled();
534+expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
535+});
536+537+it('allows profile="user" with target="node"', async () => {
538+mockSingleBrowserProxyNode();
539+setResolvedBrowserProfiles({
540+user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
541+});
542+const tool = createBrowserTool();
543+await tool.execute?.("call-1", { action: "status", profile: "user", target: "node" });
544+545+expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith(
546+"node.invoke",
547+{ timeoutMs: 25000 },
548+expect.objectContaining({
549+nodeId: "node-1",
550+command: "browser.proxy",
551+params: expect.objectContaining({
552+profile: "user",
553+path: "/",
554+method: "GET",
555+}),
556+}),
557+);
558+expect(browserClientMocks.browserStatus).not.toHaveBeenCalled();
559+});
560+561+it('allows profile="user" with an explicit node pin', async () => {
562+mockSingleBrowserProxyNode();
563+setResolvedBrowserProfiles({
564+user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
565+});
566+const tool = createBrowserTool();
567+await tool.execute?.("call-1", { action: "status", profile: "user", node: "node-1" });
568+569+expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith(
570+"node.invoke",
571+{ timeoutMs: 25000 },
572+expect.objectContaining({
573+nodeId: "node-1",
574+command: "browser.proxy",
575+params: expect.objectContaining({
576+profile: "user",
577+path: "/",
578+method: "GET",
579+}),
580+}),
581+);
582+expect(browserClientMocks.browserStatus).not.toHaveBeenCalled();
583+});
584+585+it('keeps profile="user" on the host when target="host" is explicit', async () => {
586+mockSingleBrowserProxyNode();
587+setResolvedBrowserProfiles({
588+user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
589+});
590+const tool = createBrowserTool();
591+await tool.execute?.("call-1", { action: "status", profile: "user", target: "host" });
592+481593expect(browserClientMocks.browserStatus).toHaveBeenCalledWith(
482594undefined,
483595expect.objectContaining({ profile: "user" }),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。