From 4e26b0aec7052b9daf1f1d26906198775cd3ad66 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 5 Mar 2026 17:15:14 +0800 Subject: desktop: new-session deeplink (#15322) --- packages/app/src/pages/layout/deep-links.ts | 32 +++++++++++++++------ packages/app/src/pages/layout/helpers.test.ts | 41 ++++++++++++++++++++------- 2 files changed, 55 insertions(+), 18 deletions(-) (limited to 'packages/app/src/pages/layout') diff --git a/packages/app/src/pages/layout/deep-links.ts b/packages/app/src/pages/layout/deep-links.ts index 7bdb002a3..5dca421f7 100644 --- a/packages/app/src/pages/layout/deep-links.ts +++ b/packages/app/src/pages/layout/deep-links.ts @@ -1,15 +1,17 @@ export const deepLinkEvent = "opencode:deep-link" -export const parseDeepLink = (input: string) => { +const parseUrl = (input: string) => { if (!input.startsWith("opencode://")) return if (typeof URL.canParse === "function" && !URL.canParse(input)) return - const url = (() => { - try { - return new URL(input) - } catch { - return undefined - } - })() + try { + return new URL(input) + } catch { + return + } +} + +export const parseDeepLink = (input: string) => { + const url = parseUrl(input) if (!url) return if (url.hostname !== "open-project") return const directory = url.searchParams.get("directory") @@ -17,9 +19,23 @@ export const parseDeepLink = (input: string) => { return directory } +export const parseNewSessionDeepLink = (input: string) => { + const url = parseUrl(input) + if (!url) return + if (url.hostname !== "new-session") return + const directory = url.searchParams.get("directory") + if (!directory) return + const prompt = url.searchParams.get("prompt") || undefined + if (!prompt) return { directory } + return { directory, prompt } +} + export const collectOpenProjectDeepLinks = (urls: string[]) => urls.map(parseDeepLink).filter((directory): directory is string => !!directory) +export const collectNewSessionDeepLinks = (urls: string[]) => + urls.map(parseNewSessionDeepLink).filter((link): link is { directory: string; prompt?: string } => !!link) + type OpenCodeWindow = Window & { __OPENCODE__?: { deepLinks?: string[] diff --git a/packages/app/src/pages/layout/helpers.test.ts b/packages/app/src/pages/layout/helpers.test.ts index 29517b624..d1569dbd9 100644 --- a/packages/app/src/pages/layout/helpers.test.ts +++ b/packages/app/src/pages/layout/helpers.test.ts @@ -1,15 +1,14 @@ import { describe, expect, test } from "bun:test" -import { type Session } from "@opencode-ai/sdk/v2/client" -import { collectOpenProjectDeepLinks, drainPendingDeepLinks, parseDeepLink } from "./deep-links" import { - displayName, - errorMessage, - getDraggableId, - hasProjectPermissions, - latestRootSession, - syncWorkspaceOrder, - workspaceKey, -} from "./helpers" + collectNewSessionDeepLinks, + collectOpenProjectDeepLinks, + drainPendingDeepLinks, + parseDeepLink, + parseNewSessionDeepLink, +} from "./deep-links" +import { displayName, errorMessage, getDraggableId, syncWorkspaceOrder, workspaceKey } from "./helpers" +import { type Session } from "@opencode-ai/sdk/v2/client" +import { hasProjectPermissions, latestRootSession } from "./helpers" const session = (input: Partial & Pick) => ({ @@ -62,6 +61,28 @@ describe("layout deep links", () => { expect(result).toEqual(["/a", "/c"]) }) + test("parses new-session deep links with optional prompt", () => { + expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo")).toEqual({ directory: "/tmp/demo" }) + expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo&prompt=hello%20world")).toEqual({ + directory: "/tmp/demo", + prompt: "hello world", + }) + }) + + test("ignores new-session deep links without directory", () => { + expect(parseNewSessionDeepLink("opencode://new-session")).toBeUndefined() + expect(parseNewSessionDeepLink("opencode://new-session?directory=")).toBeUndefined() + }) + + test("collects only valid new-session deep links", () => { + const result = collectNewSessionDeepLinks([ + "opencode://new-session?directory=/a", + "opencode://open-project?directory=/b", + "opencode://new-session?directory=/c&prompt=ship%20it", + ]) + expect(result).toEqual([{ directory: "/a" }, { directory: "/c", prompt: "ship it" }]) + }) + test("drains global deep links once", () => { const target = { __OPENCODE__: { -- cgit v1.2.3