feat(app): scope sdk/sync hooks per-route so /new-session targets its draft server (#32290)
This commit is contained in:
+91
-48
@@ -69,8 +69,8 @@ const SessionRoute = Object.assign(
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!settings.general.newLayoutDesigns()) return
|
if (!settings.general.newLayoutDesigns()) return
|
||||||
if (params.id || search.draftId) return
|
if (params.id || search.draftId) return
|
||||||
if (!tabs.ready() || !sdk.directory) return
|
if (!tabs.ready() || !sdk().directory) return
|
||||||
tabs.newDraft({ server: server.key, directory: sdk.directory }, search.prompt)
|
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -82,6 +82,45 @@ const SessionRoute = Object.assign(
|
|||||||
{ preload: Session.preload },
|
{ preload: Session.preload },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
|
||||||
|
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
||||||
|
// Notification/Models + the visual Layout) for that server.
|
||||||
|
function SelectedServerLayout(props: ParentProps) {
|
||||||
|
return (
|
||||||
|
<ServerKey>
|
||||||
|
<ServerSDKProvider>
|
||||||
|
<ServerSyncProvider>
|
||||||
|
<ServerScopedShell>{props.children}</ServerScopedShell>
|
||||||
|
</ServerSyncProvider>
|
||||||
|
</ServerSDKProvider>
|
||||||
|
</ServerKey>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps /new-session. It resolves the draft's target server and provides the
|
||||||
|
// server-scoped shell for that server — without ServerKey, so the page never depends
|
||||||
|
// on the globally "selected" server.
|
||||||
|
function DraftServerLayout(props: ParentProps) {
|
||||||
|
const server = useServer()
|
||||||
|
const tabs = useTabs()
|
||||||
|
const [search] = useSearchParams<{ draftId?: string }>()
|
||||||
|
const conn = createMemo(() => {
|
||||||
|
const id = search.draftId
|
||||||
|
if (!id) return undefined
|
||||||
|
const draft = tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === id)
|
||||||
|
if (!draft) return undefined
|
||||||
|
return server.list.find((c) => ServerConnection.key(c) === draft.server)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerSDKProvider server={conn}>
|
||||||
|
<ServerSyncProvider server={conn}>
|
||||||
|
<ServerScopedShell>{props.children}</ServerScopedShell>
|
||||||
|
</ServerSyncProvider>
|
||||||
|
</ServerSDKProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function DraftRoute() {
|
function DraftRoute() {
|
||||||
const [search] = useSearchParams<{ draftId?: string }>()
|
const [search] = useSearchParams<{ draftId?: string }>()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
@@ -95,19 +134,15 @@ function DraftRoute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ResolvedDraftRoute(props: { draftID: string }) {
|
function ResolvedDraftRoute(props: { draftID: string }) {
|
||||||
const server = useServer()
|
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const draft = createMemo(() =>
|
const draft = createMemo(() =>
|
||||||
tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === props.draftID),
|
tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === props.draftID),
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const current = draft()
|
|
||||||
if (current && current.server !== server.key) server.setActive(current.server)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Key on the directory so retargeting the draft's project re-instantiates the
|
// Key on the directory so retargeting the draft's project re-instantiates the
|
||||||
// SDK/data providers for the new directory while keeping the same draft id.
|
// directory-scoped providers while keeping the same draft id. The draft's target
|
||||||
|
// server is provided by DraftServerLayout, so changing only the server updates the
|
||||||
|
// SDK/sync hooks without remounting the composer.
|
||||||
const directory = () => draft()?.directory
|
const directory = () => draft()?.directory
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -171,27 +206,36 @@ function BodyDesignClass() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppShellProviders(props: ParentProps) {
|
// Server-agnostic providers shared across every route. These live in the shared
|
||||||
|
// shell (router root) so they stay mounted regardless of the active server/route.
|
||||||
|
function SharedProviders(props: ParentProps) {
|
||||||
return (
|
return (
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<BodyDesignClass />
|
<BodyDesignClass />
|
||||||
<PermissionProvider>
|
<CommandProvider>
|
||||||
<LayoutProvider>
|
<HighlightsProvider>{props.children}</HighlightsProvider>
|
||||||
<NotificationProvider>
|
</CommandProvider>
|
||||||
<ModelsProvider>
|
|
||||||
<CommandProvider>
|
|
||||||
<HighlightsProvider>
|
|
||||||
<Layout>{props.children}</Layout>
|
|
||||||
</HighlightsProvider>
|
|
||||||
</CommandProvider>
|
|
||||||
</ModelsProvider>
|
|
||||||
</NotificationProvider>
|
|
||||||
</LayoutProvider>
|
|
||||||
</PermissionProvider>
|
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server-scoped providers plus the visual Layout (tabs/sidebar). These live inside
|
||||||
|
// each per-route server layout so they resolve to that route's server (selected vs
|
||||||
|
// draft). The Layout remounts when crossing between those groups.
|
||||||
|
function ServerScopedShell(props: ParentProps) {
|
||||||
|
return (
|
||||||
|
<PermissionProvider>
|
||||||
|
<LayoutProvider>
|
||||||
|
<NotificationProvider>
|
||||||
|
<ModelsProvider>
|
||||||
|
<Layout>{props.children}</Layout>
|
||||||
|
</ModelsProvider>
|
||||||
|
</NotificationProvider>
|
||||||
|
</LayoutProvider>
|
||||||
|
</PermissionProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function SessionProviders(props: ParentProps) {
|
function SessionProviders(props: ParentProps) {
|
||||||
return (
|
return (
|
||||||
<TerminalProvider>
|
<TerminalProvider>
|
||||||
@@ -216,17 +260,6 @@ function DraftProviders(props: ParentProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
|
||||||
return (
|
|
||||||
<AppShellProviders>
|
|
||||||
{/*<Suspense fallback={<Loading />}>*/}
|
|
||||||
{props.appChildren}
|
|
||||||
{props.children}
|
|
||||||
{/*</Suspense>*/}
|
|
||||||
</AppShellProviders>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
||||||
return (
|
return (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
@@ -385,6 +418,20 @@ export function AppInterface(props: {
|
|||||||
router?: Component<BaseRouterProps>
|
router?: Component<BaseRouterProps>
|
||||||
disableHealthCheck?: boolean
|
disableHealthCheck?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
// The shared shell holds only server-agnostic providers (QueryClient + Settings/
|
||||||
|
// Command/Highlights) and stays mounted across every route. The server-scoped
|
||||||
|
// providers and the visual Layout live in the per-route layouts below, so they
|
||||||
|
// resolve to that route's server (selected for most routes, the draft's server for
|
||||||
|
// /new-session). appChildren is server-agnostic, so it renders here once.
|
||||||
|
const ServerShell = (shellProps: ParentProps) => (
|
||||||
|
<QueryProvider>
|
||||||
|
<SharedProviders>
|
||||||
|
{props.children}
|
||||||
|
{shellProps.children}
|
||||||
|
</SharedProviders>
|
||||||
|
</QueryProvider>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerProvider
|
<ServerProvider
|
||||||
defaultServer={props.defaultServer}
|
defaultServer={props.defaultServer}
|
||||||
@@ -397,23 +444,19 @@ export function AppInterface(props: {
|
|||||||
component={props.router ?? Router}
|
component={props.router ?? Router}
|
||||||
root={(routerProps) => (
|
root={(routerProps) => (
|
||||||
<TabsProvider>
|
<TabsProvider>
|
||||||
<ServerKey>
|
<ServerShell>{routerProps.children}</ServerShell>
|
||||||
<QueryProvider>
|
|
||||||
<ServerSDKProvider>
|
|
||||||
<ServerSyncProvider>
|
|
||||||
<RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>
|
|
||||||
</ServerSyncProvider>
|
|
||||||
</ServerSDKProvider>
|
|
||||||
</QueryProvider>
|
|
||||||
</ServerKey>
|
|
||||||
</TabsProvider>
|
</TabsProvider>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Route path="/" component={HomeRoute} />
|
<Route component={SelectedServerLayout}>
|
||||||
<Route path="/new-session" component={DraftRoute} />
|
<Route path="/" component={HomeRoute} />
|
||||||
<Route path="/:dir" component={DirectoryLayout}>
|
<Route path="/:dir" component={DirectoryLayout}>
|
||||||
<Route path="/" component={() => <Navigate href="session" />} />
|
<Route path="/" component={() => <Navigate href="session" />} />
|
||||||
<Route path="/session/:id?" component={SessionRoute} />
|
<Route path="/session/:id?" component={SessionRoute} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
<Route component={DraftServerLayout}>
|
||||||
|
<Route path="/new-session" component={DraftRoute} />
|
||||||
</Route>
|
</Route>
|
||||||
</Dynamic>
|
</Dynamic>
|
||||||
</ConnectionGate>
|
</ConnectionGate>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const provider = createMemo(
|
const provider = createMemo(
|
||||||
() => providers.all().get(props.provider) ?? serverSync.data.provider.all.get(props.provider)!,
|
() => providers.all().get(props.provider) ?? serverSync().data.provider.all.get(props.provider)!,
|
||||||
)
|
)
|
||||||
const fallback = createMemo<ProviderAuthMethod[]>(() => [
|
const fallback = createMemo<ProviderAuthMethod[]>(() => [
|
||||||
{
|
{
|
||||||
@@ -52,16 +52,16 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
const [auth] = createResource(
|
const [auth] = createResource(
|
||||||
() => props.provider,
|
() => props.provider,
|
||||||
async () => {
|
async () => {
|
||||||
const cached = serverSync.data.provider_auth[props.provider]
|
const cached = serverSync().data.provider_auth[props.provider]
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
const res = await serverSDK.client.provider.auth()
|
const res = await serverSDK().client.provider.auth()
|
||||||
if (!alive.value) return fallback()
|
if (!alive.value) return fallback()
|
||||||
serverSync.set("provider_auth", res.data ?? {})
|
serverSync().set("provider_auth", res.data ?? {})
|
||||||
return res.data?.[props.provider] ?? fallback()
|
return res.data?.[props.provider] ?? fallback()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const loading = createMemo(() => auth.loading && !serverSync.data.provider_auth[props.provider])
|
const loading = createMemo(() => auth.loading && !serverSync().data.provider_auth[props.provider])
|
||||||
const methods = createMemo(() => auth.latest ?? serverSync.data.provider_auth[props.provider] ?? fallback())
|
const methods = createMemo(() => auth.latest ?? serverSync().data.provider_auth[props.provider] ?? fallback())
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
methodIndex: undefined as undefined | number,
|
methodIndex: undefined as undefined | number,
|
||||||
authorization: undefined as undefined | ProviderAuthAuthorization,
|
authorization: undefined as undefined | ProviderAuthAuthorization,
|
||||||
@@ -158,7 +158,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
}
|
}
|
||||||
dispatch({ type: "auth.pending" })
|
dispatch({ type: "auth.pending" })
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
await serverSDK.client.provider.oauth
|
await serverSDK().client.provider.oauth
|
||||||
.authorize(
|
.authorize(
|
||||||
{
|
{
|
||||||
providerID: props.provider,
|
providerID: props.provider,
|
||||||
@@ -331,7 +331,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function complete() {
|
async function complete() {
|
||||||
await serverSDK.client.global.dispose()
|
await serverSDK().client.global.dispose()
|
||||||
dialog.close()
|
dialog.close()
|
||||||
showToast({
|
showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -409,7 +409,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFormStore("error", undefined)
|
setFormStore("error", undefined)
|
||||||
await serverSDK.client.auth.set({
|
await serverSDK().client.auth.set({
|
||||||
providerID: props.provider,
|
providerID: props.provider,
|
||||||
auth: {
|
auth: {
|
||||||
type: "api",
|
type: "api",
|
||||||
@@ -480,7 +480,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFormStore("error", undefined)
|
setFormStore("error", undefined)
|
||||||
const result = await serverSDK.client.provider.oauth
|
const result = await serverSDK().client.provider.oauth
|
||||||
.callback({
|
.callback({
|
||||||
providerID: props.provider,
|
providerID: props.provider,
|
||||||
method: store.methodIndex,
|
method: store.methodIndex,
|
||||||
@@ -533,7 +533,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const result = await serverSDK.client.provider.oauth
|
const result = await serverSDK().client.provider.oauth
|
||||||
.callback({
|
.callback({
|
||||||
providerID: props.provider,
|
providerID: props.provider,
|
||||||
method: store.methodIndex,
|
method: store.methodIndex,
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
const output = validateCustomProvider({
|
const output = validateCustomProvider({
|
||||||
form,
|
form,
|
||||||
t: language.t,
|
t: language.t,
|
||||||
disabledProviders: serverSync.data.config.disabled_providers ?? [],
|
disabledProviders: serverSync().data.config.disabled_providers ?? [],
|
||||||
existingProviderIDs: new Set(serverSync.data.provider.all.keys()),
|
existingProviderIDs: new Set(serverSync().data.provider.all.keys()),
|
||||||
})
|
})
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setForm("err", output.err)
|
setForm("err", output.err)
|
||||||
@@ -118,11 +118,11 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
|
|
||||||
const saveMutation = useMutation(() => ({
|
const saveMutation = useMutation(() => ({
|
||||||
mutationFn: async (result: NonNullable<ReturnType<typeof validate>>) => {
|
mutationFn: async (result: NonNullable<ReturnType<typeof validate>>) => {
|
||||||
const disabledProviders = serverSync.data.config.disabled_providers ?? []
|
const disabledProviders = serverSync().data.config.disabled_providers ?? []
|
||||||
const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
|
const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
|
||||||
|
|
||||||
if (result.key) {
|
if (result.key) {
|
||||||
await serverSDK.client.auth.set({
|
await serverSDK().client.auth.set({
|
||||||
providerID: result.providerID,
|
providerID: result.providerID,
|
||||||
auth: {
|
auth: {
|
||||||
type: "api",
|
type: "api",
|
||||||
@@ -131,7 +131,7 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await serverSync.updateConfig({
|
await serverSync().updateConfig({
|
||||||
provider: { [result.providerID]: result.config },
|
provider: { [result.providerID]: result.config },
|
||||||
disabled_providers: nextDisabled,
|
disabled_providers: nextDisabled,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ export const DialogFork: Component = () => {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return []
|
if (!sessionID) return []
|
||||||
|
|
||||||
const msgs = sync.data.message[sessionID] ?? []
|
const msgs = sync().data.message[sessionID] ?? []
|
||||||
const result: ForkableMessage[] = []
|
const result: ForkableMessage[] = []
|
||||||
|
|
||||||
for (const message of msgs) {
|
for (const message of msgs) {
|
||||||
if (message.role !== "user") continue
|
if (message.role !== "user") continue
|
||||||
|
|
||||||
const parts = sync.data.part[message.id] ?? []
|
const parts = sync().data.part[message.id] ?? []
|
||||||
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
|
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
|
||||||
if (!textPart) continue
|
if (!textPart) continue
|
||||||
|
|
||||||
@@ -61,14 +61,14 @@ export const DialogFork: Component = () => {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
|
||||||
const parts = sync.data.part[item.id] ?? []
|
const parts = sync().data.part[item.id] ?? []
|
||||||
const restored = extractPromptFromParts(parts, {
|
const restored = extractPromptFromParts(parts, {
|
||||||
directory: sdk.directory,
|
directory: sdk().directory,
|
||||||
attachmentName: language.t("common.attachment"),
|
attachmentName: language.t("common.attachment"),
|
||||||
})
|
})
|
||||||
const dir = base64Encode(sdk.directory)
|
const dir = base64Encode(sdk().directory)
|
||||||
|
|
||||||
sdk.client.session
|
sdk().client.session
|
||||||
.fork({ sessionID, messageID: item.id })
|
.fork({ sessionID, messageID: item.id })
|
||||||
.then((forked) => {
|
.then((forked) => {
|
||||||
if (!forked.data) {
|
if (!forked.data) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
|||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js"
|
import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useServerSync } from "@/context/server-sync"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
@@ -175,7 +175,7 @@ function createFileEntries(props: {
|
|||||||
function createSessionEntries(props: {
|
function createSessionEntries(props: {
|
||||||
workspaces: () => string[]
|
workspaces: () => string[]
|
||||||
label: (directory: string) => string
|
label: (directory: string) => string
|
||||||
serverSDK: ReturnType<typeof useServerSDK>
|
serverSDK: ServerSDK
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
}) {
|
}) {
|
||||||
const state: {
|
const state: {
|
||||||
@@ -292,21 +292,21 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
if (directory && !dirs.includes(directory)) return [...dirs, directory]
|
if (directory && !dirs.includes(directory)) return [...dirs, directory]
|
||||||
return dirs
|
return dirs
|
||||||
})
|
})
|
||||||
const homedir = createMemo(() => serverSync.data.path.home)
|
const homedir = createMemo(() => serverSync().data.path.home)
|
||||||
const label = (directory: string) => {
|
const label = (directory: string) => {
|
||||||
const current = project()
|
const current = project()
|
||||||
const kind =
|
const kind =
|
||||||
current && directory === current.worktree
|
current && directory === current.worktree
|
||||||
? language.t("workspace.type.local")
|
? language.t("workspace.type.local")
|
||||||
: language.t("workspace.type.sandbox")
|
: language.t("workspace.type.sandbox")
|
||||||
const [store] = serverSync.child(directory, { bootstrap: false })
|
const [store] = serverSync().child(directory, { bootstrap: false })
|
||||||
const home = homedir()
|
const home = homedir()
|
||||||
const path = home ? directory.replace(home, "~") : directory
|
const path = home ? directory.replace(home, "~") : directory
|
||||||
const name = store.vcs?.branch ?? getFilename(directory)
|
const name = store.vcs?.branch ?? getFilename(directory)
|
||||||
return `${kind} : ${name || path}`
|
return `${kind} : ${name || path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sessions } = createSessionEntries({ workspaces, label, serverSDK, language })
|
const { sessions } = createSessionEntries({ workspaces, label, serverSDK: serverSDK(), language })
|
||||||
|
|
||||||
const items = async (text: string) => {
|
const items = async (text: string) => {
|
||||||
const query = text.trim()
|
const query = text.trim()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const items = createMemo(() =>
|
const items = createMemo(() =>
|
||||||
Object.entries(sync.data.mcp ?? {})
|
Object.entries(sync().data.mcp ?? {})
|
||||||
.map(([name, status]) => ({ name, status: status.status }))
|
.map(([name, status]) => ({ name, status: status.status }))
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)),
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
)
|
)
|
||||||
@@ -48,7 +48,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => {
|
{(i) => {
|
||||||
const mcpStatus = () => sync.data.mcp[i.name]
|
const mcpStatus = () => sync().data.mcp[i.name]
|
||||||
const status = () => mcpStatus()?.status
|
const status = () => mcpStatus()?.status
|
||||||
const statusLabel = () => {
|
const statusLabel = () => {
|
||||||
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return false
|
if (!sessionID) return false
|
||||||
|
|
||||||
const diffs = sync.data.session_diff[sessionID]
|
const diffs = sync().data.session_diff[sessionID]
|
||||||
if (!diffs) return false
|
if (!diffs) return false
|
||||||
return diffs.some((diff) => diff.file === path)
|
return diffs.some((diff) => diff.file === path)
|
||||||
}
|
}
|
||||||
@@ -269,8 +269,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
|
|
||||||
return paths
|
return paths
|
||||||
})
|
})
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
|
||||||
const working = createMemo(() => sync.data.session_working(params.id ?? ""))
|
const working = createMemo(() => sync().data.session_working(params.id ?? ""))
|
||||||
const imageAttachments = createMemo(() =>
|
const imageAttachments = createMemo(() =>
|
||||||
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
|
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
|
||||||
)
|
)
|
||||||
@@ -349,7 +349,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const hasUserPrompt = createMemo(() => {
|
const hasUserPrompt = createMemo(() => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return false
|
if (!sessionID) return false
|
||||||
const messages = sync.data.message[sessionID]
|
const messages = sync().data.message[sessionID]
|
||||||
if (!messages) return false
|
if (!messages) return false
|
||||||
return messages.some((m) => m.role === "user")
|
return messages.some((m) => m.role === "user")
|
||||||
})
|
})
|
||||||
@@ -474,7 +474,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const pick = () => {
|
const pick = () => {
|
||||||
pickAttachmentFiles({
|
pickAttachmentFiles({
|
||||||
picker: platform.openAttachmentPickerDialog,
|
picker: platform.openAttachmentPickerDialog,
|
||||||
directory: () => sdk.directory,
|
directory: () => sdk().directory,
|
||||||
fallback: () => fileInputRef?.click(),
|
fallback: () => fileInputRef?.click(),
|
||||||
onFile: addAttachment,
|
onFile: addAttachment,
|
||||||
onError: (error) =>
|
onError: (error) =>
|
||||||
@@ -603,8 +603,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const agentList = createMemo(() =>
|
const agentList = createMemo(() =>
|
||||||
sync.data.agent
|
sync()
|
||||||
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
.data.agent.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
||||||
.map((agent): AtOption => ({ type: "agent", name: agent.name, display: agent.name })),
|
.map((agent): AtOption => ({ type: "agent", name: agent.name, display: agent.name })),
|
||||||
)
|
)
|
||||||
const agentNames = createMemo(() => local.agent.list().map((agent) => agent.name))
|
const agentNames = createMemo(() => local.agent.list().map((agent) => agent.name))
|
||||||
@@ -673,7 +673,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
type: "builtin" as const,
|
type: "builtin" as const,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const custom = sync.data.command.map((cmd) => ({
|
const custom = sync().data.command.map((cmd) => ({
|
||||||
id: `custom.${cmd.name}`,
|
id: `custom.${cmd.name}`,
|
||||||
trigger: cmd.name,
|
trigger: cmd.name,
|
||||||
title: cmd.name,
|
title: cmd.name,
|
||||||
@@ -1127,8 +1127,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const showVariantControl = createMemo(() => local.model.variant.list().length > 0)
|
const showVariantControl = createMemo(() => local.model.variant.list().length > 0)
|
||||||
const accepting = createMemo(() => {
|
const accepting = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
|
if (!id) return permission.isAutoAcceptingDirectory(sdk().directory)
|
||||||
return permission.isAutoAccepting(id, sdk.directory)
|
return permission.isAutoAccepting(id, sdk().directory)
|
||||||
})
|
})
|
||||||
|
|
||||||
const { abort, handleSubmit } = createPromptSubmit({
|
const { abort, handleSubmit } = createPromptSubmit({
|
||||||
@@ -1319,9 +1319,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
|
|
||||||
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
|
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
|
||||||
queries: [
|
queries: [
|
||||||
queryOptions.agents(pathKey(sdk.directory)),
|
queryOptions().agents(pathKey(sdk().directory)),
|
||||||
queryOptions.providers(null),
|
queryOptions().providers(null),
|
||||||
queryOptions.providers(pathKey(sdk.directory)),
|
queryOptions().providers(pathKey(sdk().directory)),
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -1366,7 +1366,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
(project) => pathKey(project.worktree) === key || project.sandboxes?.some((sandbox) => pathKey(sandbox) === key),
|
(project) => pathKey(project.worktree) === key || project.sandboxes?.some((sandbox) => pathKey(sandbox) === key),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const selectedProject = createMemo(() => projectForDirectory(sdk.directory))
|
const selectedProject = createMemo(() => projectForDirectory(sdk().directory))
|
||||||
const projectResults = createMemo(() => {
|
const projectResults = createMemo(() => {
|
||||||
const search = picker.projectSearch.trim().toLowerCase()
|
const search = picker.projectSearch.trim().toLowerCase()
|
||||||
if (!search) return projects()
|
if (!search) return projects()
|
||||||
|
|||||||
@@ -147,12 +147,12 @@ beforeAll(async () => {
|
|||||||
return clientFor(opts.directory)
|
return clientFor(opts.directory)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return sdk
|
return () => sdk
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@/context/sync", () => ({
|
mock.module("@/context/sync", () => ({
|
||||||
useSync: () => ({
|
useSync: () => () => ({
|
||||||
data: { command: [] },
|
data: { command: [] },
|
||||||
session: {
|
session: {
|
||||||
optimistic: {
|
optimistic: {
|
||||||
@@ -176,7 +176,7 @@ beforeAll(async () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@/context/server-sync", () => ({
|
mock.module("@/context/server-sync", () => ({
|
||||||
useServerSync: () => ({
|
useServerSync: () => () => ({
|
||||||
child: (directory: string) => {
|
child: (directory: string) => {
|
||||||
syncedDirectories.push(directory)
|
syncedDirectories.push(directory)
|
||||||
storedSessions[directory] ??= []
|
storedSessions[directory] ??= []
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import { batch, type Accessor } from "solid-js"
|
|||||||
import type { FileSelection } from "@/context/file"
|
import type { FileSelection } from "@/context/file"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
import { useTabs } from "@/context/tabs"
|
import { useTabs } from "@/context/tabs"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useServerSync, type ServerSync } from "@/context/server-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { type ContextItem, type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
|
import { type ContextItem, type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK, type DirectorySDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync, type DirectorySync } from "@/context/sync"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
import { Worktree as WorktreeState } from "@/utils/worktree"
|
import { Worktree as WorktreeState } from "@/utils/worktree"
|
||||||
import { buildRequestParts } from "./build-request-parts"
|
import { buildRequestParts } from "./build-request-parts"
|
||||||
@@ -40,9 +40,9 @@ export type FollowupDraft = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FollowupSendInput = {
|
type FollowupSendInput = {
|
||||||
client: ReturnType<typeof useSDK>["client"]
|
client: DirectorySDK["client"]
|
||||||
serverSync: ReturnType<typeof useServerSync>
|
serverSync: ServerSync
|
||||||
sync: ReturnType<typeof useSync>
|
sync: DirectorySync
|
||||||
draft: FollowupDraft
|
draft: FollowupDraft
|
||||||
messageID?: string
|
messageID?: string
|
||||||
optimisticBusy?: boolean
|
optimisticBusy?: boolean
|
||||||
@@ -218,7 +218,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
const [search] = useSearchParams<{ draftId?: string }>()
|
const [search] = useSearchParams<{ draftId?: string }>()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const pendingKey = (sessionID: string) => ScopedKey.from(sdk.scope, sessionID)
|
const pendingKey = (sessionID: string) => ScopedKey.from(sdk().scope, sessionID)
|
||||||
|
|
||||||
const errorMessage = (err: unknown) => {
|
const errorMessage = (err: unknown) => {
|
||||||
if (err && typeof err === "object" && "data" in err) {
|
if (err && typeof err === "object" && "data" in err) {
|
||||||
@@ -233,8 +233,8 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return Promise.resolve()
|
if (!sessionID) return Promise.resolve()
|
||||||
|
|
||||||
serverSync.todo.set(sessionID, [])
|
serverSync().todo.set(sessionID, [])
|
||||||
const [, setStore] = serverSync.child(sdk.directory)
|
const [, setStore] = serverSync().child(sdk().directory)
|
||||||
setStore("todo", sessionID, [])
|
setStore("todo", sessionID, [])
|
||||||
|
|
||||||
input.onAbort?.()
|
input.onAbort?.()
|
||||||
@@ -247,7 +247,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
pending.delete(key)
|
pending.delete(key)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
return sdk.client.session
|
return sdk().client.session
|
||||||
.abort({
|
.abort({
|
||||||
sessionID,
|
sessionID,
|
||||||
})
|
})
|
||||||
@@ -281,7 +281,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const seed = (dir: string, info: Session) => {
|
const seed = (dir: string, info: Session) => {
|
||||||
const [, setStore] = serverSync.child(dir)
|
const [, setStore] = serverSync().child(dir)
|
||||||
setStore("session", (list: Session[]) => {
|
setStore("session", (list: Session[]) => {
|
||||||
const result = Binary.search(list, info.id, (item) => item.id)
|
const result = Binary.search(list, info.id, (item) => item.id)
|
||||||
const next = [...list]
|
const next = [...list]
|
||||||
@@ -321,13 +321,13 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
input.addToHistory(currentPrompt, mode)
|
input.addToHistory(currentPrompt, mode)
|
||||||
input.resetHistoryNavigation()
|
input.resetHistoryNavigation()
|
||||||
|
|
||||||
const projectDirectory = sdk.directory
|
const projectDirectory = sdk().directory
|
||||||
const isNewSession = !params.id
|
const isNewSession = !params.id
|
||||||
const shouldAutoAccept = isNewSession && input.autoAccept()
|
const shouldAutoAccept = isNewSession && input.autoAccept()
|
||||||
const worktreeSelection = input.newSessionWorktree?.() || "main"
|
const worktreeSelection = input.newSessionWorktree?.() || "main"
|
||||||
|
|
||||||
let sessionDirectory = projectDirectory
|
let sessionDirectory = projectDirectory
|
||||||
let client = sdk.client
|
let client = sdk().client
|
||||||
|
|
||||||
if (isNewSession) {
|
if (isNewSession) {
|
||||||
if (worktreeSelection === "create") {
|
if (worktreeSelection === "create") {
|
||||||
@@ -349,7 +349,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WorktreeState.pending(sdk.scope, createdWorktree.directory)
|
WorktreeState.pending(sdk().scope, createdWorktree.directory)
|
||||||
sessionDirectory = createdWorktree.directory
|
sessionDirectory = createdWorktree.directory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,11 +358,11 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sessionDirectory !== projectDirectory) {
|
if (sessionDirectory !== projectDirectory) {
|
||||||
client = sdk.createClient({
|
client = sdk().createClient({
|
||||||
directory: sessionDirectory,
|
directory: sessionDirectory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
serverSync.child(sessionDirectory)
|
serverSync().child(sessionDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
input.onNewSessionWorktreeReset?.()
|
input.onNewSessionWorktreeReset?.()
|
||||||
@@ -470,7 +470,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
const [cmdName, ...args] = text.split(" ")
|
const [cmdName, ...args] = text.split(" ")
|
||||||
const commandName = cmdName.slice(1)
|
const commandName = cmdName.slice(1)
|
||||||
const customCommand = sync.data.command.find((c) => c.name === commandName)
|
const customCommand = sync().data.command.find((c) => c.name === commandName)
|
||||||
if (customCommand) {
|
if (customCommand) {
|
||||||
clearInput()
|
clearInput()
|
||||||
client.session
|
client.session
|
||||||
@@ -504,7 +504,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
const messageID = Identifier.ascending("message")
|
const messageID = Identifier.ascending("message")
|
||||||
|
|
||||||
const removeOptimisticMessage = () => {
|
const removeOptimisticMessage = () => {
|
||||||
sync.session.optimistic.remove({
|
sync().session.optimistic.remove({
|
||||||
directory: sessionDirectory,
|
directory: sessionDirectory,
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
messageID,
|
messageID,
|
||||||
@@ -515,17 +515,17 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
clearInput()
|
clearInput()
|
||||||
|
|
||||||
const waitForWorktree = async () => {
|
const waitForWorktree = async () => {
|
||||||
const worktree = WorktreeState.get(sdk.scope, sessionDirectory)
|
const worktree = WorktreeState.get(sdk().scope, sessionDirectory)
|
||||||
if (!worktree || worktree.status !== "pending") return true
|
if (!worktree || worktree.status !== "pending") return true
|
||||||
|
|
||||||
if (sessionDirectory === projectDirectory) {
|
if (sessionDirectory === projectDirectory) {
|
||||||
sync.set("session_status", session.id, { type: "busy" })
|
sync().set("session_status", session.id, { type: "busy" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (sessionDirectory === projectDirectory) {
|
if (sessionDirectory === projectDirectory) {
|
||||||
sync.set("session_status", session.id, { type: "idle" })
|
sync().set("session_status", session.id, { type: "idle" })
|
||||||
}
|
}
|
||||||
removeOptimisticMessage()
|
removeOptimisticMessage()
|
||||||
restoreCommentItems(commentItems)
|
restoreCommentItems(commentItems)
|
||||||
@@ -559,7 +559,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
}, timeoutMs)
|
}, timeoutMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await Promise.race([WorktreeState.wait(sdk.scope, sessionDirectory), abortWait, timeout]).finally(
|
const result = await Promise.race([WorktreeState.wait(sdk().scope, sessionDirectory), abortWait, timeout]).finally(
|
||||||
() => {
|
() => {
|
||||||
if (timer.id === undefined) return
|
if (timer.id === undefined) return
|
||||||
clearTimeout(timer.id)
|
clearTimeout(timer.id)
|
||||||
@@ -573,8 +573,8 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
|
|
||||||
void sendFollowupDraft({
|
void sendFollowupDraft({
|
||||||
client,
|
client,
|
||||||
sync,
|
sync: sync(),
|
||||||
serverSync,
|
serverSync: serverSync(),
|
||||||
draft,
|
draft,
|
||||||
messageID,
|
messageID,
|
||||||
optimisticBusy: sessionDirectory === projectDirectory,
|
optimisticBusy: sessionDirectory === projectDirectory,
|
||||||
@@ -582,7 +582,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
pending.delete(pendingKey(session.id))
|
pending.delete(pendingKey(session.id))
|
||||||
if (sessionDirectory === projectDirectory) {
|
if (sessionDirectory === projectDirectory) {
|
||||||
sync.set("session_status", session.id, { type: "idle" })
|
sync().set("session_status", session.id, { type: "idle" })
|
||||||
}
|
}
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("prompt.toast.promptSendFailed.title"),
|
title: language.t("prompt.toast.promptSendFailed.title"),
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
pathFromTab: file.pathFromTab,
|
pathFromTab: file.pathFromTab,
|
||||||
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
||||||
})
|
})
|
||||||
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
|
||||||
|
|
||||||
const usd = createMemo(
|
const usd = createMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -96,13 +96,13 @@ export function SessionContextTab() {
|
|||||||
const providers = useProviders()
|
const providers = useProviders()
|
||||||
const { params, view } = useSessionLayout()
|
const { params, view } = useSessionLayout()
|
||||||
|
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
|
||||||
|
|
||||||
const messages = createMemo(
|
const messages = createMemo(
|
||||||
() => {
|
() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return emptyMessages
|
if (!id) return emptyMessages
|
||||||
return (sync.data.message[id] ?? []) as Message[]
|
return (sync().data.message[id] ?? []) as Message[]
|
||||||
},
|
},
|
||||||
emptyMessages,
|
emptyMessages,
|
||||||
{ equals: same },
|
{ equals: same },
|
||||||
@@ -180,7 +180,7 @@ export function SessionContextTab() {
|
|||||||
if (!c?.input) return []
|
if (!c?.input) return []
|
||||||
return estimateSessionContextBreakdown({
|
return estimateSessionContextBreakdown({
|
||||||
messages: messages(),
|
messages: messages(),
|
||||||
parts: sync.data.part as Record<string, Part[] | undefined>,
|
parts: sync().data.part as Record<string, Part[] | undefined>,
|
||||||
input: c.input,
|
input: c.input,
|
||||||
systemPrompt: systemPrompt(),
|
systemPrompt: systemPrompt(),
|
||||||
})
|
})
|
||||||
@@ -221,7 +221,7 @@ export function SessionContextTab() {
|
|||||||
let scroll: HTMLDivElement | undefined
|
let scroll: HTMLDivElement | undefined
|
||||||
let frame: number | undefined
|
let frame: number | undefined
|
||||||
let pending: { x: number; y: number } | undefined
|
let pending: { x: number; y: number } | undefined
|
||||||
const getParts = (id: string) => (sync.data.part[id] ?? []) as Part[]
|
const getParts = (id: string) => (sync().data.part[id] ?? []) as Part[]
|
||||||
|
|
||||||
const restoreScroll = () => {
|
const restoreScroll = () => {
|
||||||
const el = scroll
|
const el = scroll
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export function SessionHeader() {
|
|||||||
)
|
)
|
||||||
const opening = createMemo(() => openRequest.app !== undefined)
|
const opening = createMemo(() => openRequest.app !== undefined)
|
||||||
const tint = createMemo(() =>
|
const tint = createMemo(() =>
|
||||||
messageAgentColor(params.id ? sync.data.message[params.id] : undefined, sync.data.agent),
|
messageAgentColor(params.id ? sync().data.message[params.id] : undefined, sync().data.agent),
|
||||||
)
|
)
|
||||||
const v2ActionsState = createMemo<SessionHeaderV2ActionsState>(() => ({
|
const v2ActionsState = createMemo<SessionHeaderV2ActionsState>(() => ({
|
||||||
statusVisible: status(),
|
statusVisible: status(),
|
||||||
|
|||||||
@@ -20,24 +20,24 @@ export function NewSessionView(props: NewSessionViewProps) {
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const sandboxes = createMemo(() => sync.project?.sandboxes ?? [])
|
const sandboxes = createMemo(() => sync().project?.sandboxes ?? [])
|
||||||
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
|
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
|
||||||
const current = createMemo(() => {
|
const current = createMemo(() => {
|
||||||
const selection = props.worktree
|
const selection = props.worktree
|
||||||
if (options().includes(selection)) return selection
|
if (options().includes(selection)) return selection
|
||||||
return MAIN_WORKTREE
|
return MAIN_WORKTREE
|
||||||
})
|
})
|
||||||
const projectRoot = createMemo(() => sync.project?.worktree ?? sdk.directory)
|
const projectRoot = createMemo(() => sync().project?.worktree ?? sdk().directory)
|
||||||
const isWorktree = createMemo(() => {
|
const isWorktree = createMemo(() => {
|
||||||
const project = sync.project
|
const project = sync().project
|
||||||
if (!project) return false
|
if (!project) return false
|
||||||
return sdk.directory !== project.worktree
|
return sdk().directory !== project.worktree
|
||||||
})
|
})
|
||||||
|
|
||||||
const label = (value: string) => {
|
const label = (value: string) => {
|
||||||
if (value === MAIN_WORKTREE) {
|
if (value === MAIN_WORKTREE) {
|
||||||
if (isWorktree()) return language.t("session.new.worktree.main")
|
if (isWorktree()) return language.t("session.new.worktree.main")
|
||||||
const branch = sync.data.vcs?.branch
|
const branch = sync().data.vcs?.branch
|
||||||
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
|
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
|
||||||
return language.t("session.new.worktree.main")
|
return language.t("session.new.worktree.main")
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ export function NewSessionView(props: NewSessionViewProps) {
|
|||||||
{label(current())}
|
{label(current())}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={sync.project}>
|
<Show when={sync().project}>
|
||||||
{(project) => (
|
{(project) => (
|
||||||
<div class="flex items-start justify-center gap-3 min-h-5">
|
<div class="flex items-start justify-center gap-3 min-h-5">
|
||||||
<div class="text-12-medium text-text-weak leading-5 min-w-0 max-w-160 break-words text-center">
|
<div class="text-12-medium text-text-weak leading-5 min-w-0 max-w-160 break-words text-center">
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const SettingsGeneral: Component = () => {
|
|||||||
|
|
||||||
const [shells] = createResource(
|
const [shells] = createResource(
|
||||||
() =>
|
() =>
|
||||||
serverSdk.client.pty
|
serverSdk().client.pty
|
||||||
.shells()
|
.shells()
|
||||||
.then((res) => res.data ?? [])
|
.then((res) => res.data ?? [])
|
||||||
.catch(() => [] as ShellOption[]),
|
.catch(() => [] as ShellOption[]),
|
||||||
@@ -151,11 +151,11 @@ export const SettingsGeneral: Component = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
|
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
|
||||||
const currentShell = createMemo(() => serverSync.data.config.shell ?? "")
|
const currentShell = createMemo(() => serverSync().data.config.shell ?? "")
|
||||||
|
|
||||||
const shellOptions = createMemo<ShellSelectOption[]>(() => {
|
const shellOptions = createMemo<ShellSelectOption[]>(() => {
|
||||||
const list = shells.latest
|
const list = shells.latest
|
||||||
const current = serverSync.data.config.shell
|
const current = serverSync().data.config.shell
|
||||||
|
|
||||||
const nameCounts = new Map<string, number>()
|
const nameCounts = new Map<string, number>()
|
||||||
for (const s of list) {
|
for (const s of list) {
|
||||||
@@ -290,7 +290,7 @@ export const SettingsGeneral: Component = () => {
|
|||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
if (option.value === currentShell()) return
|
if (option.value === currentShell()) return
|
||||||
serverSync.updateConfig({ shell: option.value })
|
serverSync().updateConfig({ shell: option.value })
|
||||||
}}
|
}}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const SettingsProvidersContent: Component = () => {
|
|||||||
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
||||||
|
|
||||||
const isConfigCustom = (providerID: string) => {
|
const isConfigCustom = (providerID: string) => {
|
||||||
const provider = serverSync.data.config.provider?.[providerID]
|
const provider = serverSync().data.config.provider?.[providerID]
|
||||||
if (!provider) return false
|
if (!provider) return false
|
||||||
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
||||||
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
||||||
@@ -91,11 +91,11 @@ const SettingsProvidersContent: Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const disableProvider = async (providerID: string, name: string) => {
|
const disableProvider = async (providerID: string, name: string) => {
|
||||||
const before = serverSync.data.config.disabled_providers ?? []
|
const before = serverSync().data.config.disabled_providers ?? []
|
||||||
const next = before.includes(providerID) ? before : [...before, providerID]
|
const next = before.includes(providerID) ? before : [...before, providerID]
|
||||||
serverSync.set("config", "disabled_providers", next)
|
serverSync().set("config", "disabled_providers", next)
|
||||||
|
|
||||||
await serverSync
|
await serverSync()
|
||||||
.updateConfig({ disabled_providers: next })
|
.updateConfig({ disabled_providers: next })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showToast({
|
showToast({
|
||||||
@@ -106,7 +106,7 @@ const SettingsProvidersContent: Component = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
serverSync.set("config", "disabled_providers", before)
|
serverSync().set("config", "disabled_providers", before)
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
showToast({ title: language.t("common.requestFailed"), description: message })
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
})
|
})
|
||||||
@@ -114,14 +114,14 @@ const SettingsProvidersContent: Component = () => {
|
|||||||
|
|
||||||
const disconnect = async (providerID: string, name: string) => {
|
const disconnect = async (providerID: string, name: string) => {
|
||||||
if (isConfigCustom(providerID)) {
|
if (isConfigCustom(providerID)) {
|
||||||
await serverSDK.client.auth.remove({ providerID }).catch(() => undefined)
|
await serverSDK().client.auth.remove({ providerID }).catch(() => undefined)
|
||||||
await disableProvider(providerID, name)
|
await disableProvider(providerID, name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await serverSDK.client.auth
|
await serverSDK().client.auth
|
||||||
.remove({ providerID })
|
.remove({ providerID })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await serverSDK.client.global.dispose()
|
await serverSDK().client.global.dispose()
|
||||||
showToast({
|
showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
icon: "circle-check",
|
icon: "circle-check",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function SettingsServerDataProviders(props: ParentProps<{ server: ServerConnecti
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={serverCtx().queryClient}>
|
<QueryClientProvider client={serverCtx().queryClient}>
|
||||||
<ServerSDKProvider server={props.server}>
|
<ServerSDKProvider server={() => props.server}>
|
||||||
<ServerSyncProvider>
|
<ServerSyncProvider>
|
||||||
<ModelsProvider>{props.children}</ModelsProvider>
|
<ModelsProvider>{props.children}</ModelsProvider>
|
||||||
</ServerSyncProvider>
|
</ServerSyncProvider>
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export const SettingsGeneralV2: Component = () => {
|
|||||||
|
|
||||||
const [shells] = createResource(
|
const [shells] = createResource(
|
||||||
() =>
|
() =>
|
||||||
serverSdk.client.pty
|
serverSdk().client.pty
|
||||||
.shells()
|
.shells()
|
||||||
.then((res) => res.data ?? [])
|
.then((res) => res.data ?? [])
|
||||||
.catch(() => [] as ShellOption[]),
|
.catch(() => [] as ShellOption[]),
|
||||||
@@ -144,11 +144,11 @@ export const SettingsGeneralV2: Component = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
|
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
|
||||||
const currentShell = createMemo(() => serverSync.data.config.shell ?? "")
|
const currentShell = createMemo(() => serverSync().data.config.shell ?? "")
|
||||||
|
|
||||||
const shellOptions = createMemo<ShellSelectOption[]>(() => {
|
const shellOptions = createMemo<ShellSelectOption[]>(() => {
|
||||||
const list = shells.latest
|
const list = shells.latest
|
||||||
const current = serverSync.data.config.shell
|
const current = serverSync().data.config.shell
|
||||||
|
|
||||||
const nameCounts = new Map<string, number>()
|
const nameCounts = new Map<string, number>()
|
||||||
for (const s of list) {
|
for (const s of list) {
|
||||||
@@ -275,7 +275,7 @@ export const SettingsGeneralV2: Component = () => {
|
|||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
if (option.value === currentShell()) return
|
if (option.value === currentShell()) return
|
||||||
serverSync.updateConfig({ shell: option.value })
|
serverSync().updateConfig({ shell: option.value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsRowV2>
|
</SettingsRowV2>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const SettingsProvidersV2: Component = () => {
|
|||||||
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
||||||
|
|
||||||
const isConfigCustom = (providerID: string) => {
|
const isConfigCustom = (providerID: string) => {
|
||||||
const provider = serverSync.data.config.provider?.[providerID]
|
const provider = serverSync().data.config.provider?.[providerID]
|
||||||
if (!provider) return false
|
if (!provider) return false
|
||||||
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
||||||
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
||||||
@@ -85,11 +85,11 @@ export const SettingsProvidersV2: Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const disableProvider = async (providerID: string, name: string) => {
|
const disableProvider = async (providerID: string, name: string) => {
|
||||||
const before = serverSync.data.config.disabled_providers ?? []
|
const before = serverSync().data.config.disabled_providers ?? []
|
||||||
const next = before.includes(providerID) ? before : [...before, providerID]
|
const next = before.includes(providerID) ? before : [...before, providerID]
|
||||||
serverSync.set("config", "disabled_providers", next)
|
serverSync().set("config", "disabled_providers", next)
|
||||||
|
|
||||||
await serverSync
|
await serverSync()
|
||||||
.updateConfig({ disabled_providers: next })
|
.updateConfig({ disabled_providers: next })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showToast({
|
showToast({
|
||||||
@@ -100,7 +100,7 @@ export const SettingsProvidersV2: Component = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
serverSync.set("config", "disabled_providers", before)
|
serverSync().set("config", "disabled_providers", before)
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
showToast({ title: language.t("common.requestFailed"), description: message })
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
})
|
})
|
||||||
@@ -108,14 +108,14 @@ export const SettingsProvidersV2: Component = () => {
|
|||||||
|
|
||||||
const disconnect = async (providerID: string, name: string) => {
|
const disconnect = async (providerID: string, name: string) => {
|
||||||
if (isConfigCustom(providerID)) {
|
if (isConfigCustom(providerID)) {
|
||||||
await serverSdk.client.auth.remove({ providerID }).catch(() => undefined)
|
await serverSdk().client.auth.remove({ providerID }).catch(() => undefined)
|
||||||
await disableProvider(providerID, name)
|
await disableProvider(providerID, name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await serverSdk.client.auth
|
await serverSdk().client.auth
|
||||||
.remove({ providerID })
|
.remove({ providerID })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await serverSdk.client.global.dispose()
|
await serverSdk().client.global.dispose()
|
||||||
showToast({
|
showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
icon: "circle-check",
|
icon: "circle-check",
|
||||||
|
|||||||
@@ -279,13 +279,13 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
|
|||||||
const sortedServers = createMemo(() => listServersByHealth(global.servers.list(), server.key, global.servers.health))
|
const sortedServers = createMemo(() => listServersByHealth(global.servers.list(), server.key, global.servers.health))
|
||||||
const toggleMcp = useMcpToggle()
|
const toggleMcp = useMcpToggle()
|
||||||
const defaultServer = useDefaultServerKey(platform.getDefaultServer)
|
const defaultServer = useDefaultServerKey(platform.getDefaultServer)
|
||||||
const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
|
const mcpNames = createMemo(() => Object.keys(sync().data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
|
||||||
const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status
|
const mcpStatus = (name: string) => sync().data.mcp?.[name]?.status
|
||||||
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
||||||
const lspItems = createMemo(() => sync.data.lsp ?? [])
|
const lspItems = createMemo(() => sync().data.lsp ?? [])
|
||||||
const lspCount = createMemo(() => lspItems().length)
|
const lspCount = createMemo(() => lspItems().length)
|
||||||
const plugins = createMemo(() =>
|
const plugins = createMemo(() =>
|
||||||
(sync.data.config.plugin ?? []).map((item) => (typeof item === "string" ? item : item[0])),
|
(sync().data.config.plugin ?? []).map((item) => (typeof item === "string" ? item : item[0])),
|
||||||
)
|
)
|
||||||
const pluginCount = createMemo(() => plugins().length)
|
const pluginCount = createMemo(() => plugins().length)
|
||||||
const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "opencode.json"))
|
const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "opencode.json"))
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ export function StatusPopover() {
|
|||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const [shown, setShown] = createSignal(false)
|
const [shown, setShown] = createSignal(false)
|
||||||
const ready = createMemo(() => global.servers.health[server.key]?.healthy === false || sync.data.mcp_ready)
|
const ready = createMemo(() => global.servers.health[server.key]?.healthy === false || sync().data.mcp_ready)
|
||||||
const mcpIssue = createMemo(() => {
|
const mcpIssue = createMemo(() => {
|
||||||
const mcp = Object.values(sync.data.mcp ?? {})
|
const mcp = Object.values(sync().data.mcp ?? {})
|
||||||
const failed = mcp.some((item) => item.status === "failed" || item.status === "needs_client_registration")
|
const failed = mcp.some((item) => item.status === "failed" || item.status === "needs_client_registration")
|
||||||
const warn = mcp.some((item) => item.status === "needs_auth")
|
const warn = mcp.some((item) => item.status === "needs_auth")
|
||||||
if (failed) return "critical" as const
|
if (failed) return "critical" as const
|
||||||
@@ -86,9 +86,9 @@ function DirectoryStatusPopover() {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const [shown, setShown] = createSignal(false)
|
const [shown, setShown] = createSignal(false)
|
||||||
const serverHealth = () => global.servers.health[server.key]?.healthy
|
const serverHealth = () => global.servers.health[server.key]?.healthy
|
||||||
const ready = createMemo(() => serverHealth() === false || sync.data.mcp_ready)
|
const ready = createMemo(() => serverHealth() === false || sync().data.mcp_ready)
|
||||||
const mcpIssue = createMemo(() => {
|
const mcpIssue = createMemo(() => {
|
||||||
const mcp = Object.values(sync.data.mcp ?? {})
|
const mcp = Object.values(sync().data.mcp ?? {})
|
||||||
const failed = mcp.some((item) => item.status === "failed" || item.status === "needs_client_registration")
|
const failed = mcp.some((item) => item.status === "failed" || item.status === "needs_client_registration")
|
||||||
const warn = mcp.some((item) => item.status === "needs_auth")
|
const warn = mcp.some((item) => item.status === "needs_auth")
|
||||||
if (failed) return "critical" as const
|
if (failed) return "critical" as const
|
||||||
|
|||||||
@@ -161,9 +161,9 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const directory = sdk.directory
|
const directory = sdk().directory
|
||||||
const client = sdk.client
|
const client = sdk().client
|
||||||
const url = sdk.url
|
const url = sdk().url
|
||||||
const auth = server.current?.http
|
const auth = server.current?.http
|
||||||
const username = auth?.username ?? "opencode"
|
const username = auth?.username ?? "opencode"
|
||||||
const password = auth?.password ?? ""
|
const password = auth?.password ?? ""
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
item.type === "session" && item.server === route.server && item.sessionId === route.sessionId,
|
item.type === "session" && item.server === route.server && item.sessionId === route.sessionId,
|
||||||
)
|
)
|
||||||
if (main) return main
|
if (main) return main
|
||||||
const sync = serverSync.createDirSyncContext(route.dir)
|
const sync = serverSync().createDirSyncContext(route.dir)
|
||||||
const session = sync.session.get(route.sessionId)
|
const session = sync.session.get(route.sessionId)
|
||||||
if (session?.parentID) {
|
if (session?.parentID) {
|
||||||
const parentID = session.parentID
|
const parentID = session.parentID
|
||||||
@@ -312,7 +312,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
if (tab) return
|
if (tab) return
|
||||||
|
|
||||||
if (route.type === "session") {
|
if (route.type === "session") {
|
||||||
const sync = serverSync.createDirSyncContext(route.dir)
|
const sync = serverSync().createDirSyncContext(route.dir)
|
||||||
const session = sync.session.get(route.sessionId)
|
const session = sync.session.get(route.sessionId)
|
||||||
if (!session) return
|
if (!session) return
|
||||||
const sessionId = session.parentID ?? session.id
|
const sessionId = session.parentID ?? session.id
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
|
|||||||
const decoded = decodeSessionKey(key)
|
const decoded = decodeSessionKey(key)
|
||||||
return createRoot((dispose) => ({
|
return createRoot((dispose) => ({
|
||||||
value: createCommentSession(
|
value: createCommentSession(
|
||||||
serverSDK.scope,
|
serverSDK().scope,
|
||||||
decoded.dir,
|
decoded.dir,
|
||||||
decoded.id === WORKSPACE_KEY ? undefined : decoded.id,
|
decoded.id === WORKSPACE_KEY ? undefined : decoded.id,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
||||||
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
|
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
|
||||||
import { diffs as list, message as clean } from "@/utils/diffs"
|
import { diffs as list, message as clean } from "@/utils/diffs"
|
||||||
import { createServerSdkContext, useServerSDK } from "./server-sdk"
|
import { type createServerSdkContext } from "./server-sdk"
|
||||||
import { type createServerSyncContextInner } from "./server-sync"
|
import { type createServerSyncContextInner } from "./server-sync"
|
||||||
|
|
||||||
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
||||||
@@ -174,7 +174,7 @@ function setOptimisticRemove(setStore: (...args: unknown[]) => void, input: Opti
|
|||||||
export const createDirSyncContext = (
|
export const createDirSyncContext = (
|
||||||
directory: string,
|
directory: string,
|
||||||
serverSync: ReturnType<typeof createServerSyncContextInner>,
|
serverSync: ReturnType<typeof createServerSyncContextInner>,
|
||||||
serverSDK: ReturnType<typeof createServerSdkContext> = useServerSDK(),
|
serverSDK: ReturnType<typeof createServerSdkContext>,
|
||||||
) => {
|
) => {
|
||||||
const client = serverSDK.createClient({ directory, throwOnError: true })
|
const client = serverSDK.createClient({ directory, throwOnError: true })
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
|
|
||||||
const scope = createMemo(() => sdk.directory)
|
const scope = createMemo(() => sdk().directory)
|
||||||
const path = createPathHelpers(scope)
|
const path = createPathHelpers(scope)
|
||||||
const tabs = layout.tabs(() =>
|
const tabs = layout.tabs(() =>
|
||||||
SessionStateKey.from(serverSDK.scope, SessionRouteKey.fromRoute(params.dir, params.id)),
|
SessionStateKey.from(serverSDK().scope, SessionRouteKey.fromRoute(params.dir, params.id)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const inflight = new Map<string, Promise<void>>()
|
const inflight = new Map<string, Promise<void>>()
|
||||||
@@ -78,7 +78,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
const tree = createFileTreeStore({
|
const tree = createFileTreeStore({
|
||||||
scope,
|
scope,
|
||||||
normalizeDir: path.normalizeDir,
|
normalizeDir: path.normalizeDir,
|
||||||
list: (dir) => sdk.client.file.list({ path: dir }).then((x) => x.data ?? []),
|
list: (dir) => sdk().client.file.list({ path: dir }).then((x) => x.data ?? []),
|
||||||
onError: (message) => {
|
onError: (message) => {
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
@@ -112,7 +112,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewCache = createFileViewCache(serverSDK.scope)
|
const viewCache = createFileViewCache(serverSDK().scope)
|
||||||
const view = createMemo(() => viewCache.load(scope(), params.id))
|
const view = createMemo(() => viewCache.load(scope(), params.id))
|
||||||
|
|
||||||
const ensure = (file: string) => {
|
const ensure = (file: string) => {
|
||||||
@@ -176,7 +176,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
|
|
||||||
setLoading(file)
|
setLoading(file)
|
||||||
|
|
||||||
const promise = sdk.client.file
|
const promise = sdk().client.file
|
||||||
.read({ path: file })
|
.read({ path: file })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
if (scope() !== directory) return
|
if (scope() !== directory) return
|
||||||
@@ -200,12 +200,12 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const search = (query: string, dirs: "true" | "false") =>
|
const search = (query: string, dirs: "true" | "false") =>
|
||||||
sdk.client.find.files({ query, dirs }).then(
|
sdk().client.find.files({ query, dirs }).then(
|
||||||
(x) => (x.data ?? []).map(path.normalize),
|
(x) => (x.data ?? []).map(path.normalize),
|
||||||
() => [],
|
() => [],
|
||||||
)
|
)
|
||||||
|
|
||||||
const stop = sdk.event.listen((e) => {
|
const stop = sdk().event.listen((e) => {
|
||||||
invalidateFromWatcher(e.details, {
|
invalidateFromWatcher(e.details, {
|
||||||
normalize: path.normalize,
|
normalize: path.normalize,
|
||||||
hasFile: (file) => Boolean(store.file[file]),
|
hasFile: (file) => Boolean(store.file[file]),
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = Persist.serverGlobal(serverSdk.scope, "layout", ["layout.v6"])
|
const target = Persist.serverGlobal(serverSdk().scope, "layout", ["layout.v6"])
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
{ ...target, migrate },
|
{ ...target, migrate },
|
||||||
createStore({
|
createStore({
|
||||||
@@ -409,11 +409,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function enrich(project: { worktree: string; expanded: boolean }) {
|
function enrich(project: { worktree: string; expanded: boolean }) {
|
||||||
const [childStore] = serverSync.child(project.worktree, { bootstrap: false })
|
const [childStore] = serverSync().child(project.worktree, { bootstrap: false })
|
||||||
const projectID = childStore.project
|
const projectID = childStore.project
|
||||||
const metadata = projectID
|
const metadata = projectID
|
||||||
? serverSync.data.project.find((x) => x.id === projectID)
|
? serverSync().data.project.find((x) => x.id === projectID)
|
||||||
: serverSync.data.project.find((x) => x.worktree === project.worktree)
|
: serverSync().data.project.find((x) => x.worktree === project.worktree)
|
||||||
|
|
||||||
// Preserve local icon override from per-workspace localStorage cache (childStore.icon).
|
// Preserve local icon override from per-workspace localStorage cache (childStore.icon).
|
||||||
// Without this, different subdirectories of the same git repo would share the same
|
// Without this, different subdirectories of the same git repo would share the same
|
||||||
@@ -427,7 +427,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
|
|
||||||
const roots = createMemo(() => {
|
const roots = createMemo(() => {
|
||||||
const map = new Map<string, string>()
|
const map = new Map<string, string>()
|
||||||
for (const project of serverSync.data.project) {
|
for (const project of serverSync().data.project) {
|
||||||
const sandboxes = project.sandboxes ?? []
|
const sandboxes = project.sandboxes ?? []
|
||||||
for (const sandbox of sandboxes) {
|
for (const sandbox of sandboxes) {
|
||||||
map.set(sandbox, project.worktree)
|
map.set(sandbox, project.worktree)
|
||||||
@@ -493,12 +493,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const projects = enriched()
|
const projects = enriched()
|
||||||
if (projects.length === 0) return
|
if (projects.length === 0) return
|
||||||
if (!serverSync.ready) return
|
if (!serverSync().ready) return
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
if (!project.id) continue
|
if (!project.id) continue
|
||||||
if (project.id === "global") continue
|
if (project.id === "global") continue
|
||||||
serverSync.project.icon(project.worktree, project.icon?.override)
|
serverSync().project.icon(project.worktree, project.icon?.override)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -532,11 +532,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
colorRequested.set(worktree, color)
|
colorRequested.set(worktree, color)
|
||||||
|
|
||||||
if (project.id === "global") {
|
if (project.id === "global") {
|
||||||
serverSync.project.meta(worktree, { icon: { color } })
|
serverSync().project.meta(worktree, { icon: { color } })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
void serverSdk.client.project
|
void serverSdk().client.project
|
||||||
.update({ projectID: project.id, directory: worktree, icon: { color } })
|
.update({ projectID: project.id, directory: worktree, icon: { color } })
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
||||||
@@ -554,7 +554,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
sessionTimer = undefined
|
sessionTimer = undefined
|
||||||
void Promise.all(
|
void Promise.all(
|
||||||
server.projects.list().map((project) => {
|
server.projects.list().map((project) => {
|
||||||
return serverSync.project.loadSessions(project.worktree)
|
return serverSync().project.loadSessions(project.worktree)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}, 0)
|
}, 0)
|
||||||
@@ -584,7 +584,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
open(directory: string) {
|
open(directory: string) {
|
||||||
const root = rootFor(directory)
|
const root = rootFor(directory)
|
||||||
if (server.projects.list().find((x) => x.worktree === root)) return
|
if (server.projects.list().find((x) => x.worktree === root)) return
|
||||||
void serverSync.project.loadSessions(root)
|
void serverSync().project.loadSessions(root)
|
||||||
server.projects.open(root)
|
server.projects.open(root)
|
||||||
},
|
},
|
||||||
close(directory: string) {
|
close(directory: string) {
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const models = useModels()
|
const models = useModels()
|
||||||
|
|
||||||
const id = createMemo(() => params.id || undefined)
|
const id = createMemo(() => params.id || undefined)
|
||||||
const list = createMemo(() => sync.data.agent.filter((item) => item.mode !== "subagent" && !item.hidden))
|
const list = createMemo(() => sync().data.agent.filter((item) => item.mode !== "subagent" && !item.hidden))
|
||||||
const connected = createMemo(() => new Set(providers.connected().map((item) => item.id)))
|
const connected = createMemo(() => new Set(providers.connected().map((item) => item.id)))
|
||||||
|
|
||||||
const [saved, setSaved] = persisted(
|
const [saved, setSaved] = persisted(
|
||||||
{
|
{
|
||||||
...Persist.serverWorkspace(serverSDK.scope, sdk.directory, "model-selection", ["model-selection.v1"]),
|
...Persist.serverWorkspace(serverSDK().scope, sdk().directory, "model-selection", ["model-selection.v1"]),
|
||||||
migrate,
|
migrate,
|
||||||
},
|
},
|
||||||
createStore<Saved>({
|
createStore<Saved>({
|
||||||
@@ -124,14 +124,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const scope = createMemo<State | undefined>(() => {
|
const scope = createMemo<State | undefined>(() => {
|
||||||
const session = id()
|
const session = id()
|
||||||
if (!session) return store.draft
|
if (!session) return store.draft
|
||||||
return saved.session[session] ?? handoff.get(handoffKey(serverSDK.scope, sdk.directory, session))
|
return saved.session[session] ?? handoff.get(handoffKey(serverSDK().scope, sdk().directory, session))
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const session = id()
|
const session = id()
|
||||||
if (!session) return
|
if (!session) return
|
||||||
|
|
||||||
const key = handoffKey(serverSDK.scope, sdk.directory, session)
|
const key = handoffKey(serverSDK().scope, sdk().directory, session)
|
||||||
const next = handoff.get(key)
|
const next = handoff.get(key)
|
||||||
if (!next) return
|
if (!next) return
|
||||||
if (saved.session[session] !== undefined) {
|
if (saved.session[session] !== undefined) {
|
||||||
@@ -144,8 +144,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const configuredModel = () => {
|
const configuredModel = () => {
|
||||||
if (!sync.data.config.model) return
|
const configured = sync().data.config.model
|
||||||
const [providerID, modelID] = sync.data.config.model.split("/")
|
if (!configured) return
|
||||||
|
const [providerID, modelID] = configured.split("/")
|
||||||
const model = { providerID, modelID }
|
const model = { providerID, modelID }
|
||||||
if (validModel(model)) return model
|
if (validModel(model)) return model
|
||||||
}
|
}
|
||||||
@@ -363,7 +364,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
slug: createMemo(() => base64Encode(sdk.directory)),
|
slug: createMemo(() => base64Encode(sdk().directory)),
|
||||||
model,
|
model,
|
||||||
agent,
|
agent,
|
||||||
session: {
|
session: {
|
||||||
@@ -374,13 +375,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const next = clone(snapshot())
|
const next = clone(snapshot())
|
||||||
if (!next) return
|
if (!next) return
|
||||||
|
|
||||||
if (dir === sdk.directory) {
|
if (dir === sdk().directory) {
|
||||||
setSaved("session", session, next)
|
setSaved("session", session, next)
|
||||||
setStore("draft", undefined)
|
setStore("draft", undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handoff.set(handoffKey(serverSDK.scope, dir, session), next)
|
handoff.set(handoffKey(serverSDK().scope, dir, session), next)
|
||||||
setStore("draft", undefined)
|
setStore("draft", undefined)
|
||||||
},
|
},
|
||||||
restore(msg: { sessionID: string; agent: string; model: ModelKey }) {
|
restore(msg: { sessionID: string; agent: string; model: ModelKey }) {
|
||||||
@@ -388,7 +389,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
if (!session) return
|
if (!session) return
|
||||||
if (msg.sessionID !== session) return
|
if (msg.sessionID !== session) return
|
||||||
if (saved.session[session] !== undefined) return
|
if (saved.session[session] !== undefined) return
|
||||||
if (handoff.has(handoffKey(serverSDK.scope, sdk.directory, session))) return
|
if (handoff.has(handoffKey(serverSDK().scope, sdk().directory, session))) return
|
||||||
|
|
||||||
setSaved("session", session, {
|
setSaved("session", session, {
|
||||||
agent: msg.agent,
|
agent: msg.agent,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function useMcpToggle() {
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
return useMutation(() => ({
|
return useMutation(() => ({
|
||||||
mutationFn: sync.mcp.toggle,
|
mutationFn: sync().mcp.toggle,
|
||||||
onError: (error) =>
|
onError: (error) =>
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
const currentSession = createMemo(() => params.id)
|
const currentSession = createMemo(() => params.id)
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
Persist.serverGlobal(serverSDK.scope, "notification", ["notification.v1"]),
|
Persist.serverGlobal(serverSDK().scope, "notification", ["notification.v1"]),
|
||||||
createStore({
|
createStore({
|
||||||
list: [] as Notification[],
|
list: [] as Notification[],
|
||||||
}),
|
}),
|
||||||
@@ -208,10 +208,10 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
|
|
||||||
const lookup = async (directory: string, sessionID?: string) => {
|
const lookup = async (directory: string, sessionID?: string) => {
|
||||||
if (!sessionID) return undefined
|
if (!sessionID) return undefined
|
||||||
const [syncStore] = serverSync.child(directory, { bootstrap: false })
|
const [syncStore] = serverSync().child(directory, { bootstrap: false })
|
||||||
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
||||||
if (match.found) return syncStore.session[match.index]
|
if (match.found) return syncStore.session[match.index]
|
||||||
return serverSDK.client.session
|
return serverSDK().client.session
|
||||||
.get({ directory, sessionID })
|
.get({ directory, sessionID })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
@@ -286,7 +286,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsub = serverSDK.event.listen((e) => {
|
const unsub = serverSDK().event.listen((e) => {
|
||||||
const event = e.details
|
const event = e.details
|
||||||
if (event.type !== "session.idle" && event.type !== "session.error") return
|
if (event.type !== "session.idle" && event.type !== "session.error") return
|
||||||
|
|
||||||
|
|||||||
@@ -55,13 +55,13 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
const permissionsEnabled = createMemo(() => {
|
const permissionsEnabled = createMemo(() => {
|
||||||
const directory = decode64(params.dir)
|
const directory = decode64(params.dir)
|
||||||
if (!directory) return false
|
if (!directory) return false
|
||||||
const [store] = serverSync.child(directory)
|
const [store] = serverSync().child(directory)
|
||||||
return hasPermissionPromptRules(store.config.permission)
|
return hasPermissionPromptRules(store.config.permission)
|
||||||
})
|
})
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
{
|
{
|
||||||
...Persist.serverGlobal(serverSDK.scope, "permission", ["permission.v3"]),
|
...Persist.serverGlobal(serverSDK().scope, "permission", ["permission.v3"]),
|
||||||
migrate(value) {
|
migrate(value) {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) return value
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
if (!ready()) return
|
if (!ready()) return
|
||||||
const directory = decode64(params.dir)
|
const directory = decode64(params.dir)
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
const [childStore] = serverSync.child(directory)
|
const [childStore] = serverSync().child(directory)
|
||||||
const perm = childStore.config.permission
|
const perm = childStore.config.permission
|
||||||
if (typeof perm === "string" && perm === "allow") {
|
if (typeof perm === "string" && perm === "allow") {
|
||||||
const key = directoryAcceptKey(directory)
|
const key = directoryAcceptKey(directory)
|
||||||
@@ -119,7 +119,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
}
|
}
|
||||||
|
|
||||||
const respond: PermissionRespondFn = (input) => {
|
const respond: PermissionRespondFn = (input) => {
|
||||||
serverSDK.client.permission.respond(input).catch(() => {
|
serverSDK().client.permission.respond(input).catch(() => {
|
||||||
responded.delete(input.permissionID)
|
responded.delete(input.permissionID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isAutoAccepting(sessionID: string, directory?: string) {
|
function isAutoAccepting(sessionID: string, directory?: string) {
|
||||||
const session = directory ? serverSync.child(directory, { bootstrap: false })[0].session : []
|
const session = directory ? serverSync().child(directory, { bootstrap: false })[0].session : []
|
||||||
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
|
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
|
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
|
||||||
const session = directory ? serverSync.child(directory, { bootstrap: false })[0].session : []
|
const session = directory ? serverSync().child(directory, { bootstrap: false })[0].session : []
|
||||||
return autoRespondsPermission(store.autoAccept, session, permission, directory)
|
return autoRespondsPermission(store.autoAccept, session, permission, directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribe = serverSDK.event.listen((e) => {
|
const unsubscribe = serverSDK().event.listen((e) => {
|
||||||
const event = e.details
|
const event = e.details
|
||||||
if (event?.type !== "permission.asked") return
|
if (event?.type !== "permission.asked") return
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
serverSDK.client.permission
|
serverSDK().client.permission
|
||||||
.list({ directory })
|
.list({ directory })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
if (!isAutoAcceptingDirectory(directory)) return
|
if (!isAutoAcceptingDirectory(directory)) return
|
||||||
@@ -211,7 +211,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
serverSDK.client.permission
|
serverSDK().client.permission
|
||||||
.list({ directory })
|
.list({ directory })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
if (enableVersion.get(key) !== version) return
|
if (enableVersion.get(key) !== version) return
|
||||||
@@ -269,7 +269,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
},
|
},
|
||||||
permissionsEnabled,
|
permissionsEnabled,
|
||||||
isPermissionAllowAll(directory: string) {
|
isPermissionAllowAll(directory: string) {
|
||||||
const [childStore] = serverSync.child(directory)
|
const [childStore] = serverSync().child(directory)
|
||||||
const perm = childStore.config.permission
|
const perm = childStore.config.permission
|
||||||
return typeof perm === "string" && perm === "allow"
|
return typeof perm === "string" && perm === "allow"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
|
|||||||
|
|
||||||
const entry = createRoot(
|
const entry = createRoot(
|
||||||
(dispose) => ({
|
(dispose) => ({
|
||||||
value: createPromptSession(serverSDK.scope, scope),
|
value: createPromptSession(serverSDK().scope, scope),
|
||||||
dispose,
|
dispose,
|
||||||
}),
|
}),
|
||||||
owner,
|
owner,
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { useServerSDK } from "./server-sdk"
|
import { type Accessor, createMemo } from "solid-js"
|
||||||
|
import { type ServerSDK, useServerSDK } from "./server-sdk"
|
||||||
|
|
||||||
|
export type DirectorySDK = ReturnType<ServerSDK["createDirSdkContext"]>
|
||||||
|
|
||||||
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||||
name: "SDK",
|
name: "SDK",
|
||||||
init: (props: { directory: string }) => {
|
// Resolves the directory-scoped SDK reactively from the (possibly changing) server.
|
||||||
|
init: (props: { directory: string | Accessor<string> }) => {
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
|
return createMemo(() => {
|
||||||
return serverSDK.createDirSdkContext(props.directory)
|
const directory = typeof props.directory === "function" ? props.directory() : props.directory
|
||||||
|
return serverSDK().createDirSdkContext(directory)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Event } from "@opencode-ai/sdk/v2/client"
|
|||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { batch, onCleanup, onMount } from "solid-js"
|
import { type Accessor, batch, createMemo, onCleanup, onMount } from "solid-js"
|
||||||
import { createSdkForServer } from "@/utils/server"
|
import { createSdkForServer } from "@/utils/server"
|
||||||
import { useLanguage } from "./language"
|
import { useLanguage } from "./language"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
@@ -21,7 +21,7 @@ export function resumeStreamAfterPageShow(event: PageTransitionEvent, start: ()
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createServerSdkContext(server: ServerConnection.Any, scope: ServerScope) {
|
function createServerSdkContextBase(server: ServerConnection.Any, scope: ServerScope) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
|
|
||||||
@@ -261,21 +261,31 @@ export function createServerSdkContext(server: ServerConnection.Any, scope: Serv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServerSDK = ReturnType<typeof createServerSdkContext>
|
type ServerSDKBase = ReturnType<typeof createServerSdkContextBase>
|
||||||
|
export type ServerSDK = ServerSDKBase & {
|
||||||
|
createDirSdkContext: (directory: string) => ReturnType<typeof createDirSdkContext>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createServerSdkContext(server: ServerConnection.Any, scope: ServerScope): ServerSDK {
|
||||||
|
const sdk = createServerSdkContextBase(server, scope)
|
||||||
|
return Object.assign(sdk, {
|
||||||
|
createDirSdkContext: createRefCountMap((dir) => createDirSdkContext(dir, sdk)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
|
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
|
||||||
name: "ServerSDK",
|
name: "ServerSDK",
|
||||||
init: (props: { server?: ServerConnection.Any }) => {
|
// Returns an accessor so the resolved server can change reactively (e.g. a
|
||||||
|
// /new-session draft retargeting its server) without re-instantiating the subtree.
|
||||||
|
init: (props: { server?: Accessor<ServerConnection.Any | undefined> }) => {
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
|
|
||||||
const conn = props.server ?? server.current
|
return createMemo<ServerSDK>(() => {
|
||||||
if (!conn) throw new Error(language.t("error.serverSDK.noServerAvailable"))
|
const conn = props.server?.() ?? server.current
|
||||||
|
if (!conn) throw new Error(language.t("error.serverSDK.noServerAvailable"))
|
||||||
const ctx = global.createServerCtx(conn)
|
return global.createServerCtx(conn).sdk
|
||||||
return Object.assign(ctx.sdk, {
|
|
||||||
createDirSdkContext: createRefCountMap((dir) => createDirSdkContext(dir, ctx.sdk)),
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -284,7 +294,7 @@ type SDKEventMap = {
|
|||||||
[key in Event["type"]]: Extract<Event, { type: key }>
|
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDirSdkContext(directory: string, serverSDK: ServerSDK) {
|
function createDirSdkContext(directory: string, serverSDK: ServerSDKBase) {
|
||||||
const client = serverSDK.createClient({
|
const client = serverSDK.createClient({
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { Config, OpencodeClient, Path, Project, ProviderAuthResponse, Todo } from "@opencode-ai/sdk/v2/client"
|
import type { Config, OpencodeClient, Path, Project, ProviderAuthResponse, Todo } from "@opencode-ai/sdk/v2/client"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/core/util/path"
|
||||||
import { batch, getOwner, onCleanup, onMount, untrack } from "solid-js"
|
import { type Accessor, batch, createMemo, getOwner, onCleanup, onMount, untrack } from "solid-js"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import type { InitError } from "../pages/error"
|
import type { InitError } from "../pages/error"
|
||||||
import { ServerSDK, useServerSDK } from "./server-sdk"
|
import { ServerSDK } from "./server-sdk"
|
||||||
import {
|
import {
|
||||||
bootstrapDirectory,
|
bootstrapDirectory,
|
||||||
bootstrapGlobal,
|
bootstrapGlobal,
|
||||||
@@ -84,8 +84,7 @@ function makeQueryOptionsApi(
|
|||||||
}
|
}
|
||||||
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
|
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
|
||||||
|
|
||||||
export function createServerSyncContextInner(_serverSDK?: ServerSDK) {
|
export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
||||||
const serverSDK: ServerSDK = _serverSDK ?? useServerSDK()
|
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const owner = getOwner()
|
const owner = getOwner()
|
||||||
if (!owner) throw new Error("ServerSync must be created within owner")
|
if (!owner) throw new Error("ServerSync must be created within owner")
|
||||||
@@ -507,33 +506,37 @@ export function createServerSyncContextInner(_serverSDK?: ServerSDK) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createServerSyncContext(_serverSDK?: ServerSDK) {
|
export function createServerSyncContext(serverSDK: ServerSDK) {
|
||||||
const inner = createServerSyncContextInner(_serverSDK)
|
const inner = createServerSyncContextInner(serverSDK)
|
||||||
return Object.assign(inner, {
|
return Object.assign(inner, {
|
||||||
createDirSyncContext: createRefCountMap(
|
createDirSyncContext: createRefCountMap(
|
||||||
(dir) => createDirSyncContext(dir, inner, _serverSDK),
|
(dir) => createDirSyncContext(dir, inner, serverSDK),
|
||||||
(dir) => inner.disableMcp(dir),
|
(dir) => inner.disableMcp(dir),
|
||||||
directoryKey,
|
directoryKey,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServerSync = ReturnType<typeof createServerSyncContext>
|
||||||
|
|
||||||
export const { use: useServerSync, provider: ServerSyncProvider } = createSimpleContext({
|
export const { use: useServerSync, provider: ServerSyncProvider } = createSimpleContext({
|
||||||
name: "ServerSync",
|
name: "ServerSync",
|
||||||
gate: false,
|
// Returns an accessor so the resolved server can change reactively without
|
||||||
init: (props: { server?: ServerConnection.Any }) => {
|
// re-instantiating the subtree (mirrors useServerSDK).
|
||||||
|
init: (props: { server?: Accessor<ServerConnection.Any | undefined> }) => {
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
|
|
||||||
const conn = props.server ?? server.current
|
return createMemo<ServerSync>(() => {
|
||||||
if (!conn) throw new Error(language.t("error.serverSDK.noServerAvailable"))
|
const conn = props.server?.() ?? server.current
|
||||||
const ctx = global.createServerCtx(conn)
|
if (!conn) throw new Error(language.t("error.serverSDK.noServerAvailable"))
|
||||||
|
return global.createServerCtx(conn).sync
|
||||||
return ctx.sync
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export function useQueryOptions() {
|
export function useQueryOptions() {
|
||||||
return useServerSync().queryOptions
|
const sync = useServerSync()
|
||||||
|
return createMemo(() => sync().queryOptions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Binary } from "@opencode-ai/core/util/binary"
|
import { Binary } from "@opencode-ai/core/util/binary"
|
||||||
|
import { createMemo } from "solid-js"
|
||||||
import { useServerSync } from "./server-sync"
|
import { useServerSync } from "./server-sync"
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK } from "./sdk"
|
||||||
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
||||||
@@ -112,5 +113,7 @@ export const useSync = () => {
|
|||||||
const serverSync = useServerSync()
|
const serverSync = useServerSync()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
|
||||||
return serverSync.createDirSyncContext(sdk.directory)
|
return createMemo(() => serverSync().createDirSyncContext(sdk().directory))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DirectorySync = ReturnType<ReturnType<typeof useSync>>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createStore, produce } from "solid-js/store"
|
|||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "solid-js"
|
import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "solid-js"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK, type DirectorySDK } from "./sdk"
|
||||||
import type { Platform } from "./platform"
|
import type { Platform } from "./platform"
|
||||||
import { useServer } from "./server"
|
import { useServer } from "./server"
|
||||||
import { defaultTitle, titleNumber } from "./terminal-title"
|
import { defaultTitle, titleNumber } from "./terminal-title"
|
||||||
@@ -143,7 +143,7 @@ export function clearWorkspaceTerminals(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createWorkspaceTerminalSession(
|
function createWorkspaceTerminalSession(
|
||||||
sdk: ReturnType<typeof useSDK>,
|
sdk: DirectorySDK,
|
||||||
dir: string,
|
dir: string,
|
||||||
scope: ServerScopeValue,
|
scope: ServerScopeValue,
|
||||||
legacySessionID?: string,
|
legacySessionID?: string,
|
||||||
@@ -202,7 +202,7 @@ function createWorkspaceTerminalSession(
|
|||||||
})
|
})
|
||||||
onCleanup(unsub)
|
onCleanup(unsub)
|
||||||
|
|
||||||
const update = (client: ReturnType<typeof useSDK>["client"], pty: Partial<LocalPTY> & { id: string }) => {
|
const update = (client: DirectorySDK["client"], pty: Partial<LocalPTY> & { id: string }) => {
|
||||||
const index = store.all.findIndex((x) => x.id === pty.id)
|
const index = store.all.findIndex((x) => x.id === pty.id)
|
||||||
const previous = index >= 0 ? store.all[index] : undefined
|
const previous = index >= 0 ? store.all[index] : undefined
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@@ -223,7 +223,7 @@ function createWorkspaceTerminalSession(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const clone = async (client: ReturnType<typeof useSDK>["client"], id: string) => {
|
const clone = async (client: DirectorySDK["client"], id: string) => {
|
||||||
const index = store.all.findIndex((x) => x.id === id)
|
const index = store.all.findIndex((x) => x.id === id)
|
||||||
const pty = store.all[index]
|
const pty = store.all[index]
|
||||||
if (!pty) return
|
if (!pty) return
|
||||||
@@ -412,7 +412,7 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
const entry = createRoot((dispose) => ({
|
const entry = createRoot((dispose) => ({
|
||||||
value: createWorkspaceTerminalSession(sdk, dir, serverScope, legacySessionID),
|
value: createWorkspaceTerminalSession(sdk(), dir, serverScope, legacySessionID),
|
||||||
dispose,
|
dispose,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ export function useProviders() {
|
|||||||
const dir = createMemo(() => decode64(params.dir) ?? "")
|
const dir = createMemo(() => decode64(params.dir) ?? "")
|
||||||
const providers = () => {
|
const providers = () => {
|
||||||
if (dir()) {
|
if (dir()) {
|
||||||
const [projectStore] = serverSync.child(dir())
|
const [projectStore] = serverSync().child(dir())
|
||||||
if (projectStore.provider_ready) return projectStore.provider
|
if (projectStore.provider_ready) return projectStore.provider
|
||||||
}
|
}
|
||||||
return serverSync.data.provider
|
return serverSync().data.provider
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
all: () => providers().all,
|
all: () => providers().all,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function DirectoryDataProvider(props: ParentProps<{ directory: string; dr
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
// A draft lives at /new-session?draftId=… and has no directory segment to normalize.
|
// A draft lives at /new-session?draftId=… and has no directory segment to normalize.
|
||||||
if (props.draftID) return
|
if (props.draftID) return
|
||||||
const next = sync.data.path.directory
|
const next = sync().data.path.directory
|
||||||
if (!next || next === props.directory) return
|
if (!next || next === props.directory) return
|
||||||
const path = location.pathname.slice(slug().length + 1)
|
const path = location.pathname.slice(slug().length + 1)
|
||||||
navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
|
navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
|
||||||
@@ -28,12 +28,12 @@ export function DirectoryDataProvider(props: ParentProps<{ directory: string; dr
|
|||||||
|
|
||||||
createResource(
|
createResource(
|
||||||
() => params.id,
|
() => params.id,
|
||||||
(id) => sync.session.sync(id).catch(() => {}),
|
(id) => sync().session.sync(id).catch(() => {}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataProvider
|
<DataProvider
|
||||||
data={sync.data}
|
data={sync().data}
|
||||||
directory={props.directory}
|
directory={props.directory}
|
||||||
onNavigateToSession={(sessionID: string) => navigate(`/${slug()}/session/${sessionID}`)}
|
onNavigateToSession={(sessionID: string) => navigate(`/${slug()}/session/${sessionID}`)}
|
||||||
onSessionHref={(sessionID: string) => `/${slug()}/session/${sessionID}`}
|
onSessionHref={(sessionID: string) => `/${slug()}/session/${sessionID}`}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { DialogSelectServer, useServerManagementController } from "@/components/
|
|||||||
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
||||||
import { ServerConnection, useServer } from "@/context/server"
|
import { ServerConnection, useServer } from "@/context/server"
|
||||||
import { sessionHasOpenTab, useTabs } from "@/context/tabs"
|
import { sessionHasOpenTab, useTabs } from "@/context/tabs"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useServerSync, type ServerSync } from "@/context/server-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useNotification } from "@/context/notification"
|
import { useNotification } from "@/context/notification"
|
||||||
import {
|
import {
|
||||||
@@ -80,7 +80,7 @@ const HOME_SEARCH_RESULT_META =
|
|||||||
let pendingHomeNavigation: { server: ServerConnection.Key; href: string } | undefined
|
let pendingHomeNavigation: { server: ServerConnection.Key; href: string } | undefined
|
||||||
|
|
||||||
function buildHomeSessionRecords(input: {
|
function buildHomeSessionRecords(input: {
|
||||||
sync: Pick<ReturnType<typeof useServerSync>, "child">
|
sync: Pick<ServerSync, "child">
|
||||||
projectDirectories: () => string[]
|
projectDirectories: () => string[]
|
||||||
projects: () => LocalProject[]
|
projects: () => LocalProject[]
|
||||||
projectByID: () => Map<string, LocalProject>
|
projectByID: () => Map<string, LocalProject>
|
||||||
@@ -149,7 +149,7 @@ function HomeDesign() {
|
|||||||
if (!conn) return
|
if (!conn) return
|
||||||
return global.createServerCtx(conn)
|
return global.createServerCtx(conn)
|
||||||
})
|
})
|
||||||
const focusedSync = () => focusedServerCtx()?.sync ?? sync
|
const focusedSync = () => focusedServerCtx()?.sync ?? sync()
|
||||||
const projects = createMemo(() => focusedServerCtx()?.projects.list() ?? layout.projects.list())
|
const projects = createMemo(() => focusedServerCtx()?.projects.list() ?? layout.projects.list())
|
||||||
const selectedProject = createMemo(() => projects().find((project) => project.worktree === state.selection.directory))
|
const selectedProject = createMemo(() => projects().find((project) => project.worktree === state.selection.directory))
|
||||||
const newSessionProject = createMemo(
|
const newSessionProject = createMemo(
|
||||||
@@ -1102,9 +1102,9 @@ function LegacyHome() {
|
|||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const homedir = createMemo(() => sync.data.path.home)
|
const homedir = createMemo(() => sync().data.path.home)
|
||||||
const recent = createMemo(() => {
|
const recent = createMemo(() => {
|
||||||
return sync.data.project
|
return sync().data.project
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created))
|
.sort((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created))
|
||||||
.slice(0, 5)
|
.slice(0, 5)
|
||||||
@@ -1164,7 +1164,7 @@ function LegacyHome() {
|
|||||||
{server.name}
|
{server.name}
|
||||||
</Button>
|
</Button>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={sync.data.project.length > 0}>
|
<Match when={sync().data.project.length > 0}>
|
||||||
<div class="mt-20 w-full flex flex-col gap-4">
|
<div class="mt-20 w-full flex flex-col gap-4">
|
||||||
<div class="flex gap-2 items-center justify-between pl-3">
|
<div class="flex gap-2 items-center justify-between pl-3">
|
||||||
<div class="text-14-medium text-text-strong">{language.t("home.recentProjects")}</div>
|
<div class="text-14-medium text-text-strong">{language.t("home.recentProjects")}</div>
|
||||||
@@ -1191,7 +1191,7 @@ function LegacyHome() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!sync.ready}>
|
<Match when={!sync().ready}>
|
||||||
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||||
<div class="text-12-regular text-text-weak">{language.t("common.loading")}</div>
|
<div class="text-12-regular text-text-weak">{language.t("common.loading")}</div>
|
||||||
<Button class="px-3" onClick={chooseProject}>
|
<Button class="px-3" onClick={chooseProject}>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ import { SidebarContent } from "./layout/sidebar-shell"
|
|||||||
export default function Layout(props: ParentProps) {
|
export default function Layout(props: ParentProps) {
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const [store, setStore, , ready] = persisted(
|
const [store, setStore, , ready] = persisted(
|
||||||
Persist.serverGlobal(serverSDK.scope, "layout.page", ["layout.page.v1"]),
|
Persist.serverGlobal(serverSDK().scope, "layout.page", ["layout.page.v1"]),
|
||||||
createStore({
|
createStore({
|
||||||
lastProjectSession: {} as { [directory: string]: { directory: string; id: string; at: number } },
|
lastProjectSession: {} as { [directory: string]: { directory: string; id: string; at: number } },
|
||||||
activeProject: undefined as string | undefined,
|
activeProject: undefined as string | undefined,
|
||||||
@@ -140,7 +140,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!slug) return { slug, dir: "" }
|
if (!slug) return { slug, dir: "" }
|
||||||
const dir = decode64(slug)
|
const dir = decode64(slug)
|
||||||
if (!dir) return { slug, dir: "" }
|
if (!dir) return { slug, dir: "" }
|
||||||
const store = serverSync.peek(dir, { bootstrap: false })
|
const store = serverSync().peek(dir, { bootstrap: false })
|
||||||
return {
|
return {
|
||||||
slug,
|
slug,
|
||||||
store,
|
store,
|
||||||
@@ -213,7 +213,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
active: () => state.hoverProject,
|
active: () => state.hoverProject,
|
||||||
el: () => state.nav?.querySelector<HTMLElement>("[data-component='sidebar-rail']") ?? state.nav,
|
el: () => state.nav?.querySelector<HTMLElement>("[data-component='sidebar-rail']") ?? state.nav,
|
||||||
onActivate: (directory) => {
|
onActivate: (directory) => {
|
||||||
serverSync.child(directory)
|
serverSync().child(directory)
|
||||||
setState("hoverProject", directory)
|
setState("hoverProject", directory)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -397,17 +397,17 @@ export default function Layout(props: ParentProps) {
|
|||||||
alertedAtBySession.delete(sessionKey)
|
alertedAtBySession.delete(sessionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsub = serverSDK.event.listen((e) => {
|
const unsub = serverSDK().event.listen((e) => {
|
||||||
if (e.details?.type === "worktree.ready") {
|
if (e.details?.type === "worktree.ready") {
|
||||||
setBusy(e.name, false)
|
setBusy(e.name, false)
|
||||||
WorktreeState.ready(serverSDK.scope, e.name)
|
WorktreeState.ready(serverSDK().scope, e.name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.details?.type === "worktree.failed") {
|
if (e.details?.type === "worktree.failed") {
|
||||||
setBusy(e.name, false)
|
setBusy(e.name, false)
|
||||||
WorktreeState.failed(
|
WorktreeState.failed(
|
||||||
serverSDK.scope,
|
serverSDK().scope,
|
||||||
e.name,
|
e.name,
|
||||||
e.details.properties?.message ?? language.t("common.requestFailed"),
|
e.details.properties?.message ?? language.t("common.requestFailed"),
|
||||||
)
|
)
|
||||||
@@ -435,7 +435,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const props = e.details.properties
|
const props = e.details.properties
|
||||||
if (e.details.type === "permission.asked" && permission.autoResponds(e.details.properties, directory)) return
|
if (e.details.type === "permission.asked" && permission.autoResponds(e.details.properties, directory)) return
|
||||||
|
|
||||||
const [store] = serverSync.child(directory, { bootstrap: false })
|
const [store] = serverSync().child(directory, { bootstrap: false })
|
||||||
const session = store.session.find((s) => s.id === props.sessionID)
|
const session = store.session.find((s) => s.id === props.sessionID)
|
||||||
const sessionKey = `${directory}:${props.sessionID}`
|
const sessionKey = `${directory}:${props.sessionID}`
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!currentDir() || !currentSession) return
|
if (!currentDir() || !currentSession) return
|
||||||
const sessionKey = `${currentDir()}:${currentSession}`
|
const sessionKey = `${currentDir()}:${currentSession}`
|
||||||
dismissSessionAlert(sessionKey)
|
dismissSessionAlert(sessionKey)
|
||||||
const [store] = serverSync.child(currentDir(), { bootstrap: false })
|
const [store] = serverSync().child(currentDir(), { bootstrap: false })
|
||||||
const childSessions = store.session.filter((s) => s.parentID === currentSession)
|
const childSessions = store.session.filter((s) => s.parentID === currentSession)
|
||||||
for (const child of childSessions) {
|
for (const child of childSessions) {
|
||||||
dismissSessionAlert(`${currentDir()}:${child.id}`)
|
dismissSessionAlert(`${currentDir()}:${child.id}`)
|
||||||
@@ -536,11 +536,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
const direct = projects.find((p) => pathKey(p.worktree) === key)
|
const direct = projects.find((p) => pathKey(p.worktree) === key)
|
||||||
if (direct) return direct
|
if (direct) return direct
|
||||||
|
|
||||||
const [child] = serverSync.child(directory, { bootstrap: false })
|
const [child] = serverSync().child(directory, { bootstrap: false })
|
||||||
const id = child.project
|
const id = child.project
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
const meta = serverSync.data.project.find((p) => p.id === id)
|
const meta = serverSync().data.project.find((p) => p.id === id)
|
||||||
const root = meta?.worktree
|
const root = meta?.worktree
|
||||||
if (!root) return
|
if (!root) return
|
||||||
|
|
||||||
@@ -631,7 +631,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const result: Session[] = []
|
const result: Session[] = []
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const [dirStore] = serverSync.child(dir, { bootstrap: true })
|
const [dirStore] = serverSync().child(dir, { bootstrap: true })
|
||||||
const dirSessions = sortedRootSessions(dirStore, now)
|
const dirSessions = sortedRootSessions(dirStore, now)
|
||||||
result.push(...dirSessions)
|
result.push(...dirSessions)
|
||||||
}
|
}
|
||||||
@@ -683,10 +683,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
route()
|
route()
|
||||||
serverSDK.url
|
serverSDK().url
|
||||||
|
|
||||||
prefetchToken.value += 1
|
prefetchToken.value += 1
|
||||||
clearSessionPrefetchInflight(serverSDK.scope)
|
clearSessionPrefetchInflight(serverSDK().scope)
|
||||||
prefetchQueues.clear()
|
prefetchQueues.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -730,17 +730,17 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function prefetchMessages(directory: string, sessionID: string, token: number) {
|
async function prefetchMessages(directory: string, sessionID: string, token: number) {
|
||||||
const [store, setStore] = serverSync.child(directory, { bootstrap: false })
|
const [store, setStore] = serverSync().child(directory, { bootstrap: false })
|
||||||
|
|
||||||
return runSessionPrefetch({
|
return runSessionPrefetch({
|
||||||
scope: serverSDK.scope,
|
scope: serverSDK().scope,
|
||||||
directory,
|
directory,
|
||||||
sessionID,
|
sessionID,
|
||||||
task: (rev) =>
|
task: (rev) =>
|
||||||
retry(() => serverSDK.client.session.messages({ directory, sessionID, limit: prefetchChunk }))
|
retry(() => serverSDK().client.session.messages({ directory, sessionID, limit: prefetchChunk }))
|
||||||
.then((messages) => {
|
.then((messages) => {
|
||||||
if (prefetchToken.value !== token) return
|
if (prefetchToken.value !== token) return
|
||||||
if (!isSessionPrefetchCurrent(serverSDK.scope, directory, sessionID, rev)) return
|
if (!isSessionPrefetchCurrent(serverSDK().scope, directory, sessionID, rev)) return
|
||||||
|
|
||||||
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
|
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
|
||||||
const next = items.map((x) => x.info).filter((m): m is Message => !!m?.id)
|
const next = items.map((x) => x.info).filter((m): m is Message => !!m?.id)
|
||||||
@@ -755,9 +755,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stale.length > 0) {
|
if (stale.length > 0) {
|
||||||
clearSessionPrefetch(serverSDK.scope, directory, stale)
|
clearSessionPrefetch(serverSDK().scope, directory, stale)
|
||||||
for (const id of stale) {
|
for (const id of stale) {
|
||||||
serverSync.todo.set(id, undefined)
|
serverSync().todo.set(id, undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,7 +767,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
sorted,
|
sorted,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isSessionPrefetchCurrent(serverSDK.scope, directory, sessionID, rev)) return
|
if (!isSessionPrefetchCurrent(serverSDK().scope, directory, sessionID, rev)) return
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
if (stale.length > 0) {
|
if (stale.length > 0) {
|
||||||
@@ -779,7 +779,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setStore("message", sessionID, reconcile(merged, { key: "id" }))
|
setStore("message", sessionID, reconcile(merged, { key: "id" }))
|
||||||
setSessionPrefetch({ scope: serverSDK.scope, directory, sessionID, ...meta })
|
setSessionPrefetch({ scope: serverSDK().scope, directory, sessionID, ...meta })
|
||||||
|
|
||||||
for (const message of items) {
|
for (const message of items) {
|
||||||
const currentParts = store.part[message.info.id] ?? []
|
const currentParts = store.part[message.info.id] ?? []
|
||||||
@@ -822,9 +822,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
const directory = session.directory
|
const directory = session.directory
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
|
|
||||||
const [store] = serverSync.child(directory, { bootstrap: false })
|
const [store] = serverSync().child(directory, { bootstrap: false })
|
||||||
const cached = untrack(() => {
|
const cached = untrack(() => {
|
||||||
const info = getSessionPrefetch(serverSDK.scope, directory, session.id)
|
const info = getSessionPrefetch(serverSDK().scope, directory, session.id)
|
||||||
return shouldSkipSessionPrefetch({
|
return shouldSkipSessionPrefetch({
|
||||||
message: store.message[session.id] !== undefined,
|
message: store.message[session.id] !== undefined,
|
||||||
info,
|
info,
|
||||||
@@ -927,7 +927,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
||||||
// warm up child store to prevent flicker
|
// warm up child store to prevent flicker
|
||||||
serverSync.child(target.worktree)
|
serverSync().child(target.worktree)
|
||||||
void openProject(target.worktree)
|
void openProject(target.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,7 +936,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const target = projects[index]
|
const target = projects[index]
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
||||||
serverSync.child(target.worktree)
|
serverSync().child(target.worktree)
|
||||||
void openProject(target.worktree)
|
void openProject(target.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,12 +965,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function archiveSession(session: Session) {
|
async function archiveSession(session: Session) {
|
||||||
const [store, setStore] = serverSync.child(session.directory)
|
const [store, setStore] = serverSync().child(session.directory)
|
||||||
const sessions = store.session ?? []
|
const sessions = store.session ?? []
|
||||||
const index = sessions.findIndex((s) => s.id === session.id)
|
const index = sessions.findIndex((s) => s.id === session.id)
|
||||||
const nextSession = sessions[index + 1] ?? sessions[index - 1]
|
const nextSession = sessions[index + 1] ?? sessions[index - 1]
|
||||||
|
|
||||||
await serverSDK.client.session.update({
|
await serverSDK().client.session.update({
|
||||||
directory: session.directory,
|
directory: session.directory,
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
time: { archived: Date.now() },
|
time: { archived: Date.now() },
|
||||||
@@ -1241,11 +1241,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
)
|
)
|
||||||
if (known) return known[0]
|
if (known) return known[0]
|
||||||
|
|
||||||
const [child] = serverSync.child(directory, { bootstrap: false })
|
const [child] = serverSync().child(directory, { bootstrap: false })
|
||||||
const id = child.project
|
const id = child.project
|
||||||
if (!id) return directory
|
if (!id) return directory
|
||||||
|
|
||||||
const meta = serverSync.data.project.find((item) => item.id === id)
|
const meta = serverSync().data.project.find((item) => item.id === id)
|
||||||
return meta?.worktree ?? directory
|
return meta?.worktree ?? directory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1293,7 +1293,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
const refreshDirs = async (target?: string) => {
|
const refreshDirs = async (target?: string) => {
|
||||||
if (!target || target === root || canOpen(target)) return canOpen(target)
|
if (!target || target === root || canOpen(target)) return canOpen(target)
|
||||||
const listed = await serverSDK.client.worktree
|
const listed = await serverSDK().client.worktree
|
||||||
.list({ directory: root })
|
.list({ directory: root })
|
||||||
.then((x) => x.data ?? [])
|
.then((x) => x.data ?? [])
|
||||||
.catch(() => [] as string[])
|
.catch(() => [] as string[])
|
||||||
@@ -1302,13 +1302,13 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
const openSession = async (target: { directory: string; id: string }) => {
|
const openSession = async (target: { directory: string; id: string }) => {
|
||||||
if (!canOpen(target.directory)) return false
|
if (!canOpen(target.directory)) return false
|
||||||
const [data] = serverSync.child(target.directory, { bootstrap: false })
|
const [data] = serverSync().child(target.directory, { bootstrap: false })
|
||||||
if (data.session.some((item) => item.id === target.id)) {
|
if (data.session.some((item) => item.id === target.id)) {
|
||||||
setStore("lastProjectSession", root, { directory: target.directory, id: target.id, at: Date.now() })
|
setStore("lastProjectSession", root, { directory: target.directory, id: target.id, at: Date.now() })
|
||||||
navigateWithSidebarReset(`/${base64Encode(target.directory)}/session/${target.id}`)
|
navigateWithSidebarReset(`/${base64Encode(target.directory)}/session/${target.id}`)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const resolved = await serverSDK.client.session
|
const resolved = await serverSDK().client.session
|
||||||
.get({ sessionID: target.id })
|
.get({ sessionID: target.id })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
@@ -1328,7 +1328,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const latest = latestRootSession(
|
const latest = latestRootSession(
|
||||||
dirs.map((item) => serverSync.child(item, { bootstrap: false })[0]),
|
dirs.map((item) => serverSync().child(item, { bootstrap: false })[0]),
|
||||||
Date.now(),
|
Date.now(),
|
||||||
)
|
)
|
||||||
if (latest && (await openSession(latest))) {
|
if (latest && (await openSession(latest))) {
|
||||||
@@ -1339,7 +1339,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
dirs.map(async (item) => ({
|
dirs.map(async (item) => ({
|
||||||
path: { directory: item },
|
path: { directory: item },
|
||||||
session: await serverSDK.client.session
|
session: await serverSDK().client.session
|
||||||
.list({ directory: item })
|
.list({ directory: item })
|
||||||
.then((x) => x.data ?? [])
|
.then((x) => x.data ?? [])
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
@@ -1402,11 +1402,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
const name = next === getFilename(project.worktree) ? "" : next
|
const name = next === getFilename(project.worktree) ? "" : next
|
||||||
|
|
||||||
if (project.id && project.id !== "global") {
|
if (project.id && project.id !== "global") {
|
||||||
await serverSDK.client.project.update({ projectID: project.id, directory: project.worktree, name })
|
await serverSDK().client.project.update({ projectID: project.id, directory: project.worktree, name })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serverSync.project.meta(project.worktree, { name })
|
serverSync().project.meta(project.worktree, { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
const renameWorkspace = (directory: string, next: string, projectId?: string, branch?: string) => {
|
const renameWorkspace = (directory: string, next: string, projectId?: string, branch?: string) => {
|
||||||
@@ -1495,7 +1495,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
setBusy(directory, true)
|
setBusy(directory, true)
|
||||||
|
|
||||||
const result = await serverSDK.client.worktree
|
const result = await serverSDK().client.worktree
|
||||||
.remove({ directory: root, worktreeRemoveInput: { directory } })
|
.remove({ directory: root, worktreeRemoveInput: { directory } })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -1514,7 +1514,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
clearLastProjectSession(root)
|
clearLastProjectSession(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverSync.set(
|
serverSync().set(
|
||||||
"project",
|
"project",
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
const project = draft.find((item) => item.worktree === root)
|
const project = draft.find((item) => item.worktree === root)
|
||||||
@@ -1553,7 +1553,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
const dismiss = () => toaster.dismiss(progress)
|
const dismiss = () => toaster.dismiss(progress)
|
||||||
|
|
||||||
const sessions: Session[] = await serverSDK.client.session
|
const sessions: Session[] = await serverSDK().client.session
|
||||||
.list({ directory })
|
.list({ directory })
|
||||||
.then((x) => x.data ?? [])
|
.then((x) => x.data ?? [])
|
||||||
.catch(() => [])
|
.catch(() => [])
|
||||||
@@ -1562,11 +1562,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
directory,
|
directory,
|
||||||
sessions.map((s) => s.id),
|
sessions.map((s) => s.id),
|
||||||
platform,
|
platform,
|
||||||
serverSDK.scope,
|
serverSDK().scope,
|
||||||
)
|
)
|
||||||
await serverSDK.client.instance.dispose({ directory }).catch(() => undefined)
|
await serverSDK().client.instance.dispose({ directory }).catch(() => undefined)
|
||||||
|
|
||||||
const result = await serverSDK.client.worktree
|
const result = await serverSDK().client.worktree
|
||||||
.reset({ directory: root, worktreeResetInput: { directory } })
|
.reset({ directory: root, worktreeResetInput: { directory } })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -1588,7 +1588,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
sessions
|
sessions
|
||||||
.filter((session) => session.time.archived === undefined)
|
.filter((session) => session.time.archived === undefined)
|
||||||
.map((session) =>
|
.map((session) =>
|
||||||
serverSDK.client.session
|
serverSDK().client.session
|
||||||
.update({
|
.update({
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
directory: session.directory,
|
directory: session.directory,
|
||||||
@@ -1629,7 +1629,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
serverSDK.client.vcs
|
serverSDK().client.vcs
|
||||||
.status({ directory: props.directory })
|
.status({ directory: props.directory })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const files = x.data ?? []
|
const files = x.data ?? []
|
||||||
@@ -1688,7 +1688,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
const sessions = await serverSDK.client.session
|
const sessions = await serverSDK().client.session
|
||||||
.list({ directory: props.directory })
|
.list({ directory: props.directory })
|
||||||
.then((x) => x.data ?? [])
|
.then((x) => x.data ?? [])
|
||||||
.catch(() => [])
|
.catch(() => [])
|
||||||
@@ -1697,7 +1697,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
serverSDK.client.vcs
|
serverSDK().client.vcs
|
||||||
.status({ directory: props.directory })
|
.status({ directory: props.directory })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const files = x.data ?? []
|
const files = x.data ?? []
|
||||||
@@ -1831,7 +1831,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const next = new Set(dirs)
|
const next = new Set(dirs)
|
||||||
for (const directory of next) {
|
for (const directory of next) {
|
||||||
if (loadedSessionDirs.has(directory)) continue
|
if (loadedSessionDirs.has(directory)) continue
|
||||||
void serverSync.project.loadSessions(directory)
|
void serverSync().project.loadSessions(directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedSessionDirs.clear()
|
loadedSessionDirs.clear()
|
||||||
@@ -1876,7 +1876,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
directory && pathKey(directory) !== pathKey(local) && !dirs.some((item) => pathKey(item) === pathKey(directory))
|
directory && pathKey(directory) !== pathKey(local) && !dirs.some((item) => pathKey(item) === pathKey(directory))
|
||||||
? directory
|
? directory
|
||||||
: undefined
|
: undefined
|
||||||
const pending = extra ? WorktreeState.get(serverSDK.scope, extra)?.status === "pending" : false
|
const pending = extra ? WorktreeState.get(serverSDK().scope, extra)?.status === "pending" : false
|
||||||
|
|
||||||
const ordered = effectiveWorkspaceOrder(local, dirs, store.workspaceOrder[project.worktree])
|
const ordered = effectiveWorkspaceOrder(local, dirs, store.workspaceOrder[project.worktree])
|
||||||
if (pending && extra) return [local, extra, ...ordered.filter((item) => item !== local)]
|
if (pending && extra) return [local, extra, ...ordered.filter((item) => item !== local)]
|
||||||
@@ -1928,7 +1928,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const createWorkspace = async (project: LocalProject) => {
|
const createWorkspace = async (project: LocalProject) => {
|
||||||
clearSidebarHoverState()
|
clearSidebarHoverState()
|
||||||
const created = await serverSDK.client.worktree
|
const created = await serverSDK().client.worktree
|
||||||
.create({ directory: project.worktree })
|
.create({ directory: project.worktree })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -1948,7 +1948,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const root = pathKey(local)
|
const root = pathKey(local)
|
||||||
|
|
||||||
setBusy(created.directory, true)
|
setBusy(created.directory, true)
|
||||||
WorktreeState.pending(serverSDK.scope, created.directory)
|
WorktreeState.pending(serverSDK().scope, created.directory)
|
||||||
setStore("workspaceExpanded", key, true)
|
setStore("workspaceExpanded", key, true)
|
||||||
if (key !== created.directory) {
|
if (key !== created.directory) {
|
||||||
setStore("workspaceExpanded", created.directory, true)
|
setStore("workspaceExpanded", created.directory, true)
|
||||||
@@ -1962,7 +1962,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
return [created.directory, ...next]
|
return [created.directory, ...next]
|
||||||
})
|
})
|
||||||
|
|
||||||
serverSync.child(created.directory)
|
serverSync().child(created.directory)
|
||||||
navigateWithSidebarReset(`/${base64Encode(created.directory)}/session`)
|
navigateWithSidebarReset(`/${base64Encode(created.directory)}/session`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2067,7 +2067,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!item) return false
|
if (!item) return false
|
||||||
return item.vcs === "git" || layout.sidebar.workspaces(item.worktree)()
|
return item.vcs === "git" || layout.sidebar.workspaces(item.worktree)()
|
||||||
})
|
})
|
||||||
const homedir = createMemo(() => serverSync.data.path.home)
|
const homedir = createMemo(() => serverSync().data.path.home)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function useSessionTabAvatarState(
|
|||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
const hasPermissions = createMemo(() => {
|
const hasPermissions = createMemo(() => {
|
||||||
if (!active()) return false
|
if (!active()) return false
|
||||||
const [store] = globalSync.child(directory(), { bootstrap: false })
|
const [store] = globalSync().child(directory(), { bootstrap: false })
|
||||||
return !!sessionPermissionRequest(store.session, store.permission, sessionId(), (item) => {
|
return !!sessionPermissionRequest(store.session, store.permission, sessionId(), (item) => {
|
||||||
return !permission.autoResponds(item, directory())
|
return !permission.autoResponds(item, directory())
|
||||||
})
|
})
|
||||||
@@ -23,7 +23,7 @@ export function useSessionTabAvatarState(
|
|||||||
const loading = createMemo(() => {
|
const loading = createMemo(() => {
|
||||||
if (!active()) return false
|
if (!active()) return false
|
||||||
if (hasPermissions()) return false
|
if (hasPermissions()) return false
|
||||||
const [store] = globalSync.child(directory(), { bootstrap: false })
|
const [store] = globalSync().child(directory(), { bootstrap: false })
|
||||||
return store.session_working(sessionId())
|
return store.session_working(sessionId())
|
||||||
})
|
})
|
||||||
return { unread, loading }
|
return { unread, loading }
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const ProjectIcon = (props: {
|
|||||||
const hasError = createMemo(() => dirs().some((directory) => notification.project.unseenHasError(directory)))
|
const hasError = createMemo(() => dirs().some((directory) => notification.project.unseenHasError(directory)))
|
||||||
const hasPermissions = createMemo(() =>
|
const hasPermissions = createMemo(() =>
|
||||||
dirs().some((directory) => {
|
dirs().some((directory) => {
|
||||||
const [store] = serverSync.child(directory, { bootstrap: false })
|
const [store] = serverSync().child(directory, { bootstrap: false })
|
||||||
return hasProjectPermissions(store.permission, (item) => !permission.autoResponds(item, directory))
|
return hasProjectPermissions(store.permission, (item) => !permission.autoResponds(item, directory))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -149,7 +149,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||||||
const serverSync = useServerSync()
|
const serverSync = useServerSync()
|
||||||
const unseenCount = createMemo(() => notification.session.unseenCount(props.session.id))
|
const unseenCount = createMemo(() => notification.session.unseenCount(props.session.id))
|
||||||
const hasError = createMemo(() => notification.session.unseenHasError(props.session.id))
|
const hasError = createMemo(() => notification.session.unseenHasError(props.session.id))
|
||||||
const [sessionStore] = serverSync.child(props.session.directory)
|
const [sessionStore] = serverSync().child(props.session.directory)
|
||||||
const hasPermissions = createMemo(() => {
|
const hasPermissions = createMemo(() => {
|
||||||
return !!sessionPermissionRequest(sessionStore.session, sessionStore.permission, props.session.id, (item) => {
|
return !!sessionPermissionRequest(sessionStore.session, sessionStore.permission, props.session.id, (item) => {
|
||||||
return !permission.autoResponds(item, props.session.directory)
|
return !permission.autoResponds(item, props.session.directory)
|
||||||
|
|||||||
@@ -294,23 +294,23 @@ export const SortableProject = (props: {
|
|||||||
const hoverOpen = () => isHoverProject() && preview() && !selected() && !state.menu
|
const hoverOpen = () => isHoverProject() && preview() && !selected() && !state.menu
|
||||||
|
|
||||||
const label = (directory: string) => {
|
const label = (directory: string) => {
|
||||||
const [data] = serverSync.child(directory, { bootstrap: false })
|
const [data] = serverSync().child(directory, { bootstrap: false })
|
||||||
const kind =
|
const kind =
|
||||||
directory === props.project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
directory === props.project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
||||||
const name = props.ctx.workspaceLabel(directory, data.vcs?.branch, props.project.id)
|
const name = props.ctx.workspaceLabel(directory, data.vcs?.branch, props.project.id)
|
||||||
return `${kind} : ${name}`
|
return `${kind} : ${name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectStore = createMemo(() => serverSync.child(props.project.worktree, { bootstrap: false })[0])
|
const projectStore = createMemo(() => serverSync().child(props.project.worktree, { bootstrap: false })[0])
|
||||||
const isWorking = createMemo(() =>
|
const isWorking = createMemo(() =>
|
||||||
dirs().some((directory) => {
|
dirs().some((directory) => {
|
||||||
const [store] = serverSync.child(directory, { bootstrap: false })
|
const [store] = serverSync().child(directory, { bootstrap: false })
|
||||||
return Object.keys(store.session_status).some((id) => store.session_working(id))
|
return Object.keys(store.session_status).some((id) => store.session_working(id))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow()))
|
const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow()))
|
||||||
const workspaceSessions = (directory: string) => {
|
const workspaceSessions = (directory: string) => {
|
||||||
const [data] = serverSync.child(directory, { bootstrap: false })
|
const [data] = serverSync().child(directory, { bootstrap: false })
|
||||||
return sortedRootSessions(data, props.sortNow())
|
return sortedRootSessions(data, props.sortNow())
|
||||||
}
|
}
|
||||||
const tile = () => (
|
const tile = () => (
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const WorkspaceDragOverlay = (props: {
|
|||||||
const directory = props.activeWorkspace()
|
const directory = props.activeWorkspace()
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
|
|
||||||
const [workspaceStore] = serverSync.child(directory, { bootstrap: false })
|
const [workspaceStore] = serverSync().child(directory, { bootstrap: false })
|
||||||
const kind =
|
const kind =
|
||||||
directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
||||||
const name = props.workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
|
const name = props.workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
|
||||||
@@ -303,7 +303,7 @@ export const SortableWorkspace = (props: {
|
|||||||
const queryOptions = useQueryOptions()
|
const queryOptions = useQueryOptions()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const sortable = createSortable(props.directory)
|
const sortable = createSortable(props.directory)
|
||||||
const [workspaceStore, setWorkspaceStore] = serverSync.child(props.directory, { bootstrap: false })
|
const [workspaceStore, setWorkspaceStore] = serverSync().child(props.directory, { bootstrap: false })
|
||||||
const [menu, setMenu] = createStore({
|
const [menu, setMenu] = createStore({
|
||||||
open: false,
|
open: false,
|
||||||
pendingRename: false,
|
pendingRename: false,
|
||||||
@@ -321,14 +321,14 @@ export const SortableWorkspace = (props: {
|
|||||||
const boot = createMemo(() => open() || active())
|
const boot = createMemo(() => open() || active())
|
||||||
const count = createMemo(() => sessions()?.length ?? 0)
|
const count = createMemo(() => sessions()?.length ?? 0)
|
||||||
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
|
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
|
||||||
const fetching = useIsFetching(() => queryOptions.sessions(pathKey(props.directory)))
|
const fetching = useIsFetching(() => queryOptions().sessions(pathKey(props.directory)))
|
||||||
const busy = createMemo(() => props.ctx.isBusy(props.directory))
|
const busy = createMemo(() => props.ctx.isBusy(props.directory))
|
||||||
const loading = () => fetching() > 0 && count() === 0
|
const loading = () => fetching() > 0 && count() === 0
|
||||||
const touch = createMediaQuery("(hover: none)")
|
const touch = createMediaQuery("(hover: none)")
|
||||||
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
|
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
setWorkspaceStore("limit", (limit) => (limit ?? 0) + 5)
|
setWorkspaceStore("limit", (limit) => (limit ?? 0) + 5)
|
||||||
await serverSync.project.loadSessions(props.directory)
|
await serverSync().project.loadSessions(props.directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceEditActive = createMemo(() => props.ctx.editorOpen(`workspace:${props.directory}`))
|
const workspaceEditActive = createMemo(() => props.ctx.editorOpen(`workspace:${props.directory}`))
|
||||||
@@ -357,7 +357,7 @@ export const SortableWorkspace = (props: {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!boot()) return
|
if (!boot()) return
|
||||||
serverSync.child(props.directory, { bootstrap: true })
|
serverSync().child(props.directory, { bootstrap: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -450,18 +450,18 @@ export const LocalWorkspace = (props: {
|
|||||||
const queryOptions = useQueryOptions()
|
const queryOptions = useQueryOptions()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const workspace = createMemo(() => {
|
const workspace = createMemo(() => {
|
||||||
const [store, setStore] = serverSync.child(props.project.worktree)
|
const [store, setStore] = serverSync().child(props.project.worktree)
|
||||||
return { store, setStore }
|
return { store, setStore }
|
||||||
})
|
})
|
||||||
const slug = createMemo(() => base64Encode(props.project.worktree))
|
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||||
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
||||||
const count = createMemo(() => sessions()?.length ?? 0)
|
const count = createMemo(() => sessions()?.length ?? 0)
|
||||||
const fetching = useIsFetching(() => queryOptions.sessions(pathKey(props.project.worktree)))
|
const fetching = useIsFetching(() => queryOptions().sessions(pathKey(props.project.worktree)))
|
||||||
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
||||||
const loading = () => fetching() > 0 && count() === 0
|
const loading = () => fetching() > 0 && count() === 0
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
||||||
await serverSync.project.loadSessions(props.project.worktree)
|
await serverSync().project.loadSessions(props.project.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ export default function NewSessionPage() {
|
|||||||
|
|
||||||
const newSessionWorktree = createMemo(() => {
|
const newSessionWorktree = createMemo(() => {
|
||||||
if (store.worktree === "create") return "create"
|
if (store.worktree === "create") return "create"
|
||||||
const project = sync.project
|
const project = sync().project
|
||||||
if (project && sdk.directory !== project.worktree) return sdk.directory
|
if (project && sdk().directory !== project.worktree) return sdk().directory
|
||||||
return "main"
|
return "main"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -308,10 +308,10 @@ export default function Page() {
|
|||||||
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
|
||||||
const isChildSession = createMemo(() => !!info()?.parentID)
|
const isChildSession = createMemo(() => !!info()?.parentID)
|
||||||
const diffs = createMemo(() => (params.id ? list(sync.data.session_diff[params.id]) : []))
|
const diffs = createMemo(() => (params.id ? list(sync().data.session_diff[params.id]) : []))
|
||||||
const canReview = createMemo(() => !!sync.project)
|
const canReview = createMemo(() => !!sync().project)
|
||||||
const reviewTab = createMemo(() => isDesktop())
|
const reviewTab = createMemo(() => isDesktop())
|
||||||
const tabState = createSessionTabs({
|
const tabState = createSessionTabs({
|
||||||
tabs,
|
tabs,
|
||||||
@@ -323,21 +323,21 @@ export default function Page() {
|
|||||||
const activeTab = tabState.activeTab
|
const activeTab = tabState.activeTab
|
||||||
const activeFileTab = tabState.activeFileTab
|
const activeFileTab = tabState.activeFileTab
|
||||||
const revertMessageID = createMemo(() => info()?.revert?.messageID)
|
const revertMessageID = createMemo(() => info()?.revert?.messageID)
|
||||||
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
|
||||||
const messagesReady = createMemo(() => {
|
const messagesReady = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return true
|
if (!id) return true
|
||||||
return sync.data.message[id] !== undefined
|
return sync().data.message[id] !== undefined
|
||||||
})
|
})
|
||||||
const historyMore = createMemo(() => {
|
const historyMore = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return false
|
if (!id) return false
|
||||||
return sync.session.history.more(id)
|
return sync().session.history.more(id)
|
||||||
})
|
})
|
||||||
const historyLoading = createMemo(() => {
|
const historyLoading = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return false
|
if (!id) return false
|
||||||
return sync.session.history.loading(id)
|
return sync().session.history.loading(id)
|
||||||
})
|
})
|
||||||
const userMessages = createMemo(
|
const userMessages = createMemo(
|
||||||
() => messages().filter((m) => m.role === "user") as UserMessage[],
|
() => messages().filter((m) => m.role === "user") as UserMessage[],
|
||||||
@@ -397,7 +397,7 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const [followup, setFollowup] = persisted(
|
const [followup, setFollowup] = persisted(
|
||||||
Persist.serverWorkspace(serverSDK.scope, sdk.directory, "followup", ["followup.v1"]),
|
Persist.serverWorkspace(serverSDK().scope, sdk().directory, "followup", ["followup.v1"]),
|
||||||
createStore<{
|
createStore<{
|
||||||
items: Record<string, FollowupItem[] | undefined>
|
items: Record<string, FollowupItem[] | undefined>
|
||||||
failed: Record<string, string | undefined>
|
failed: Record<string, string | undefined>
|
||||||
@@ -444,16 +444,16 @@ export default function Page() {
|
|||||||
}, desktopReviewOpen())
|
}, desktopReviewOpen())
|
||||||
|
|
||||||
const turnDiffs = createMemo(() => list(lastUserMessage()?.summary?.diffs))
|
const turnDiffs = createMemo(() => list(lastUserMessage()?.summary?.diffs))
|
||||||
const nogit = createMemo(() => !!sync.project && sync.project.vcs !== "git")
|
const nogit = createMemo(() => {
|
||||||
|
const project = sync().project
|
||||||
|
return !!project && project.vcs !== "git"
|
||||||
|
})
|
||||||
const changesOptions = createMemo<ChangeMode[]>(() => {
|
const changesOptions = createMemo<ChangeMode[]>(() => {
|
||||||
const list: ChangeMode[] = []
|
const list: ChangeMode[] = []
|
||||||
if (sync.project?.vcs === "git") list.push("git")
|
const project = sync().project
|
||||||
if (
|
const vcs = sync().data.vcs
|
||||||
sync.project?.vcs === "git" &&
|
if (project?.vcs === "git") list.push("git")
|
||||||
sync.data.vcs?.branch &&
|
if (project?.vcs === "git" && vcs?.branch && vcs?.default_branch && vcs.branch !== vcs.default_branch) {
|
||||||
sync.data.vcs?.default_branch &&
|
|
||||||
sync.data.vcs.branch !== sync.data.vcs.default_branch
|
|
||||||
) {
|
|
||||||
list.push("branch")
|
list.push("branch")
|
||||||
}
|
}
|
||||||
list.push("turn")
|
list.push("turn")
|
||||||
@@ -469,18 +469,18 @@ export default function Page() {
|
|||||||
if (store.changes === "git" || store.changes === "branch") return store.changes
|
if (store.changes === "git" || store.changes === "branch") return store.changes
|
||||||
})
|
})
|
||||||
const vcsKey = createMemo(
|
const vcsKey = createMemo(
|
||||||
() => ["session-vcs", sdk.directory, sync.data.vcs?.branch ?? "", sync.data.vcs?.default_branch ?? ""] as const,
|
() => ["session-vcs", sdk().directory, sync().data.vcs?.branch ?? "", sync().data.vcs?.default_branch ?? ""] as const,
|
||||||
)
|
)
|
||||||
const vcsQuery = createQuery(() => {
|
const vcsQuery = createQuery(() => {
|
||||||
const mode = vcsMode()
|
const mode = vcsMode()
|
||||||
const enabled = wantsReview() && sync.project?.vcs === "git"
|
const enabled = wantsReview() && sync().project?.vcs === "git"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: [...vcsKey(), mode] as const,
|
queryKey: [...vcsKey(), mode] as const,
|
||||||
enabled,
|
enabled,
|
||||||
queryFn: mode
|
queryFn: mode
|
||||||
? () =>
|
? () =>
|
||||||
sdk.client.vcs
|
sdk().client.vcs
|
||||||
.diff({ mode })
|
.diff({ mode })
|
||||||
.then((result) => list(result.data))
|
.then((result) => list(result.data))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -506,8 +506,8 @@ export default function Page() {
|
|||||||
|
|
||||||
const newSessionWorktree = createMemo(() => {
|
const newSessionWorktree = createMemo(() => {
|
||||||
if (store.newSessionWorktree === "create") return "create"
|
if (store.newSessionWorktree === "create") return "create"
|
||||||
const project = sync.project
|
const project = sync().project
|
||||||
if (project && sdk.directory !== project.worktree) return sdk.directory
|
if (project && sdk().directory !== project.worktree) return sdk().directory
|
||||||
return "main"
|
return "main"
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -569,11 +569,11 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function upsert(next: Project) {
|
function upsert(next: Project) {
|
||||||
const list = serverSync.data.project
|
const list = serverSync().data.project
|
||||||
sync.set("project", next.id)
|
sync().set("project", next.id)
|
||||||
const idx = list.findIndex((item) => item.id === next.id)
|
const idx = list.findIndex((item) => item.id === next.id)
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
serverSync.set(
|
serverSync().set(
|
||||||
"project",
|
"project",
|
||||||
list.map((item, i) => (i === idx ? { ...item, ...next } : item)),
|
list.map((item, i) => (i === idx ? { ...item, ...next } : item)),
|
||||||
)
|
)
|
||||||
@@ -581,14 +581,14 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
const at = list.findIndex((item) => item.id > next.id)
|
const at = list.findIndex((item) => item.id > next.id)
|
||||||
if (at >= 0) {
|
if (at >= 0) {
|
||||||
serverSync.set("project", [...list.slice(0, at), next, ...list.slice(at)])
|
serverSync().set("project", [...list.slice(0, at), next, ...list.slice(at)])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serverSync.set("project", [...list, next])
|
serverSync().set("project", [...list, next])
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitMutation = useMutation(() => ({
|
const gitMutation = useMutation(() => ({
|
||||||
mutationFn: () => sdk.client.project.initGit(),
|
mutationFn: () => sdk().client.project.initGit(),
|
||||||
onSuccess: (x) => {
|
onSuccess: (x) => {
|
||||||
if (!x.data) return
|
if (!x.data) return
|
||||||
upsert(x.data)
|
upsert(x.data)
|
||||||
@@ -632,7 +632,7 @@ export default function Page() {
|
|||||||
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
|
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
|
||||||
|
|
||||||
const [sessionSync] = createResource(
|
const [sessionSync] = createResource(
|
||||||
() => [sdk.directory, params.id] as const,
|
() => [sdk().directory, params.id] as const,
|
||||||
([directory, id]) => {
|
([directory, id]) => {
|
||||||
if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame)
|
if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame)
|
||||||
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
||||||
@@ -640,11 +640,11 @@ export default function Page() {
|
|||||||
refreshTimer = undefined
|
refreshTimer = undefined
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
const cached = untrack(() => sync.data.message[id] !== undefined)
|
const cached = untrack(() => sync().data.message[id] !== undefined)
|
||||||
const stale = !cached
|
const stale = !cached
|
||||||
? false
|
? false
|
||||||
: (() => {
|
: (() => {
|
||||||
const info = getSessionPrefetch(serverSDK.scope, directory, id)
|
const info = getSessionPrefetch(serverSDK().scope, directory, id)
|
||||||
if (!info) return true
|
if (!info) return true
|
||||||
return Date.now() - info.at > SESSION_PREFETCH_TTL
|
return Date.now() - info.at > SESSION_PREFETCH_TTL
|
||||||
})()
|
})()
|
||||||
@@ -655,12 +655,12 @@ export default function Page() {
|
|||||||
refreshTimer = undefined
|
refreshTimer = undefined
|
||||||
if (params.id !== id) return
|
if (params.id !== id) return
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
if (stale) void sync.session.sync(id, { force: true })
|
if (stale) void sync().session.sync(id, { force: true })
|
||||||
})
|
})
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
return sync.session.sync(id)
|
return sync().session.sync(id)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -669,9 +669,9 @@ export default function Page() {
|
|||||||
() => {
|
() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
return [
|
return [
|
||||||
sdk.directory,
|
sdk().directory,
|
||||||
id,
|
id,
|
||||||
id ? (sync.data.session_status[id]?.type ?? "idle") : "idle",
|
id ? (sync().data.session_status[id]?.type ?? "idle") : "idle",
|
||||||
id ? composer.blocked() : false,
|
id ? composer.blocked() : false,
|
||||||
] as const
|
] as const
|
||||||
},
|
},
|
||||||
@@ -682,15 +682,15 @@ export default function Page() {
|
|||||||
todoTimer = undefined
|
todoTimer = undefined
|
||||||
if (!id) return
|
if (!id) return
|
||||||
if (status === "idle" && !blocked) return
|
if (status === "idle" && !blocked) return
|
||||||
const cached = untrack(() => sync.data.todo[id] !== undefined || serverSync.data.session_todo[id] !== undefined)
|
const cached = untrack(() => sync().data.todo[id] !== undefined || serverSync().data.session_todo[id] !== undefined)
|
||||||
|
|
||||||
todoFrame = requestAnimationFrame(() => {
|
todoFrame = requestAnimationFrame(() => {
|
||||||
todoFrame = undefined
|
todoFrame = undefined
|
||||||
todoTimer = window.setTimeout(() => {
|
todoTimer = window.setTimeout(() => {
|
||||||
todoTimer = undefined
|
todoTimer = undefined
|
||||||
if (sdk.directory !== dir || params.id !== id) return
|
if (sdk().directory !== dir || params.id !== id) return
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
void sync.session.todo(id, cached ? { force: true } : undefined)
|
void sync().session.todo(id, cached ? { force: true } : undefined)
|
||||||
})
|
})
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
@@ -723,7 +723,7 @@ export default function Page() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const stopVcs = sdk.event.listen((evt) => {
|
const stopVcs = sdk().event.listen((evt) => {
|
||||||
if (evt.details.type !== "file.watcher.updated") return
|
if (evt.details.type !== "file.watcher.updated") return
|
||||||
const props =
|
const props =
|
||||||
typeof evt.details.properties === "object" && evt.details.properties
|
typeof evt.details.properties === "object" && evt.details.properties
|
||||||
@@ -866,7 +866,7 @@ export default function Page() {
|
|||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => sync.data.session_status[params.id ?? ""]?.type,
|
() => sync().data.session_status[params.id ?? ""]?.type,
|
||||||
(next, prev) => {
|
(next, prev) => {
|
||||||
if (next !== "idle" || prev === undefined || prev === "idle") return
|
if (next !== "idle" || prev === undefined || prev === "idle") return
|
||||||
refreshVcs()
|
refreshVcs()
|
||||||
@@ -1137,10 +1137,10 @@ export default function Page() {
|
|||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
if (!wantsReview()) return
|
if (!wantsReview()) return
|
||||||
if (sync.data.session_diff[id] !== undefined) return
|
if (sync().data.session_diff[id] !== undefined) return
|
||||||
if (sync.status === "loading") return
|
if (sync().status === "loading") return
|
||||||
|
|
||||||
void sync.session.diff(id)
|
void sync().session.diff(id)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -1155,14 +1155,14 @@ export default function Page() {
|
|||||||
|
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
if (!untrack(() => sync.data.session_diff[id] !== undefined)) return
|
if (!untrack(() => sync().data.session_diff[id] !== undefined)) return
|
||||||
|
|
||||||
diffFrame = requestAnimationFrame(() => {
|
diffFrame = requestAnimationFrame(() => {
|
||||||
diffFrame = undefined
|
diffFrame = undefined
|
||||||
diffTimer = window.setTimeout(() => {
|
diffTimer = window.setTimeout(() => {
|
||||||
diffTimer = undefined
|
diffTimer = undefined
|
||||||
if (sessionKey() !== key) return
|
if (sessionKey() !== key) return
|
||||||
void sync.session.diff(id, { force: true })
|
void sync().session.diff(id, { force: true })
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -1172,10 +1172,10 @@ export default function Page() {
|
|||||||
|
|
||||||
let treeDir: string | undefined
|
let treeDir: string | undefined
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const dir = sdk.directory
|
const dir = sdk().directory
|
||||||
if (!isDesktop()) return
|
if (!isDesktop()) return
|
||||||
if (!layout.fileTree.opened()) return
|
if (!layout.fileTree.opened()) return
|
||||||
if (sync.status === "loading") return
|
if (sync().status === "loading") return
|
||||||
|
|
||||||
fileTreeTab()
|
fileTreeTab()
|
||||||
const refresh = treeDir !== dir
|
const refresh = treeDir !== dir
|
||||||
@@ -1185,7 +1185,7 @@ export default function Page() {
|
|||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => sdk.directory,
|
() => sdk().directory,
|
||||||
() => {
|
() => {
|
||||||
const tab = activeFileTab()
|
const tab = activeFileTab()
|
||||||
if (!tab) return
|
if (!tab) return
|
||||||
@@ -1285,7 +1285,7 @@ export default function Page() {
|
|||||||
visibleUserMessages,
|
visibleUserMessages,
|
||||||
historyMore,
|
historyMore,
|
||||||
historyLoading,
|
historyLoading,
|
||||||
loadMore: (sessionID) => sync.session.history.loadMore(sessionID),
|
loadMore: (sessionID) => sync().session.history.loadMore(sessionID),
|
||||||
userScrolled: autoScroll.userScrolled,
|
userScrolled: autoScroll.userScrolled,
|
||||||
scroller: () => scroller,
|
scroller: () => scroller,
|
||||||
})
|
})
|
||||||
@@ -1329,8 +1329,8 @@ export default function Page() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const draft = (id: string) =>
|
const draft = (id: string) =>
|
||||||
extractPromptFromParts(sync.data.part[id] ?? [], {
|
extractPromptFromParts(sync().data.part[id] ?? [], {
|
||||||
directory: sdk.directory,
|
directory: sdk().directory,
|
||||||
attachmentName: language.t("common.attachment"),
|
attachmentName: language.t("common.attachment"),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1353,7 +1353,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const merge = (next: NonNullable<ReturnType<typeof info>>) =>
|
const merge = (next: NonNullable<ReturnType<typeof info>>) =>
|
||||||
sync.set("session", (list) => {
|
sync().set("session", (list) => {
|
||||||
const idx = list.findIndex((item) => item.id === next.id)
|
const idx = list.findIndex((item) => item.id === next.id)
|
||||||
if (idx < 0) return list
|
if (idx < 0) return list
|
||||||
const out = list.slice()
|
const out = list.slice()
|
||||||
@@ -1362,7 +1362,7 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const roll = (sessionID: string, next: NonNullable<ReturnType<typeof info>>["revert"]) =>
|
const roll = (sessionID: string, next: NonNullable<ReturnType<typeof info>>["revert"]) =>
|
||||||
sync.set("session", (list) => {
|
sync().set("session", (list) => {
|
||||||
const idx = list.findIndex((item) => item.id === sessionID)
|
const idx = list.findIndex((item) => item.id === sessionID)
|
||||||
if (idx < 0) return list
|
if (idx < 0) return list
|
||||||
const out = list.slice()
|
const out = list.slice()
|
||||||
@@ -1370,7 +1370,7 @@ export default function Page() {
|
|||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
|
|
||||||
const busy = (sessionID: string) => sync.data.session_working(sessionID)
|
const busy = (sessionID: string) => sync().data.session_working(sessionID)
|
||||||
|
|
||||||
const queuedFollowups = createMemo(() => {
|
const queuedFollowups = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
@@ -1393,11 +1393,11 @@ export default function Page() {
|
|||||||
setFollowup("failed", input.sessionID, undefined)
|
setFollowup("failed", input.sessionID, undefined)
|
||||||
|
|
||||||
const ok = await sendFollowupDraft({
|
const ok = await sendFollowupDraft({
|
||||||
client: sdk.client,
|
client: sdk().client,
|
||||||
sync,
|
sync: sync(),
|
||||||
serverSync,
|
serverSync: serverSync(),
|
||||||
draft: item,
|
draft: item,
|
||||||
optimisticBusy: item.sessionDirectory === sdk.directory,
|
optimisticBusy: item.sessionDirectory === sdk().directory,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
setFollowup("failed", input.sessionID, input.id)
|
setFollowup("failed", input.sessionID, input.id)
|
||||||
fail(err)
|
fail(err)
|
||||||
@@ -1455,7 +1455,7 @@ export default function Page() {
|
|||||||
const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item) })))
|
const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item) })))
|
||||||
|
|
||||||
const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => {
|
const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => {
|
||||||
if (sync.session.get(sessionID)?.parentID) return Promise.resolve()
|
if (sync().session.get(sessionID)?.parentID) return Promise.resolve()
|
||||||
const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id)
|
const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id)
|
||||||
if (!item) return Promise.resolve()
|
if (!item) return Promise.resolve()
|
||||||
if (followupBusy(sessionID)) return Promise.resolve()
|
if (followupBusy(sessionID)) return Promise.resolve()
|
||||||
@@ -1487,7 +1487,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const halt = (sessionID: string) =>
|
const halt = (sessionID: string) =>
|
||||||
busy(sessionID) ? sdk.client.session.abort({ sessionID }).catch(() => {}) : Promise.resolve()
|
busy(sessionID) ? sdk().client.session.abort({ sessionID }).catch(() => {}) : Promise.resolve()
|
||||||
|
|
||||||
const revertMutation = useMutation(() => ({
|
const revertMutation = useMutation(() => ({
|
||||||
mutationFn: async (input: { sessionID: string; messageID: string }) => {
|
mutationFn: async (input: { sessionID: string; messageID: string }) => {
|
||||||
@@ -1499,7 +1499,7 @@ export default function Page() {
|
|||||||
prompt.set(value)
|
prompt.set(value)
|
||||||
})
|
})
|
||||||
await halt(input.sessionID)
|
await halt(input.sessionID)
|
||||||
.then(() => sdk.client.session.revert(input))
|
.then(() => sdk().client.session.revert(input))
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.data) merge(result.data)
|
if (result.data) merge(result.data)
|
||||||
})
|
})
|
||||||
@@ -1532,9 +1532,9 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const task = !next
|
const task = !next
|
||||||
? halt(sessionID).then(() => sdk.client.session.unrevert({ sessionID }))
|
? halt(sessionID).then(() => sdk().client.session.unrevert({ sessionID }))
|
||||||
: halt(sessionID).then(() =>
|
: halt(sessionID).then(() =>
|
||||||
sdk.client.session.revert({
|
sdk().client.session.revert({
|
||||||
sessionID,
|
sessionID,
|
||||||
messageID: next.id,
|
messageID: next.id,
|
||||||
}),
|
}),
|
||||||
@@ -1622,7 +1622,7 @@ export default function Page() {
|
|||||||
visibleUserMessages,
|
visibleUserMessages,
|
||||||
historyMore,
|
historyMore,
|
||||||
historyLoading,
|
historyLoading,
|
||||||
loadMore: (sessionID) => sync.session.history.loadMore(sessionID),
|
loadMore: (sessionID) => sync().session.history.loadMore(sessionID),
|
||||||
currentMessageId: () => store.messageId,
|
currentMessageId: () => store.messageId,
|
||||||
pendingMessage: () => ui.pendingMessage,
|
pendingMessage: () => ui.pendingMessage,
|
||||||
setPendingMessage: (value) => setUi("pendingMessage", value),
|
setPendingMessage: (value) => setUi("pendingMessage", value),
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function SessionComposerRegion(props: {
|
|||||||
const view = layout.view(route.sessionKey)
|
const view = layout.view(route.sessionKey)
|
||||||
|
|
||||||
const handoffPrompt = createMemo(() => getSessionHandoff(route.sessionKey())?.prompt)
|
const handoffPrompt = createMemo(() => getSessionHandoff(route.sessionKey())?.prompt)
|
||||||
const info = createMemo(() => (route.params.id ? sync.session.get(route.params.id) : undefined))
|
const info = createMemo(() => (route.params.id ? sync().session.get(route.params.id) : undefined))
|
||||||
const parentID = createMemo(() => info()?.parentID)
|
const parentID = createMemo(() => info()?.parentID)
|
||||||
const child = createMemo(() => !!parentID())
|
const child = createMemo(() => !!parentID())
|
||||||
const showComposer = createMemo(() => !props.state.blocked() || child())
|
const showComposer = createMemo(() => !props.state.blocked() || child())
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
|
|
||||||
const questionRequest = createMemo((): QuestionRequest | undefined => {
|
const questionRequest = createMemo((): QuestionRequest | undefined => {
|
||||||
return sessionQuestionRequest(sync.data.session, sync.data.question, params.id)
|
return sessionQuestionRequest(sync().data.session, sync().data.question, params.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
const permissionRequest = createMemo((): PermissionRequest | undefined => {
|
const permissionRequest = createMemo((): PermissionRequest | undefined => {
|
||||||
return sessionPermissionRequest(sync.data.session, sync.data.permission, params.id, (item) => {
|
return sessionPermissionRequest(sync().data.session, sync().data.permission, params.id, (item) => {
|
||||||
return !permission.autoResponds(item, sdk.directory)
|
return !permission.autoResponds(item, sdk().directory)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -50,14 +50,14 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
const todos = createMemo((): Todo[] => {
|
const todos = createMemo((): Todo[] => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return []
|
if (!id) return []
|
||||||
return serverSync.data.session_todo[id] ?? []
|
return serverSync().data.session_todo[id] ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
const done = createMemo(
|
const done = createMemo(
|
||||||
() => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"),
|
() => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"),
|
||||||
)
|
)
|
||||||
|
|
||||||
const live = createMemo(() => sync.data.session_working(params.id ?? "") || blocked())
|
const live = createMemo(() => sync().data.session_working(params.id ?? "") || blocked())
|
||||||
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
responding: undefined as string | undefined,
|
responding: undefined as string | undefined,
|
||||||
@@ -78,7 +78,7 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
if (store.responding === perm.id) return
|
if (store.responding === perm.id) return
|
||||||
|
|
||||||
setStore("responding", perm.id)
|
setStore("responding", perm.id)
|
||||||
sdk.client.permission
|
sdk().client.permission
|
||||||
.respond({ sessionID: perm.sessionID, permissionID: perm.id, response })
|
.respond({ sessionID: perm.sessionID, permissionID: perm.id, response })
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
const description = err instanceof Error ? err.message : String(err)
|
const description = err instanceof Error ? err.message : String(err)
|
||||||
@@ -111,8 +111,8 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
const clear = () => {
|
const clear = () => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
serverSync.todo.set(id, [])
|
serverSync().todo.set(id, [])
|
||||||
sync.set("todo", id, [])
|
sync().set("todo", id, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const cacheKey = ScopedKey.from(serverSDK.scope, props.request.id)
|
const cacheKey = ScopedKey.from(serverSDK().scope, props.request.id)
|
||||||
|
|
||||||
const questions = createMemo(() => props.request.questions)
|
const questions = createMemo(() => props.request.questions)
|
||||||
const total = createMemo(() => questions().length)
|
const total = createMemo(() => questions().length)
|
||||||
@@ -209,7 +209,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const replyMutation = useMutation(() => ({
|
const replyMutation = useMutation(() => ({
|
||||||
mutationFn: (answers: QuestionAnswer[]) => sdk.client.question.reply({ requestID: props.request.id, answers }),
|
mutationFn: (answers: QuestionAnswer[]) => sdk().client.question.reply({ requestID: props.request.id, answers }),
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
props.onSubmit()
|
props.onSubmit()
|
||||||
},
|
},
|
||||||
@@ -221,7 +221,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const rejectMutation = useMutation(() => ({
|
const rejectMutation = useMutation(() => ({
|
||||||
mutationFn: () => sdk.client.question.reject({ requestID: props.request.id }),
|
mutationFn: () => sdk().client.question.reject({ requestID: props.request.id }),
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
props.onSubmit()
|
props.onSubmit()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ export function MessageTimeline(props: {
|
|||||||
const sessionMessages = createMemo(() => {
|
const sessionMessages = createMemo(() => {
|
||||||
const id = sessionID()
|
const id = sessionID()
|
||||||
if (!id) return emptyMessages
|
if (!id) return emptyMessages
|
||||||
return sync.data.message[id] ?? emptyMessages
|
return sync().data.message[id] ?? emptyMessages
|
||||||
})
|
})
|
||||||
const messageByID = createMemo(() => new Map(sessionMessages().map((message) => [message.id, message] as const)))
|
const messageByID = createMemo(() => new Map(sessionMessages().map((message) => [message.id, message] as const)))
|
||||||
const assistantMessagesByParent = createMemo(() => {
|
const assistantMessagesByParent = createMemo(() => {
|
||||||
@@ -324,10 +324,10 @@ export function MessageTimeline(props: {
|
|||||||
const sessionStatus = createMemo(() => {
|
const sessionStatus = createMemo(() => {
|
||||||
const id = sessionID()
|
const id = sessionID()
|
||||||
if (!id) return idle
|
if (!id) return idle
|
||||||
return sync.data.session_status[id] ?? idle
|
return sync().data.session_status[id] ?? idle
|
||||||
})
|
})
|
||||||
const working = createMemo(() => sessionStatus().type !== "idle")
|
const working = createMemo(() => sessionStatus().type !== "idle")
|
||||||
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
|
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync().data.agent))
|
||||||
|
|
||||||
const [timeoutDone, setTimeoutDone] = createSignal(true)
|
const [timeoutDone, setTimeoutDone] = createSignal(true)
|
||||||
|
|
||||||
@@ -366,25 +366,25 @@ export function MessageTimeline(props: {
|
|||||||
const info = createMemo(() => {
|
const info = createMemo(() => {
|
||||||
const id = sessionID()
|
const id = sessionID()
|
||||||
if (!id) return
|
if (!id) return
|
||||||
return sync.session.get(id)
|
return sync().session.get(id)
|
||||||
})
|
})
|
||||||
const titleValue = createMemo(() => info()?.title)
|
const titleValue = createMemo(() => info()?.title)
|
||||||
const titleLabel = createMemo(() => sessionTitle(titleValue()))
|
const titleLabel = createMemo(() => sessionTitle(titleValue()))
|
||||||
const shareUrl = createMemo(() => info()?.share?.url)
|
const shareUrl = createMemo(() => info()?.share?.url)
|
||||||
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
const shareEnabled = createMemo(() => sync().data.config.share !== "disabled")
|
||||||
const parentID = createMemo(() => info()?.parentID)
|
const parentID = createMemo(() => info()?.parentID)
|
||||||
const parent = createMemo(() => {
|
const parent = createMemo(() => {
|
||||||
const id = parentID()
|
const id = parentID()
|
||||||
if (!id) return
|
if (!id) return
|
||||||
return sync.session.get(id)
|
return sync().session.get(id)
|
||||||
})
|
})
|
||||||
const parentMessages = createMemo(() => {
|
const parentMessages = createMemo(() => {
|
||||||
const id = parentID()
|
const id = parentID()
|
||||||
if (!id) return emptyMessages
|
if (!id) return emptyMessages
|
||||||
return sync.data.message[id] ?? emptyMessages
|
return sync().data.message[id] ?? emptyMessages
|
||||||
})
|
})
|
||||||
const parentTitle = createMemo(() => sessionTitle(parent()?.title) ?? language.t("command.session.new"))
|
const parentTitle = createMemo(() => sessionTitle(parent()?.title) ?? language.t("command.session.new"))
|
||||||
const getMsgParts = (msgId: string) => sync.data.part[msgId] ?? emptyParts
|
const getMsgParts = (msgId: string) => sync().data.part[msgId] ?? emptyParts
|
||||||
const childTaskDescription = createMemo(() => {
|
const childTaskDescription = createMemo(() => {
|
||||||
const id = sessionID()
|
const id = sessionID()
|
||||||
if (!id) return
|
if (!id) return
|
||||||
@@ -730,14 +730,14 @@ export function MessageTimeline(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shareMutation = useMutation(() => ({
|
const shareMutation = useMutation(() => ({
|
||||||
mutationFn: (id: string) => serverSDK.client.session.share({ sessionID: id, directory: sdk.directory }),
|
mutationFn: (id: string) => serverSDK().client.session.share({ sessionID: id, directory: sdk().directory }),
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.error("Failed to share session", err)
|
console.error("Failed to share session", err)
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const unshareMutation = useMutation(() => ({
|
const unshareMutation = useMutation(() => ({
|
||||||
mutationFn: (id: string) => serverSDK.client.session.unshare({ sessionID: id, directory: sdk.directory }),
|
mutationFn: (id: string) => serverSDK().client.session.unshare({ sessionID: id, directory: sdk().directory }),
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.error("Failed to unshare session", err)
|
console.error("Failed to unshare session", err)
|
||||||
},
|
},
|
||||||
@@ -745,9 +745,9 @@ export function MessageTimeline(props: {
|
|||||||
|
|
||||||
const titleMutation = useMutation(() => ({
|
const titleMutation = useMutation(() => ({
|
||||||
mutationFn: (input: { id: string; title: string }) =>
|
mutationFn: (input: { id: string; title: string }) =>
|
||||||
sdk.client.session.update({ sessionID: input.id, title: input.title }),
|
sdk().client.session.update({ sessionID: input.id, title: input.title }),
|
||||||
onSuccess: (_, input) => {
|
onSuccess: (_, input) => {
|
||||||
sync.set(
|
sync().set(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
const index = draft.session.findIndex((s) => s.id === input.id)
|
const index = draft.session.findIndex((s) => s.id === input.id)
|
||||||
if (index !== -1) draft.session[index].title = input.title
|
if (index !== -1) draft.session[index].title = input.title
|
||||||
@@ -797,8 +797,8 @@ export function MessageTimeline(props: {
|
|||||||
() => [parentID(), childTaskDescription()] as const,
|
() => [parentID(), childTaskDescription()] as const,
|
||||||
([id, description]) => {
|
([id, description]) => {
|
||||||
if (!id || description) return
|
if (!id || description) return
|
||||||
if (sync.data.message[id] !== undefined) return
|
if (sync().data.message[id] !== undefined) return
|
||||||
void sync.session.sync(id)
|
void sync().session.sync(id)
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
@@ -846,25 +846,25 @@ export function MessageTimeline(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const archiveSession = async (sessionID: string) => {
|
const archiveSession = async (sessionID: string) => {
|
||||||
const session = sync.session.get(sessionID)
|
const session = sync().session.get(sessionID)
|
||||||
if (!session) return
|
if (!session) return
|
||||||
|
|
||||||
const sessions = sync.data.session ?? []
|
const sessions = sync().data.session ?? []
|
||||||
const index = sessions.findIndex((s) => s.id === sessionID)
|
const index = sessions.findIndex((s) => s.id === sessionID)
|
||||||
const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1])
|
const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1])
|
||||||
|
|
||||||
await sdk.client.session
|
await sdk().client.session
|
||||||
.update({ sessionID, time: { archived: Date.now() } })
|
.update({ sessionID, time: { archived: Date.now() } })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
sync.set(
|
sync().set(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
const index = draft.session.findIndex((s) => s.id === sessionID)
|
const index = draft.session.findIndex((s) => s.id === sessionID)
|
||||||
if (index !== -1) draft.session.splice(index, 1)
|
if (index !== -1) draft.session.splice(index, 1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
sync.session.evict(sessionID)
|
sync().session.evict(sessionID)
|
||||||
navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id)
|
navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id)
|
||||||
notifySessionTabsRemoved({ directory: sdk.directory, sessionIDs: [sessionID] })
|
notifySessionTabsRemoved({ directory: sdk().directory, sessionIDs: [sessionID] })
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToast({
|
showToast({
|
||||||
@@ -875,14 +875,14 @@ export function MessageTimeline(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteSession = async (sessionID: string) => {
|
const deleteSession = async (sessionID: string) => {
|
||||||
const session = sync.session.get(sessionID)
|
const session = sync().session.get(sessionID)
|
||||||
if (!session) return false
|
if (!session) return false
|
||||||
|
|
||||||
const sessions = (sync.data.session ?? []).filter((s) => !s.parentID && !s.time?.archived)
|
const sessions = (sync().data.session ?? []).filter((s) => !s.parentID && !s.time?.archived)
|
||||||
const index = sessions.findIndex((s) => s.id === sessionID)
|
const index = sessions.findIndex((s) => s.id === sessionID)
|
||||||
const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1])
|
const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1])
|
||||||
|
|
||||||
const result = await sdk.client.session
|
const result = await sdk().client.session
|
||||||
.delete({ sessionID })
|
.delete({ sessionID })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -897,7 +897,7 @@ export function MessageTimeline(props: {
|
|||||||
|
|
||||||
const removed = new Set<string>([sessionID])
|
const removed = new Set<string>([sessionID])
|
||||||
const byParent = new Map<string, string[]>()
|
const byParent = new Map<string, string[]>()
|
||||||
for (const item of sync.data.session) {
|
for (const item of sync().data.session) {
|
||||||
const parentID = item.parentID
|
const parentID = item.parentID
|
||||||
if (!parentID) continue
|
if (!parentID) continue
|
||||||
const existing = byParent.get(parentID)
|
const existing = byParent.get(parentID)
|
||||||
@@ -925,16 +925,16 @@ export function MessageTimeline(props: {
|
|||||||
|
|
||||||
navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id)
|
navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id)
|
||||||
|
|
||||||
sync.set(
|
sync().set(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
draft.session = draft.session.filter((s) => !removed.has(s.id))
|
draft.session = draft.session.filter((s) => !removed.has(s.id))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const id of removed) {
|
for (const id of removed) {
|
||||||
sync.session.evict(id)
|
sync().session.evict(id)
|
||||||
}
|
}
|
||||||
notifySessionTabsRemoved({ directory: sdk.directory, sessionIDs: [...removed] })
|
notifySessionTabsRemoved({ directory: sdk().directory, sessionIDs: [...removed] })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,7 +946,7 @@ export function MessageTimeline(props: {
|
|||||||
|
|
||||||
function DialogDeleteSession(props: { sessionID: string }) {
|
function DialogDeleteSession(props: { sessionID: string }) {
|
||||||
const name = createMemo(
|
const name = createMemo(
|
||||||
() => sessionTitle(sync.session.get(props.sessionID)?.title) ?? language.t("command.session.new"),
|
() => sessionTitle(sync().session.get(props.sessionID)?.title) ?? language.t("command.session.new"),
|
||||||
)
|
)
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
await deleteSession(props.sessionID)
|
await deleteSession(props.sessionID)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
|
|||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
|
|
||||||
const readFile = async (path: string) => {
|
const readFile = async (path: string) => {
|
||||||
return sdk.client.file
|
return sdk().client.file
|
||||||
.read({ path })
|
.read({ path })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function useUsageExceededDialogs() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
onCleanup(
|
onCleanup(
|
||||||
sdk.event.on("session.status", (evt) => {
|
sdk().event.on("session.status", (evt) => {
|
||||||
if (evt.properties.sessionID !== params.id) return
|
if (evt.properties.sessionID !== params.id) return
|
||||||
if (evt.properties.status.type !== "retry") return
|
if (evt.properties.status.type !== "retry") return
|
||||||
const { action } = evt.properties.status
|
const { action } = evt.properties.status
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
const info = () => {
|
const info = () => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
return sync.session.get(id)
|
return sync().session.get(id)
|
||||||
}
|
}
|
||||||
const hasReview = () => !!params.id
|
const hasReview = () => !!params.id
|
||||||
const normalizeTab = (tab: string) => {
|
const normalizeTab = (tab: string) => {
|
||||||
@@ -73,7 +73,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
const messages = () => {
|
const messages = () => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return []
|
if (!id) return []
|
||||||
return sync.data.message[id] ?? []
|
return sync().data.message[id] ?? []
|
||||||
}
|
}
|
||||||
const userMessages = () => messages().filter((m) => m.role === "user") as UserMessage[]
|
const userMessages = () => messages().filter((m) => m.role === "user") as UserMessage[]
|
||||||
const visibleUserMessages = () => {
|
const visibleUserMessages = () => {
|
||||||
@@ -122,8 +122,8 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
|
|
||||||
const isAutoAcceptActive = () => {
|
const isAutoAcceptActive = () => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (sessionID) return permission.isAutoAccepting(sessionID, sdk.directory)
|
if (sessionID) return permission.isAutoAccepting(sessionID, sdk().directory)
|
||||||
return permission.isAutoAcceptingDirectory(sdk.directory)
|
return permission.isAutoAcceptingDirectory(sdk().directory)
|
||||||
}
|
}
|
||||||
const write = async (value: string) => {
|
const write = async (value: string) => {
|
||||||
const body = typeof document === "undefined" ? undefined : document.body
|
const body = typeof document === "undefined" ? undefined : document.body
|
||||||
@@ -175,7 +175,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = await sdk.client.session
|
const url = await sdk().client.session
|
||||||
.share({ sessionID })
|
.share({ sessionID })
|
||||||
.then((res) => res.data?.share?.url)
|
.then((res) => res.data?.share?.url)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
@@ -195,7 +195,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
|
||||||
await sdk.client.session
|
await sdk().client.session
|
||||||
.unshare({ sessionID })
|
.unshare({ sessionID })
|
||||||
.then(() =>
|
.then(() =>
|
||||||
showToast({
|
showToast({
|
||||||
@@ -263,12 +263,12 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
|
|
||||||
const toggleAutoAccept = () => {
|
const toggleAutoAccept = () => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (sessionID) permission.toggleAutoAccept(sessionID, sdk.directory)
|
if (sessionID) permission.toggleAutoAccept(sessionID, sdk().directory)
|
||||||
else permission.toggleAutoAcceptDirectory(sdk.directory)
|
else permission.toggleAutoAcceptDirectory(sdk().directory)
|
||||||
|
|
||||||
const active = sessionID
|
const active = sessionID
|
||||||
? permission.isAutoAccepting(sessionID, sdk.directory)
|
? permission.isAutoAccepting(sessionID, sdk().directory)
|
||||||
: permission.isAutoAcceptingDirectory(sdk.directory)
|
: permission.isAutoAcceptingDirectory(sdk().directory)
|
||||||
showToast({
|
showToast({
|
||||||
title: active
|
title: active
|
||||||
? language.t("toast.permissions.autoaccept.on.title")
|
? language.t("toast.permissions.autoaccept.on.title")
|
||||||
@@ -283,18 +283,18 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
|
||||||
if (sync.data.session_working(params.id ?? "")) {
|
if (sync().data.session_working(params.id ?? "")) {
|
||||||
await sdk.client.session.abort({ sessionID }).catch(() => {})
|
await sdk().client.session.abort({ sessionID }).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const revert = info()?.revert?.messageID
|
const revert = info()?.revert?.messageID
|
||||||
const message = findLast(userMessages(), (x) => !revert || x.id < revert)
|
const message = findLast(userMessages(), (x) => !revert || x.id < revert)
|
||||||
if (!message) return
|
if (!message) return
|
||||||
|
|
||||||
await sdk.client.session.revert({ sessionID, messageID: message.id })
|
await sdk().client.session.revert({ sessionID, messageID: message.id })
|
||||||
const parts = sync.data.part[message.id]
|
const parts = sync().data.part[message.id]
|
||||||
if (parts) {
|
if (parts) {
|
||||||
const restored = extractPromptFromParts(parts, { directory: sdk.directory })
|
const restored = extractPromptFromParts(parts, { directory: sdk().directory })
|
||||||
prompt.set(restored)
|
prompt.set(restored)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,14 +311,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
|
|
||||||
const next = userMessages().find((x) => x.id > revertMessageID)
|
const next = userMessages().find((x) => x.id > revertMessageID)
|
||||||
if (!next) {
|
if (!next) {
|
||||||
await sdk.client.session.unrevert({ sessionID })
|
await sdk().client.session.unrevert({ sessionID })
|
||||||
prompt.reset()
|
prompt.reset()
|
||||||
const last = findLast(userMessages(), (x) => x.id >= revertMessageID)
|
const last = findLast(userMessages(), (x) => x.id >= revertMessageID)
|
||||||
setActiveMessage(last)
|
setActiveMessage(last)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await sdk.client.session.revert({ sessionID, messageID: next.id })
|
await sdk().client.session.revert({ sessionID, messageID: next.id })
|
||||||
const prev = findLast(userMessages(), (x) => x.id < next.id)
|
const prev = findLast(userMessages(), (x) => x.id < next.id)
|
||||||
setActiveMessage(prev)
|
setActiveMessage(prev)
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await sdk.client.session.summarize({
|
await sdk().client.session.summarize({
|
||||||
sessionID,
|
sessionID,
|
||||||
modelID: model.id,
|
modelID: model.id,
|
||||||
providerID: model.provider.id,
|
providerID: model.provider.id,
|
||||||
@@ -350,7 +350,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shareCmds = () => {
|
const shareCmds = () => {
|
||||||
if (sync.data.config.share === "disabled") return []
|
if (sync().data.config.share === "disabled") return []
|
||||||
return [
|
return [
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.share",
|
id: "session.share",
|
||||||
|
|||||||
Reference in New Issue
Block a user