browser-pilot-cli 0.3.0-rc.1 → 0.3.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ A project can pin Browser Pilot locally and run the same CLI without a global
|
|
|
182
182
|
installation:
|
|
183
183
|
|
|
184
184
|
```bash
|
|
185
|
-
npm install --save-exact browser-pilot-cli@0.3.0-rc.
|
|
185
|
+
npm install --save-exact browser-pilot-cli@0.3.0-rc.2
|
|
186
186
|
npx --no-install browser-pilot tabs
|
|
187
187
|
```
|
|
188
188
|
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { writeFileSync as writeFileSync2, readFileSync as readFileSync4, existsS
|
|
|
6
6
|
import { resolve as resolvePath } from "path";
|
|
7
7
|
|
|
8
8
|
// src/version.ts
|
|
9
|
-
var BROWSER_PILOT_VERSION = "0.3.0-rc.
|
|
9
|
+
var BROWSER_PILOT_VERSION = "0.3.0-rc.2";
|
|
10
10
|
|
|
11
11
|
// src/protocol/errors.ts
|
|
12
12
|
var ERROR_CODES = [
|
package/dist/daemon.js
CHANGED
|
@@ -11753,13 +11753,13 @@ var BrowserToolService = class {
|
|
|
11753
11753
|
await this.transport.send("Target.closeTarget", { targetId });
|
|
11754
11754
|
} catch {
|
|
11755
11755
|
}
|
|
11756
|
-
for (let attempt = 0; attempt <
|
|
11756
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
11757
11757
|
try {
|
|
11758
11758
|
const current = await this.transport.send("Target.getTargets");
|
|
11759
11759
|
if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
|
|
11760
11760
|
} catch {
|
|
11761
11761
|
}
|
|
11762
|
-
if (attempt <
|
|
11762
|
+
if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11763
11763
|
}
|
|
11764
11764
|
return false;
|
|
11765
11765
|
}
|
|
@@ -11861,9 +11861,9 @@ var BrowserToolService = class {
|
|
|
11861
11861
|
context: { profileContextId: profile.id }
|
|
11862
11862
|
});
|
|
11863
11863
|
}
|
|
11864
|
-
for (let attempt = 0; attempt <
|
|
11864
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
11865
11865
|
const current = await this.transport.send("Target.getTargets");
|
|
11866
|
-
const markerTargets = Array.isArray(current?.targetInfos) ? current.targetInfos.filter((target) => target && typeof target.targetId === "string" && !existing.has(target.targetId) && target.type === "page" && target.
|
|
11866
|
+
const markerTargets = Array.isArray(current?.targetInfos) ? current.targetInfos.filter((target) => target && typeof target.targetId === "string" && !existing.has(target.targetId) && target.type === "page" && target.url === marker) : [];
|
|
11867
11867
|
for (const target of markerTargets) spawnedTargetIds.add(target.targetId);
|
|
11868
11868
|
if (markerTargets.length > 1) {
|
|
11869
11869
|
throw new BrowserPilotError("profile_context_unavailable", "Chrome created ambiguous managed window targets", {
|
|
@@ -11889,6 +11889,21 @@ var BrowserToolService = class {
|
|
|
11889
11889
|
}
|
|
11890
11890
|
);
|
|
11891
11891
|
}
|
|
11892
|
+
if (candidate.openerId !== representative.cdpTargetId && !await this.targetWindowNameMatches(candidate.targetId, windowName)) {
|
|
11893
|
+
throw new BrowserPilotError(
|
|
11894
|
+
"profile_context_unavailable",
|
|
11895
|
+
"Chrome could not prove ownership of the managed Profile window",
|
|
11896
|
+
{
|
|
11897
|
+
retryable: true,
|
|
11898
|
+
context: { profileContextId: profile.id },
|
|
11899
|
+
remediation: {
|
|
11900
|
+
code: "relist_profile_contexts",
|
|
11901
|
+
message: "List Profile contexts again and retry with a current Profile context.",
|
|
11902
|
+
actionRequired: true
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
);
|
|
11906
|
+
}
|
|
11892
11907
|
candidateTargetId = candidate.targetId;
|
|
11893
11908
|
break;
|
|
11894
11909
|
}
|
|
@@ -11912,6 +11927,37 @@ var BrowserToolService = class {
|
|
|
11912
11927
|
if (cleanup2.some((closed) => !closed)) throw this.managedTargetUnknownOutcome(profile);
|
|
11913
11928
|
}
|
|
11914
11929
|
}
|
|
11930
|
+
async targetWindowNameMatches(targetId, expected) {
|
|
11931
|
+
let sessionId;
|
|
11932
|
+
try {
|
|
11933
|
+
const attached = await this.transport.send("Target.attachToTarget", {
|
|
11934
|
+
targetId,
|
|
11935
|
+
flatten: true
|
|
11936
|
+
});
|
|
11937
|
+
if (typeof attached?.sessionId !== "string") return false;
|
|
11938
|
+
sessionId = attached.sessionId;
|
|
11939
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
11940
|
+
try {
|
|
11941
|
+
const result = await this.transport.send("Runtime.evaluate", {
|
|
11942
|
+
expression: "window.name",
|
|
11943
|
+
returnByValue: true
|
|
11944
|
+
}, sessionId);
|
|
11945
|
+
if (result?.result?.value === expected) return true;
|
|
11946
|
+
if (result?.result?.value !== void 0) return false;
|
|
11947
|
+
} catch {
|
|
11948
|
+
}
|
|
11949
|
+
if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
11950
|
+
}
|
|
11951
|
+
return false;
|
|
11952
|
+
} catch {
|
|
11953
|
+
return false;
|
|
11954
|
+
} finally {
|
|
11955
|
+
if (sessionId) {
|
|
11956
|
+
await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
|
|
11957
|
+
});
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11915
11961
|
async resolveTargetSession(context, targetId, operation) {
|
|
11916
11962
|
const inventoryContext = this.inventoryContext(context);
|
|
11917
11963
|
await this.inventory.refresh(inventoryContext);
|
|
@@ -13943,7 +13989,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13943
13989
|
};
|
|
13944
13990
|
|
|
13945
13991
|
// src/version.ts
|
|
13946
|
-
var BROWSER_PILOT_VERSION = "0.3.0-rc.
|
|
13992
|
+
var BROWSER_PILOT_VERSION = "0.3.0-rc.2";
|
|
13947
13993
|
|
|
13948
13994
|
// src/daemon.ts
|
|
13949
13995
|
var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
|
|
@@ -615,7 +615,9 @@ Artifacts. Raw CDP is never listed. `eval` requires `developer.eval`.
|
|
|
615
615
|
- **BR-32:** Every ManagedTabSet is bound to at most one ProfileContext. Managed
|
|
616
616
|
target creation verifies the returned context before public registration, and
|
|
617
617
|
fallback-created targets enter janitor ownership before they become
|
|
618
|
-
addressable.
|
|
618
|
+
addressable. Fallback ownership requires the exact new URL marker plus either
|
|
619
|
+
the expected opener ID or exact readback of a per-command random window name;
|
|
620
|
+
Profile membership alone is never sufficient.
|
|
619
621
|
- **BR-33:** Switching to an existing target may update the owning Workspace's
|
|
620
622
|
selected ProfileContext, but never prevents inventory or control of targets
|
|
621
623
|
in other Profiles. No Profile selection is a permission grant.
|
|
@@ -138,11 +138,13 @@ managed-target janitor:
|
|
|
138
138
|
varies by regular Profile context.
|
|
139
139
|
3. If Chrome rejects or misroutes that call, attach to a bounded representative
|
|
140
140
|
page target in the selected context, create an isolated world, and call
|
|
141
|
-
`window.open(
|
|
142
|
-
debugger user gesture.
|
|
143
|
-
4. Identify exactly one new target by the pre-dispatch target set,
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
`window.open(<unique blank marker>, <unique window name>, <fixed popup window
|
|
142
|
+
features>)` with a debugger user gesture.
|
|
143
|
+
4. Identify exactly one new page target by the pre-dispatch target set, unique
|
|
144
|
+
URL marker, and Profile context. Prove ownership through either the expected
|
|
145
|
+
opener ID or exact readback of the per-command random `window.name`; Chrome
|
|
146
|
+
may normalize the reported opener across regular Profile tabs. Ambiguous or
|
|
147
|
+
unverified targets are closed and the operation fails.
|
|
146
148
|
5. Explicitly adopt the verified target into the janitor before registering it
|
|
147
149
|
publicly, so Broker crash cleanup still closes it.
|
|
148
150
|
6. Read and retain its window ID, bind the ManagedTabSet, register the opaque
|
|
@@ -177,7 +179,7 @@ candidate targets were closed.
|
|
|
177
179
|
- [x] P4: update stdio integration guidance, adapters, skill, and release docs.
|
|
178
180
|
- [x] P5: add protocol, dual-Profile, concurrency, reconnect, cleanup, and
|
|
179
181
|
compatibility tests.
|
|
180
|
-
- [ ] P6: run real Chrome acceptance and publish `v0.3.0-rc.
|
|
182
|
+
- [ ] P6: run real Chrome acceptance and publish `v0.3.0-rc.2`.
|
|
181
183
|
|
|
182
184
|
## Acceptance
|
|
183
185
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Browser Pilot 0.3.0-rc.2
|
|
2
|
+
|
|
3
|
+
This release candidate fixes a real-Chrome edge case found while accepting the
|
|
4
|
+
multi-Profile routing introduced in `0.3.0-rc.1`.
|
|
5
|
+
|
|
6
|
+
Some regular Chrome Profile tabs create a `window.open` target whose
|
|
7
|
+
`TargetInfo.openerId` differs from the page target used for the isolated-world
|
|
8
|
+
dispatch. Chrome still returns the exact per-command URL marker, Profile
|
|
9
|
+
context, and random window name. RC.1 correctly refused to register that target
|
|
10
|
+
but could leave the unregistered blank marker visible until the caller cleaned
|
|
11
|
+
up the reported `unknown_outcome`.
|
|
12
|
+
|
|
13
|
+
RC.2 keeps fail-closed ownership verification without relying on opener IDs
|
|
14
|
+
alone. A fallback-created target must be a new page with the exact random URL
|
|
15
|
+
marker and selected Profile context, then prove ownership through either the
|
|
16
|
+
expected opener ID or exact readback of the per-command random `window.name`.
|
|
17
|
+
Profile membership by itself is never accepted.
|
|
18
|
+
|
|
19
|
+
All marker candidates are now tracked before validation, target discovery waits
|
|
20
|
+
through a bounded five-second Chrome metadata window, and failure cleanup waits
|
|
21
|
+
for asynchronous target destruction to become observable. A target that fails
|
|
22
|
+
both ownership proofs is closed and never registered or adopted.
|
|
23
|
+
|
|
24
|
+
The regression suite covers both paths: a Chrome-normalized opener with a valid
|
|
25
|
+
window-name proof succeeds, while a mismatched opener and mismatched window name
|
|
26
|
+
fails closed and leaves no marker target.
|
|
27
|
+
|
|
28
|
+
Automated release gates completed before tagging:
|
|
29
|
+
|
|
30
|
+
- TypeScript type checking passed.
|
|
31
|
+
- 266 Node unit, protocol, process, and adapter tests passed.
|
|
32
|
+
- 109 isolated Playwright core, compatibility, and network tests passed.
|
|
33
|
+
- The black-box stdio conformance suite passed all 13 checks.
|
|
34
|
+
- npm distribution verification passed for global, local project, and
|
|
35
|
+
product-bundled executable modes.
|
|
36
|
+
- The npm dry-run package contains both Profile RC notes and the Profile routing
|
|
37
|
+
plan.
|
|
38
|
+
|
|
39
|
+
The RC.1 real-Chrome run used one authorization and verified three live Profile
|
|
40
|
+
contexts, cross-Profile user-tab inventory, existing-tab control in two
|
|
41
|
+
Profiles, and managed creation in the direct-create Profile before exposing this
|
|
42
|
+
fallback defect. RC.2 is accepted against the same Chrome setup after installing
|
|
43
|
+
the published package.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-pilot-cli",
|
|
3
|
-
"version": "0.3.0-rc.
|
|
3
|
+
"version": "0.3.0-rc.2",
|
|
4
4
|
"description": "CLI tool to control your browser via Chrome DevTools Protocol",
|
|
5
5
|
"repository": "https://github.com/relixiaobo/browser-pilot",
|
|
6
6
|
"type": "module",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"docs/plans/profile-context-routing.md",
|
|
44
44
|
"docs/plans/universal-agent-integration.md",
|
|
45
45
|
"docs/releases/v0.3.0-rc.1.md",
|
|
46
|
+
"docs/releases/v0.3.0-rc.2.md",
|
|
46
47
|
"README.md",
|
|
47
48
|
"LICENSE"
|
|
48
49
|
],
|