Compare commits

...
5 changed files with 34 additions and 28 deletions
@@ -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"
@@ -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,
+8 -6
View File
@@ -1,4 +1,4 @@
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"
@@ -6,7 +6,7 @@ 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)
@@ -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>
)
@@ -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", () => {
@@ -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")
})
+7 -1
View File
@@ -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)
}
+2 -2
View File
@@ -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)