Goal
Eliminate the about:blank placeholder tab that gets created on group_id="new" lane creation when the agent already knows the URL it wants to start at. Make the fresh lane's working tab navigate directly to the target URL.
Background
Today, group_id="new" triggers createAgentLane() → groupNew(["agent"]) which calls chrome.tabs.create({ url: "about:blank", active: false }) to get a tab Chrome will let us group. Then the agent's first open <url> call creates a second tab in the group with the actual URL — and the about:blank tab sits there for the rest of the lane's life.
The MCP server side of the fix shipped in @apireno/domshell@2.0.4 (commit TODO; see CHANGELOG MCP 2.0.4 entry). The server now accepts a new initial_url parameter on domshell_execute and forwards it to the kernel as a new field on the WS EXECUTE message:
{
"type": "EXECUTE",
"id": "...",
"sessionId": "...",
"groupId": "new",
"initialUrl": "https://example.com/article", // <- new
"command": "text main"
}
This issue tracks the kernel side. Extension 1.3.1 silently ignores msg.initialUrl (the JSON field travels but no code reads it — verified, see src/background/index.ts:579+). Extension 1.3.2 should honor it and produce the optimised behaviour.
Implementation sketch
1. groupNew accepts a --url flag
src/background/index.ts around line 3653. Parse an optional --url <url> from rest before the chrome.tabs.create call:
async function groupNew(rest: string[]): Promise<string> {
// Parse --url <url> ahead of name extraction so the URL doesn't leak into the group name
let workingUrl = "about:blank";
const urlIdx = rest.indexOf("--url");
if (urlIdx !== -1 && rest[urlIdx + 1]) {
workingUrl = rest[urlIdx + 1];
rest = [...rest.slice(0, urlIdx), ...rest.slice(urlIdx + 2)];
}
// ... existing previousGroupId / name logic ...
const tab = await chrome.tabs.create({ url: workingUrl, active: false });
// ... rest unchanged ...
// Wait for navigation to complete before returning, so the AX tree is ready
// by the time the agent's first command runs. (Match the open command's load-wait shape.)
if (workingUrl !== "about:blank" && tab.id) {
await new Promise<void>((resolve) => {
const listener = (id: number, info: { status?: string }) => {
if (id === tab.id && info.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}
};
chrome.tabs.onUpdated.addListener(listener);
setTimeout(() => { chrome.tabs.onUpdated.removeListener(listener); resolve(); }, 5000);
});
// Auto-cd into the loaded tab so state.path = ["tabs", String(tab.id)]
// and the agent's first command runs against the loaded page.
state.path = ["tabs", String(tab.id!)];
} else {
state.path = ["tabs"]; // existing about:blank behaviour
}
// ...
}
2. createAgentLane plumbs the URL through
src/background/index.ts around line 192:
async function createAgentLane(initialUrl?: string): Promise<void> {
// ... existing swap-to-temp-session logic ...
const args = initialUrl ? ["--url", initialUrl, "agent"] : ["agent"];
await groupNew(args);
// ... existing re-key logic ...
}
3. WS EXECUTE handler extracts and forwards
src/background/index.ts around line 583:
if (msg.groupId === "new") {
await createAgentLane(typeof msg.initialUrl === "string" ? msg.initialUrl : undefined);
} else if (typeof msg.groupId === "string" && msg.groupId) {
// unchanged — initialUrl is only meaningful when groupId === "new"
}
Tests
groupNew(["--url", "https://example.com", "agent"]) creates a tab with example.com loaded (waits for status === complete), sets state.path = ["tabs", <id>], no about:blank tab in the group
groupNew(["agent"]) (no --url) preserves existing about:blank + state.path = ["tabs"] behaviour exactly
- WS EXECUTE with
groupId: "new" + initialUrl: "https://..." → lane created with URL loaded
- WS EXECUTE with
groupId: "new" + no initialUrl → existing about:blank behaviour
- WS EXECUTE with
groupId: "shared" + initialUrl → initialUrl ignored (lane resolution doesn't run createAgentLane)
- Malformed
initialUrl (e.g. unparseable URL) → tab still created (Chrome handles the bad URL gracefully — shows an error page); kernel doesn't reject
Sequencing
This is queued for the next bundled extension 1.3.2 release alongside other kernel-side items:
One Chrome Web Store submission for the bundle.
Why ship the server side first?
- New WS message field
initialUrl is silently dropped by extension 1.3.1 (verified — see src/background/index.ts:579+, no code touches the field), so the field travels harmlessly
- Eager agents can pass
initial_url today without breaking anything; they automatically get the optimised behaviour the moment 1.3.2 lands
- Decouples the release cadence: the MCP server can iterate on parameter design without waiting for the CWS gate
CLI-Anything coordination
Zero required. The parameter is optional; their current flow doesn't pass it and keeps working unchanged. If they want it later for their page open on a fresh REPL, that's a future opt-in PR on their side, not a coordinated release with DOMShell.
Goal
Eliminate the
about:blankplaceholder tab that gets created ongroup_id="new"lane creation when the agent already knows the URL it wants to start at. Make the fresh lane's working tab navigate directly to the target URL.Background
Today,
group_id="new"triggerscreateAgentLane()→groupNew(["agent"])which callschrome.tabs.create({ url: "about:blank", active: false })to get a tab Chrome will let us group. Then the agent's firstopen <url>call creates a second tab in the group with the actual URL — and theabout:blanktab sits there for the rest of the lane's life.The MCP server side of the fix shipped in
@apireno/domshell@2.0.4(commit TODO; see CHANGELOG MCP 2.0.4 entry). The server now accepts a newinitial_urlparameter ondomshell_executeand forwards it to the kernel as a new field on the WSEXECUTEmessage:{ "type": "EXECUTE", "id": "...", "sessionId": "...", "groupId": "new", "initialUrl": "https://example.com/article", // <- new "command": "text main" }This issue tracks the kernel side. Extension 1.3.1 silently ignores
msg.initialUrl(the JSON field travels but no code reads it — verified, seesrc/background/index.ts:579+). Extension 1.3.2 should honor it and produce the optimised behaviour.Implementation sketch
1.
groupNewaccepts a--urlflagsrc/background/index.tsaround line 3653. Parse an optional--url <url>fromrestbefore thechrome.tabs.createcall:2.
createAgentLaneplumbs the URL throughsrc/background/index.tsaround line 192:3. WS
EXECUTEhandler extracts and forwardssrc/background/index.tsaround line 583:Tests
groupNew(["--url", "https://example.com", "agent"])creates a tab withexample.comloaded (waits forstatus === complete), setsstate.path = ["tabs", <id>], noabout:blanktab in the groupgroupNew(["agent"])(no--url) preserves existingabout:blank+state.path = ["tabs"]behaviour exactlygroupId: "new"+initialUrl: "https://..."→ lane created with URL loadedgroupId: "new"+ noinitialUrl→ existingabout:blankbehaviourgroupId: "shared"+initialUrl→initialUrlignored (lane resolution doesn't runcreateAgentLane)initialUrl(e.g. unparseable URL) → tab still created (Chrome handles the bad URL gracefully — shows an error page); kernel doesn't rejectSequencing
This is queued for the next bundled extension
1.3.2release alongside other kernel-side items:createAgentLanesilently swallowsgroupNewfailure--end-of-options separator (and consider-e <pattern>for grep) #49 —parseArgs --end-of-options--urlforgroupNew(paired with MCP 2.0.4's already-shippedinitial_urlparameter)One Chrome Web Store submission for the bundle.
Why ship the server side first?
initialUrlis silently dropped by extension 1.3.1 (verified — seesrc/background/index.ts:579+, no code touches the field), so the field travels harmlesslyinitial_urltoday without breaking anything; they automatically get the optimised behaviour the moment 1.3.2 landsCLI-Anything coordination
Zero required. The parameter is optional; their current flow doesn't pass it and keeps working unchanged. If they want it later for their
page openon a fresh REPL, that's a future opt-in PR on their side, not a coordinated release with DOMShell.