mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
docs(ux): clarify version mismatches and update record skill docs
Surface actionable update guidance in the popup and connection errors, document the trace v3 directory bundle in SKILL.md.
This commit is contained in:
@@ -100,6 +100,24 @@ describe("App", () => {
|
||||
expect(screen.queryByText("录制你的操作,供 Agent 参考")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders localized copy for a version failure instead of protocol details", () => {
|
||||
mockUseConnectionState.mockReturnValue({
|
||||
snapshot: {
|
||||
...baseSnapshot,
|
||||
lastError:
|
||||
"[handshake] daemon rejected handshake: version_too_old — peer protocol 1.0 is below local min_compatible_protocol 1.1",
|
||||
},
|
||||
statusState: "disconnected",
|
||||
setLabel,
|
||||
setConnectionEnabled,
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
|
||||
const error = screen.getByText(/bsk 与 BrowserSkill 扩展版本不兼容/);
|
||||
expect(error.textContent).not.toContain("min_compatible_protocol");
|
||||
});
|
||||
|
||||
it("shows single-line compact metadata and copies the instance id", async () => {
|
||||
mockUseConnectionState.mockReturnValue({
|
||||
snapshot: {
|
||||
|
||||
@@ -83,6 +83,9 @@ export function App() {
|
||||
const daemonProtocol = snapshot.handshake?.protocol_version ?? "—";
|
||||
const extensionVersion = snapshot.extensionVersion || "—";
|
||||
const instanceId = snapshot.instanceId || "—";
|
||||
const errorMessage = snapshot.lastError?.includes("version_too_old")
|
||||
? t("popup.versionIncompatibleError")
|
||||
: snapshot.lastError;
|
||||
|
||||
const copyInstanceId = async () => {
|
||||
if (!snapshot.instanceId || !navigator.clipboard?.writeText) return;
|
||||
@@ -252,12 +255,12 @@ export function App() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{snapshot.lastError && (
|
||||
{errorMessage && (
|
||||
<div
|
||||
className="rounded-lg border border-destructive/25 bg-destructive/10 px-3 py-2 text-xs leading-snug text-destructive"
|
||||
data-slot="popup-error"
|
||||
>
|
||||
{snapshot.lastError}
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -27,19 +27,19 @@ function handshake(
|
||||
|
||||
describe("computeConnectedState (protocol-based compat)", () => {
|
||||
it("returns connected when protocol strings match", () => {
|
||||
expect(computeConnectedState(handshake("1.0", "1.0"), MIN_COMPATIBLE_PROTOCOL)).toEqual({
|
||||
expect(computeConnectedState(handshake("1.1", "1.1"), MIN_COMPATIBLE_PROTOCOL)).toEqual({
|
||||
kind: "connected",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns version_skew when daemon protocol minor is newer", () => {
|
||||
expect(computeConnectedState(handshake("1.1", "1.0"))).toEqual({
|
||||
expect(computeConnectedState(handshake("1.2", "1.1"))).toEqual({
|
||||
kind: "version_skew",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns version_skew when daemon protocol string differs but floor is satisfied", () => {
|
||||
expect(computeConnectedState(handshake("1", "1.0"))).toEqual({
|
||||
expect(computeConnectedState(handshake("1.1.0", "1.1"))).toEqual({
|
||||
kind: "version_skew",
|
||||
});
|
||||
});
|
||||
@@ -48,16 +48,19 @@ describe("computeConnectedState (protocol-based compat)", () => {
|
||||
const result = computeConnectedState(handshake("2.0", "1.0"));
|
||||
expect(result.kind).toBe("rejected");
|
||||
if (result.kind === "rejected") {
|
||||
expect(result.reason).toContain("protocol-major mismatch");
|
||||
expect(result.reason).toContain("bsk CLI");
|
||||
expect(result.reason).toContain("BrowserSkill extension");
|
||||
expect(result.reason).not.toContain("protocol");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects when extension is below daemon min_compatible_protocol", () => {
|
||||
const result = computeConnectedState(handshake("1.0", "1.5"));
|
||||
const result = computeConnectedState(handshake("1.1", "1.5"));
|
||||
expect(result.kind).toBe("rejected");
|
||||
if (result.kind === "rejected") {
|
||||
expect(result.reason).toContain("min_compatible_protocol");
|
||||
expect(result.reason).toContain("1.5");
|
||||
expect(result.reason).toContain("BrowserSkill extension is out of date");
|
||||
expect(result.reason).toContain("Update and reload the extension");
|
||||
expect(result.reason).not.toContain("min_compatible_protocol");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -65,7 +68,7 @@ describe("computeConnectedState (protocol-based compat)", () => {
|
||||
const result = computeConnectedState({
|
||||
server: "browser-skill-daemon",
|
||||
version: "0.1.0",
|
||||
protocol_version: "1.0",
|
||||
protocol_version: "1.1",
|
||||
min_compatible_peer: "0.1.0",
|
||||
});
|
||||
expect(result).toEqual({ kind: "connected" });
|
||||
@@ -75,16 +78,23 @@ describe("computeConnectedState (protocol-based compat)", () => {
|
||||
const result = computeConnectedState(handshake("1.0", "1.0"), "1.1");
|
||||
expect(result.kind).toBe("rejected");
|
||||
if (result.kind === "rejected") {
|
||||
expect(result.reason).toContain("below extension min_compatible_protocol");
|
||||
expect(result.reason).toContain("bsk CLI is out of date");
|
||||
expect(result.reason).toContain("bsk daemon restart");
|
||||
expect(result.reason).not.toContain("min_compatible_protocol");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a pre-Trace-v3 daemon using the configured extension floor", () => {
|
||||
const result = computeConnectedState(handshake("1.0", "1.0"));
|
||||
expect(result.kind).toBe("rejected");
|
||||
});
|
||||
|
||||
it("rejects malformed daemon min_compatible_protocol with a daemon-floor reason", () => {
|
||||
const result = computeConnectedState(handshake("1.0", "not-a-protocol"));
|
||||
const result = computeConnectedState(handshake("1.1", "not-a-protocol"));
|
||||
expect(result.kind).toBe("rejected");
|
||||
if (result.kind === "rejected") {
|
||||
expect(result.reason).toContain("daemon min_compatible_protocol");
|
||||
expect(result.reason).toContain("not-a-protocol");
|
||||
expect(result.reason).toContain("could not verify version compatibility");
|
||||
expect(result.reason).not.toContain("min_compatible_protocol");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -242,11 +252,11 @@ describe("ConnectionController connectionEnabled", () => {
|
||||
const second = transport.send.mock.calls[1]?.[0] as { id: string };
|
||||
expect(second.id).not.toBe(first.id);
|
||||
|
||||
transport.emitMessage({ id: first.id, result: handshake("1.0", "1.0") });
|
||||
transport.emitMessage({ id: first.id, result: handshake("1.1", "1.1") });
|
||||
await Promise.resolve();
|
||||
expect(controller.snapshot().state).not.toBe("connected");
|
||||
|
||||
transport.emitMessage({ id: second.id, result: handshake("1.0", "1.0") });
|
||||
transport.emitMessage({ id: second.id, result: handshake("1.1", "1.1") });
|
||||
await vi.waitFor(() => expect(controller.snapshot().state).toBe("connected"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -313,6 +313,15 @@ export type HandshakeVerdict =
|
||||
| { kind: "version_skew" }
|
||||
| { kind: "rejected"; reason: string };
|
||||
|
||||
const UPDATE_BOTH_VERSION_MESSAGE =
|
||||
"The installed bsk CLI and BrowserSkill extension are incompatible. Update both, reload the extension, then run `bsk daemon restart`.";
|
||||
const UPDATE_BSK_VERSION_MESSAGE =
|
||||
"The installed bsk CLI is out of date for this BrowserSkill extension. Update bsk, then run `bsk daemon restart`.";
|
||||
const UPDATE_EXTENSION_VERSION_MESSAGE =
|
||||
"This BrowserSkill extension is out of date for the installed bsk CLI. Update and reload the extension, then try again.";
|
||||
const INVALID_VERSION_MESSAGE =
|
||||
"BrowserSkill could not verify version compatibility. Update bsk and the BrowserSkill extension, reload the extension, then run `bsk daemon restart`.";
|
||||
|
||||
/**
|
||||
* Symmetric extension-side **protocol** compat check.
|
||||
*
|
||||
@@ -339,19 +348,19 @@ export function computeConnectedState(
|
||||
if (daemonMajor === null || ourMajor === null || daemonMajor !== ourMajor) {
|
||||
return {
|
||||
kind: "rejected",
|
||||
reason: `protocol-major mismatch (daemon protocol v${handshake.protocol_version}, extension protocol v${PROTOCOL_VERSION})`,
|
||||
reason: UPDATE_BOTH_VERSION_MESSAGE,
|
||||
};
|
||||
}
|
||||
if (compareProtocol(handshake.protocol_version, localMinCompatibleProtocol) === null) {
|
||||
return {
|
||||
kind: "rejected",
|
||||
reason: `daemon protocol v${handshake.protocol_version} is unparseable`,
|
||||
reason: INVALID_VERSION_MESSAGE,
|
||||
};
|
||||
}
|
||||
if (compareProtocol(handshake.protocol_version, localMinCompatibleProtocol)! < 0) {
|
||||
return {
|
||||
kind: "rejected",
|
||||
reason: `daemon protocol v${handshake.protocol_version} below extension min_compatible_protocol ${localMinCompatibleProtocol}`,
|
||||
reason: UPDATE_BSK_VERSION_MESSAGE,
|
||||
};
|
||||
}
|
||||
const peerFloor = handshake.min_compatible_protocol;
|
||||
@@ -359,13 +368,13 @@ export function computeConnectedState(
|
||||
if (compareProtocol(PROTOCOL_VERSION, peerFloor) === null) {
|
||||
return {
|
||||
kind: "rejected",
|
||||
reason: `daemon min_compatible_protocol ${peerFloor} is unparseable`,
|
||||
reason: INVALID_VERSION_MESSAGE,
|
||||
};
|
||||
}
|
||||
if (compareProtocol(PROTOCOL_VERSION, peerFloor)! < 0) {
|
||||
return {
|
||||
kind: "rejected",
|
||||
reason: `extension protocol v${PROTOCOL_VERSION} below daemon min_compatible_protocol ${peerFloor}`,
|
||||
reason: UPDATE_EXTENSION_VERSION_MESSAGE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,19 +265,26 @@ retry — complete the task autonomously or stop gracefully.
|
||||
|
||||
### Recording — `bsk record`
|
||||
|
||||
Capture the user's own actions in the Agent Window to a `trace.json`, for later LLM-driven automation:
|
||||
Capture the user's own actions in the Agent Window to a **trace v3 bundle**, for later LLM-driven automation:
|
||||
|
||||
```bash
|
||||
bsk record start --browser <instance-id-or-label> [--url https://…] [--purpose "publish a wiki doc"] [--output trace.json]
|
||||
bsk record start --browser <instance-id-or-label> \
|
||||
[--url https://…] [--purpose "publish a wiki doc"] \
|
||||
[--max-page-tokens 3000] [--redact-values] \
|
||||
[--output trace]
|
||||
# `--url` is optional; default https://example.com/ when omitted (must be http(s)).
|
||||
# Blocks until the user clicks Finish in the recording panel, then writes ./trace.json and closes the window.
|
||||
# Blocks until the user clicks Finish in the recording panel, then writes:
|
||||
# trace/trace.json — action chain + state index (version 3)
|
||||
# trace/pages/ — one `.vom.txt` observe snapshot per settled page state
|
||||
|
||||
bsk record stop [--output trace.json] # terminal fallback if the browser panel is unavailable
|
||||
bsk record stop [--output trace] # terminal fallback if the browser panel is unavailable
|
||||
```
|
||||
|
||||
- The trace is a **record-only action log** (a `pages[]` dictionary + `navigate`/`click`/`fill`/`select`/`press` steps with `target` descriptors). It records *what the user did*; deciding which inputs are variable is left to the executing agent.
|
||||
- **Bundle layout:** `--output` is a directory (default `./trace`) containing `trace.json` and `pages/`. `trace.json` lists `states[]` (page observation ids) and `steps[]` (each step binds `state` = observe snapshot *before* the action and `result.state` = snapshot *after* settle). Page bodies live in `pages/sN.vom.txt` using the same VOM format as `bsk observe`.
|
||||
- Each `states[]` entry is one **settled page observation** (captured after recording start, navigation landing, or action settle — not on a timer).
|
||||
- `target.ref` values like `@e12` exist **only inside** the bundle for disambiguation; do **not** copy `@eN` refs into SKILL.md or agent runbooks — use visible names from the observation text instead.
|
||||
- `--purpose` is optional context metadata; it does **not** change what gets captured.
|
||||
- There is **no** `bsk replay` — to redo a flow, read the trace and reuse the existing `session` / `snapshot` / `@eN` / `click` / `fill` tools. Follow **Stop when the goal is met**.
|
||||
- `--redact-values` masks all form values in page files as `[filled]` / `[empty]`.
|
||||
- Do **not** record on banking/SSO/password-manager pages; passwords are redacted but traces may still contain sensitive text.
|
||||
|
||||
## Error handling
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
"disconnected": "Disconnected",
|
||||
"connecting": "Connecting…",
|
||||
"connected": "Connected",
|
||||
"version_skew": "Protocol mismatch",
|
||||
"version_skew": "Version mismatch",
|
||||
"disabled": "Connection off"
|
||||
},
|
||||
"stateDetail": {
|
||||
"disconnected": "Open BrowserSkill to connect.",
|
||||
"connecting": "Connecting to BrowserSkill…",
|
||||
"connected": "Ready for automation tasks.",
|
||||
"version_skew": "Some features may be unavailable."
|
||||
"version_skew": "bsk and the browser extension differ in version. You can continue using them, but updating is recommended."
|
||||
},
|
||||
"stateBadge": {
|
||||
"disconnected": "Standby",
|
||||
"connecting": "Igniting",
|
||||
"connected": "Ready",
|
||||
"version_skew": "Action needed",
|
||||
"version_skew": "Update recommended",
|
||||
"disabled": "Off"
|
||||
},
|
||||
"connectionToggleTitle": "BrowserSkill connection",
|
||||
@@ -27,6 +27,7 @@
|
||||
"controlHintsToggleHint": "Show the status pill and orange glow while the Agent controls a page.",
|
||||
"controlHintsInfoLabel": "About control hints",
|
||||
"versionSkewWarning": "Extension protocol v{{extensionProtocol}}, daemon v{{daemonProtocol}}.",
|
||||
"versionIncompatibleError": "bsk and the BrowserSkill extension are incompatible. Update both, reload the extension, then run bsk daemon restart.",
|
||||
"upgradeAvailable": "Upgradable",
|
||||
"launcher": {
|
||||
"title": "Quick actions"
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
"disconnected": "未连接",
|
||||
"connecting": "连接中…",
|
||||
"connected": "已连接",
|
||||
"version_skew": "协议不一致",
|
||||
"version_skew": "版本不一致",
|
||||
"disabled": "连接已关闭"
|
||||
},
|
||||
"stateDetail": {
|
||||
"disconnected": "请先打开 BrowserSkill。",
|
||||
"connecting": "正在连接 BrowserSkill…",
|
||||
"connected": "可接收自动化任务。",
|
||||
"version_skew": "部分能力可能不可用。"
|
||||
"version_skew": "bsk 与浏览器扩展版本不同,当前仍可使用,建议更新至最新版本。"
|
||||
},
|
||||
"stateBadge": {
|
||||
"disconnected": "Standby",
|
||||
"connecting": "Igniting",
|
||||
"connected": "Ready",
|
||||
"version_skew": "Action needed",
|
||||
"version_skew": "建议更新",
|
||||
"disabled": "Off"
|
||||
},
|
||||
"connectionToggleTitle": "BrowserSkill 连接",
|
||||
@@ -27,6 +27,7 @@
|
||||
"controlHintsToggleHint": "Agent 控制页面时显示提示条和橙色闪光。",
|
||||
"controlHintsInfoLabel": "控制提示说明",
|
||||
"versionSkewWarning": "扩展协议 v{{extensionProtocol}},daemon 协议 v{{daemonProtocol}}。",
|
||||
"versionIncompatibleError": "bsk 与 BrowserSkill 扩展版本不兼容。请将两者更新至最新版本,重新加载扩展后,再运行 bsk daemon restart。",
|
||||
"upgradeAvailable": "可升级",
|
||||
"launcher": {
|
||||
"title": "快捷功能"
|
||||
|
||||
+13
-6
@@ -265,19 +265,26 @@ retry — complete the task autonomously or stop gracefully.
|
||||
|
||||
### Recording — `bsk record`
|
||||
|
||||
Capture the user's own actions in the Agent Window to a `trace.json`, for later LLM-driven automation:
|
||||
Capture the user's own actions in the Agent Window to a **trace v3 bundle**, for later LLM-driven automation:
|
||||
|
||||
```bash
|
||||
bsk record start --browser <instance-id-or-label> [--url https://…] [--purpose "publish a wiki doc"] [--output trace.json]
|
||||
bsk record start --browser <instance-id-or-label> \
|
||||
[--url https://…] [--purpose "publish a wiki doc"] \
|
||||
[--max-page-tokens 3000] [--redact-values] \
|
||||
[--output trace]
|
||||
# `--url` is optional; default https://example.com/ when omitted (must be http(s)).
|
||||
# Blocks until the user clicks Finish in the recording panel, then writes ./trace.json and closes the window.
|
||||
# Blocks until the user clicks Finish in the recording panel, then writes:
|
||||
# trace/trace.json — action chain + state index (version 3)
|
||||
# trace/pages/ — one `.vom.txt` observe snapshot per settled page state
|
||||
|
||||
bsk record stop [--output trace.json] # terminal fallback if the browser panel is unavailable
|
||||
bsk record stop [--output trace] # terminal fallback if the browser panel is unavailable
|
||||
```
|
||||
|
||||
- The trace is a **record-only action log** (a `pages[]` dictionary + `navigate`/`click`/`fill`/`select`/`press` steps with `target` descriptors). It records *what the user did*; deciding which inputs are variable is left to the executing agent.
|
||||
- **Bundle layout:** `--output` is a directory (default `./trace`) containing `trace.json` and `pages/`. `trace.json` lists `states[]` (page observation ids) and `steps[]` (each step binds `state` = observe snapshot *before* the action and `result.state` = snapshot *after* settle). Page bodies live in `pages/sN.vom.txt` using the same VOM format as `bsk observe`.
|
||||
- Each `states[]` entry is one **settled page observation** (captured after recording start, navigation landing, or action settle — not on a timer).
|
||||
- `target.ref` values like `@e12` exist **only inside** the bundle for disambiguation; do **not** copy `@eN` refs into SKILL.md or agent runbooks — use visible names from the observation text instead.
|
||||
- `--purpose` is optional context metadata; it does **not** change what gets captured.
|
||||
- There is **no** `bsk replay` — to redo a flow, read the trace and reuse the existing `session` / `snapshot` / `@eN` / `click` / `fill` tools. Follow **Stop when the goal is met**.
|
||||
- `--redact-values` masks all form values in page files as `[filled]` / `[empty]`.
|
||||
- Do **not** record on banking/SSO/password-manager pages; passwords are redacted but traces may still contain sensitive text.
|
||||
|
||||
## Error handling
|
||||
|
||||
Reference in New Issue
Block a user