Skip to content

feat(extension): honor initial_url for group_id="new" — eliminate about:blank placeholder on lane creation #52

Description

@apireno

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" + initialUrlinitialUrl 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions