Skip to content
Open
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
32 changes: 14 additions & 18 deletions packages/app/src/new-session/composer-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { base64Encode } from "@opencode-ai/util/encode"
import { getDirectory } from "@opencode-ai/util/path"
import { startTransition } from "solid-js"
import type { NewSessionComposerAdapter } from "@/composer/adapter"
import { useComposerState } from "@/composer/persistence"
import { createComposerControls, createComposerModelSelection } from "@/composer/selection"
Expand Down Expand Up @@ -72,24 +71,21 @@ export function createNewSessionComposerAdapter(props: {
if (!created) return

data.session.remember(created)
await startTransition(() => {
tabs.updateDraft(props.draftID, { worktree: undefined })
if (permission.isAutoAcceptingDirectory(projectDirectory)) {
permission.enableAutoAccept(created.id, sessionDirectory)
}
local.session.promote(sessionDirectory, created.id, {
agent: selection.agent,
model: selection.model,
variant: selection.variant ?? null,
})
tabs.promoteDraft(props.draftID, { server: server.key, sessionId: created.id })
submission.retarget(
prompt.capture(
{ dir: base64Encode(sessionDirectory), id: created.id },
{ server: server.key, scope: serverSDK.scope },
),
)
if (permission.isAutoAcceptingDirectory(projectDirectory)) {
permission.enableAutoAccept(created.id, sessionDirectory)
}
local.session.promote(sessionDirectory, created.id, {
agent: selection.agent,
model: selection.model,
variant: selection.variant ?? null,
})
await tabs.promoteDraft(props.draftID, { server: server.key, sessionId: created.id })
submission.retarget(
prompt.capture(
{ dir: base64Encode(sessionDirectory), id: created.id },
{ server: server.key, scope: serverSDK.scope },
),
)

return {
id: created.id,
Expand Down
14 changes: 8 additions & 6 deletions packages/app/src/shell/routes/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Route, useParams } from "@solidjs/router"
import { Navigate, Route, useParams } from "@solidjs/router"
import { createMemo, lazy, Show, type ParentProps } from "solid-js"
import { Home } from "@/home/route"
import { ServerProvider } from "@/runtime/server/current"
import { useGlobal } from "@/runtime/server/runtime"
import { ServerConnection } from "@/runtime/server/registry"
import { LayoutProvider } from "@/shell/state/layout"
import Shell from "@/shell/shell"
import { requireServerKey } from "./session"
import { parseServerKey } from "./session"

export const File = lazy(() => import("@opencode-ai/session-ui/file").then((module) => ({ default: module.File })))
const loadDraftRoute = () => Promise.all([import("@/new-session/route"), File.preload()]).then(([module]) => module)
Expand Down Expand Up @@ -44,12 +44,14 @@ export function AppRoutes() {
function TargetServerRoute(props: ParentProps) {
const params = useParams<{ serverKey: string }>()
const global = useGlobal()
const connection = createMemo(() =>
global.servers.list().find((item) => ServerConnection.key(item) === requireServerKey(params.serverKey)),
)
const connection = createMemo(() => {
const key = parseServerKey(params.serverKey)
if (!key) return
return global.servers.list().find((item) => ServerConnection.key(item) === key)
})

return (
<Show when={connection()} keyed>
<Show when={connection()} keyed fallback={<Navigate href="/" />}>
{(connection) => <ServerProvider conn={connection}>{props.children}</ServerProvider>}
</Show>
)
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/shell/routes/session.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { ServerConnection } from "@/runtime/server/registry"
import { requireServerKey, rootSession, sessionHref } from "./session"
import { parseServerKey, requireServerKey, rootSession, sessionHref } from "./session"

describe("session routes", () => {
test("builds and decodes a server-keyed session route", () => {
Expand All @@ -12,6 +12,8 @@ describe("session routes", () => {
})

test("rejects malformed server keys", () => {
expect(parseServerKey(undefined)).toBeUndefined()
expect(parseServerKey("not-base64")).toBeUndefined()
expect(() => requireServerKey("not-base64")).toThrow("Invalid server route")
})

Expand Down
8 changes: 7 additions & 1 deletion packages/app/src/shell/routes/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ export function sessionHref(server: ServerConnection.Key, sessionID: string) {
}

export function requireServerKey(segment: string | undefined) {
const key = parseServerKey(segment)
if (!key) throw new Error("Invalid server route")
return key
}

export function parseServerKey(segment: string | undefined) {
const key = decode64(segment)
if (!key || base64Encode(key) !== segment) throw new Error("Invalid server route")
if (!key || base64Encode(key) !== segment) return
return ServerConnection.Key.make(key)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/shell/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,12 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
)
})
},
promoteDraft(draftID: string, session: Omit<SessionTab, "type">) {
async promoteDraft(draftID: string, session: Omit<SessionTab, "type">) {
// Keep the replacement and navigation atomic so /new-session never renders
// after its backing draft tab has been removed from the store.
const active = location.pathname === "/new-session" && location.query.draftId === draftID
const next = { type: "session" as const, ...session }
void startTransition(() => {
await startTransition(() => {
setStore(
produce((tabs) => {
const index = tabs.findIndex((tab) => tab.type === "draft" && tab.draftID === draftID)
Expand Down
Loading