mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 08:07:18 +00:00
* feat(browser): add copilot security contracts * fix(gateway): expose verified client identity to handlers * feat(browser): add secure per-tab copilot panel Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * refactor(browser): separate copilot gateway hint custody * fix(browser): preserve legacy pairing parse shape * fix(browser): harden copilot lifecycle custody Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): enforce copilot lifecycle boundaries Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * style(browser): format copilot sources Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): preserve copilot consent revocation Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * refactor(browser): split copilot custody owners Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(browser): normalize websocket array buffers Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * chore(protocol): regenerate Swift gateway models Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * refactor(browser): model copilot runtime entrypoints Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): honor extension build boundaries Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(gateway): assert targeted chat delivery Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(gateway): cover targeted delivery calls Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): declare copilot build dependencies Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(ci): clear browser copilot gate failures Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(ci): cover copilot lint exclusion Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * fix(browser): gate copilot on relay custody Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> * test(browser): bound copilot relay frames Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com> --------- Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>
94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
applyChatDelta,
|
|
buildCopilotChatSendParams,
|
|
createChatStream,
|
|
deriveTabSessionKey,
|
|
gatewayUrlFromPairing,
|
|
normalizeGatewayUrl,
|
|
readMessageText,
|
|
renderMarkdownLite,
|
|
} from "./panel-core.js";
|
|
|
|
describe("browser copilot panel contracts", () => {
|
|
it("mints isolated thread keys without exposing reusable tab ids", () => {
|
|
const first = deriveTabSessionKey("agent:main:main", "11111111-1111-4111-8111-111111111111");
|
|
const second = deriveTabSessionKey(
|
|
"agent:main:main:thread:old",
|
|
"22222222-2222-4222-8222-222222222222",
|
|
);
|
|
expect(first).toBe(
|
|
"agent:main:main:thread:browser-copilot-11111111-1111-4111-8111-111111111111",
|
|
);
|
|
expect(second).toBe(
|
|
"agent:main:main:thread:browser-copilot-22222222-2222-4222-8222-222222222222",
|
|
);
|
|
expect(first).not.toBe(second);
|
|
expect(deriveTabSessionKey("agent:main:main", "tab-7")).toBeNull();
|
|
});
|
|
|
|
it("derives only secure remote or loopback Gateway endpoints", () => {
|
|
expect(gatewayUrlFromPairing("wss://gateway.example/base/browser/extension", undefined)).toBe(
|
|
"wss://gateway.example/base",
|
|
);
|
|
expect(gatewayUrlFromPairing("ws://127.0.0.1:18792/extension", "ws://127.0.0.1:18789")).toBe(
|
|
"ws://127.0.0.1:18789/",
|
|
);
|
|
expect(normalizeGatewayUrl("ws://gateway.example")).toBeNull();
|
|
const credentialed = new URL("wss://gateway.example");
|
|
credentialed.username = "fixture-user";
|
|
credentialed.password = "test-password";
|
|
expect(normalizeGatewayUrl(credentialed.toString())).toBeNull();
|
|
});
|
|
|
|
it("builds a local-only delivery with the trusted browser binding", () => {
|
|
vi.spyOn(crypto, "randomUUID").mockReturnValue("33333333-3333-4333-8333-333333333333");
|
|
const binding = {
|
|
kind: "tab",
|
|
tabId: 7,
|
|
target: "host",
|
|
profile: "chrome",
|
|
targetId: "target-7",
|
|
} as const;
|
|
expect(
|
|
buildCopilotChatSendParams({
|
|
binding,
|
|
message: " inspect this ",
|
|
sessionId: "session-7",
|
|
sessionKey: "agent:main:main:thread:browser-copilot-x",
|
|
}),
|
|
).toEqual({
|
|
sessionKey: "agent:main:main:thread:browser-copilot-x",
|
|
sessionId: "session-7",
|
|
message: "inspect this",
|
|
idempotencyKey: "33333333-3333-4333-8333-333333333333",
|
|
deliver: false,
|
|
toolBindings: { browser: binding },
|
|
});
|
|
});
|
|
|
|
it("renders cumulative deltas once and escapes page-controlled markup", () => {
|
|
const stream = createChatStream();
|
|
expect(applyChatDelta(stream, { runId: "run", deltaText: "Hello" })).toEqual({
|
|
text: "Hello",
|
|
newBubble: true,
|
|
});
|
|
expect(
|
|
applyChatDelta(stream, {
|
|
runId: "run",
|
|
message: { content: [{ text: "Hello world" }] },
|
|
}),
|
|
).toEqual({ text: "Hello world", newBubble: false });
|
|
expect(renderMarkdownLite("<img src=x> **safe**")).toBe(
|
|
"<img src=x> <strong>safe</strong>",
|
|
);
|
|
});
|
|
|
|
it("projects only visible text from history content", () => {
|
|
expect(readMessageText({ content: [{ type: "text", text: "one" }, { text: "two" }] })).toBe(
|
|
"one\ntwo",
|
|
);
|
|
expect(readMessageText({ content: [{ type: "image", data: "secret" }] })).toBe("");
|
|
});
|
|
});
|