Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

* **browser** — replace the `--session <name>` flag with a `<session>` positional argument that immediately follows `browser`. `opencli browser work click 12` instead of `opencli browser --session work click 12`; `opencli browser work bind` instead of `opencli browser bind --session work`. Required-flag semantics are now encoded structurally as a positional, matching the Docker/git convention for required operation-target identifiers. The internal `--session` flag is preserved for the daemon protocol and for direct `program.parseAsync` callers but is no longer part of the user-facing surface.
* **env** — remove `OPENCLI_KEEP_TAB`. The flag was a debugging shortcut, not a config dimension: `--keep-tab true|false` on the command line is the single source of truth, and adapter `siteSession: 'persistent'` already pins persistent site tabs as a hard constraint. Removing the env eliminates a globally-leaking process state that overrode every browser command in the shell.
* **extension** — remove the internal `surface\\0session` command-session backdoor. Browser Bridge commands now route only through structured `session` + `surface` fields; lease-key strings remain an extension-internal registry detail.

### Internal

* **extension 1.0.13** — remove the internal command-session lease-key backdoor.

## [1.7.18](https://github.com/jackwener/opencli/compare/v1.7.17...v1.7.18) (2026-05-12)

Expand Down
7 changes: 2 additions & 5 deletions extension/dist/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -785,14 +785,11 @@ function getSessionName(session) {
if (!raw) throw new CommandFailure(
"session_required",
"Browser session is required.",
"Pass --session <name> with opencli browser commands."
"Pass a browser session name, e.g. opencli browser <session> <command>."
);
return raw.includes(LEASE_KEY_SEPARATOR) ? getSessionFromKey(raw) : raw;
return raw;
}
function getCommandSurface(cmd) {
if (typeof cmd.session === "string" && cmd.session.includes(LEASE_KEY_SEPARATOR)) {
return getSurfaceFromKey(cmd.session);
}
return cmd.surface === "adapter" ? "adapter" : "browser";
}
function getSurfaceFromKey(key) {
Expand Down
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "OpenCLI",
"version": "1.0.12",
"version": "1.0.13",
"description": "Browser automation bridge for the OpenCLI CLI tool. Executes commands in Chrome tab leases via a local daemon.",
"permissions": [
"debugger",
Expand Down
4 changes: 2 additions & 2 deletions extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opencli-extension",
"version": "1.0.12",
"version": "1.0.13",
"private": true,
"opencli": {
"compatRange": ">=1.7.0"
Expand Down
87 changes: 79 additions & 8 deletions extension/src/background.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,69 @@ describe('background tab isolation', () => {
]);
});

it('does not parse lease-key separators from command session fields', async () => {
const { chrome } = createChromeMock();
vi.stubGlobal('chrome', chrome);

const mod = await import('./background');

expect(mod.__test__.getSessionName(adapterKey('twitter'))).toBe(adapterKey('twitter'));
expect(mod.__test__.getCommandSurface({ session: adapterKey('twitter') })).toBe('browser');
expect(mod.__test__.getCommandSurface({ session: browserKey('work'), surface: 'adapter' })).toBe('adapter');
});

it('routes structured command session and surface fields without encoded lease keys', async () => {
const { chrome } = createChromeMock();
chrome.debugger.sendCommand = vi.fn(async () => ({}));
vi.stubGlobal('chrome', chrome);

const mod = await import('./background');
mod.__test__.setAutomationWindowId(adapterKey('twitter'), 1);

const result = await mod.__test__.handleCommand({
id: 'structured-cdp',
action: 'cdp',
session: 'twitter',
surface: 'adapter',
cdpMethod: 'Accessibility.enable',
cdpParams: {},
});

expect(result).toEqual(expect.objectContaining({ ok: true }));
expect(chrome.debugger.sendCommand).toHaveBeenCalledWith(
{ tabId: 1 },
'Accessibility.enable',
{},
);
});

it('does not route encoded adapter lease keys through the command session backdoor', async () => {
const { chrome } = createChromeMock();
chrome.debugger.sendCommand = vi.fn(async () => ({}));
vi.stubGlobal('chrome', chrome);

const mod = await import('./background');
mod.__test__.setAutomationWindowId(adapterKey('twitter'), 1);

const result = await mod.__test__.handleCommand({
id: 'encoded-session',
action: 'cdp',
session: adapterKey('twitter'),
cdpMethod: 'Accessibility.enable',
cdpParams: {},
});

expect(result).toEqual(expect.objectContaining({ ok: true }));
expect(mod.__test__.getSession(adapterKey('twitter'))).toEqual(expect.objectContaining({
surface: 'adapter',
session: 'twitter',
}));
expect(mod.__test__.getSession(browserKey(adapterKey('twitter')))).toEqual(expect.objectContaining({
surface: 'browser',
session: adapterKey('twitter'),
}));
});

it('allows Accessibility.enable through the guarded CDP passthrough', async () => {
const { chrome } = createChromeMock();
chrome.debugger.sendCommand = vi.fn(async () => ({}));
Expand Down Expand Up @@ -943,7 +1006,8 @@ describe('background tab isolation', () => {
id: 'new-foreground',
action: 'tabs',
op: 'new',
session: adapterKey('twitter'),
session: 'twitter',
surface: 'adapter',
url: 'https://x.com',
windowMode: 'foreground',
});
Expand Down Expand Up @@ -1259,7 +1323,8 @@ describe('background tab isolation', () => {
const result = await mod.__test__.handleCommand({
id: 'close-1',
action: 'close-window',
session: browserKey('default'),
session: 'default',
surface: 'browser',
});

expect(result.ok).toBe(true);
Expand All @@ -1280,7 +1345,8 @@ describe('background tab isolation', () => {
await mod.__test__.handleCommand({
id: 'custom-1',
action: 'cookies',
session: browserKey('default'),
session: 'default',
surface: 'browser',
domain: 'example.com',
idleTimeout: 120,
});
Expand Down Expand Up @@ -1409,7 +1475,8 @@ describe('background tab isolation', () => {
const result = await mod.__test__.handleCommand({
id: 'bound-close',
action: 'close-window',
session: browserKey('default'),
session: 'default',
surface: 'browser',
});

expect(result).toEqual(expect.objectContaining({ ok: true }));
Expand Down Expand Up @@ -1443,7 +1510,8 @@ describe('background tab isolation', () => {
const result = await mod.__test__.handleCommand({
id: 'bound-exec-gone',
action: 'exec',
session: browserKey('default'),
session: 'default',
surface: 'browser',
code: 'document.title',
});

Expand All @@ -1465,7 +1533,8 @@ describe('background tab isolation', () => {
const result = await mod.__test__.handleCommand({
id: 'bound-exec-undebuggable',
action: 'exec',
session: browserKey('default'),
session: 'default',
surface: 'browser',
code: 'document.title',
});

Expand All @@ -1492,13 +1561,15 @@ describe('background tab isolation', () => {
const nav = await mod.__test__.handleCommand({
id: 'bound-nav',
action: 'navigate',
session: browserKey('default'),
session: 'default',
surface: 'browser',
url: 'https://other.example',
});
const tabNew = await mod.__test__.handleCommand({
id: 'bound-tab-new',
action: 'tabs',
session: browserKey('default'),
session: 'default',
surface: 'browser',
op: 'new',
url: 'https://other.example',
});
Expand Down
9 changes: 4 additions & 5 deletions extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,12 @@ function getSessionName(session?: string): string {
if (!raw) throw new CommandFailure(
'session_required',
'Browser session is required.',
'Pass --session <name> with opencli browser commands.',
'Pass a browser session name, e.g. opencli browser <session> <command>.',
);
return raw.includes(LEASE_KEY_SEPARATOR) ? getSessionFromKey(raw) : raw;
return raw;
}

function getCommandSurface(cmd: Pick<Command, 'surface' | 'session'>): BrowserSurface {
if (typeof cmd.session === 'string' && cmd.session.includes(LEASE_KEY_SEPARATOR)) {
return getSurfaceFromKey(cmd.session);
}
return cmd.surface === 'adapter' ? 'adapter' : 'browser';
}

Expand Down Expand Up @@ -1715,6 +1712,8 @@ export const __test__ = {
resolveTabId,
resetWindowIdleTimer,
handleCommand,
getSessionName,
getCommandSurface,
getIdleTimeout,
getLeaseKey,
sessionTimeoutOverrides,
Expand Down
Loading