Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bec0557994 | |||
| 733c45c7ec | |||
| 6636683323 | |||
| edd65fcbba | |||
| e00de49589 | |||
| 9d8a9c3c27 | |||
| 3af9f64265 | |||
| 5a23bdcb69 | |||
| 9e167fad57 | |||
| bc1513dc75 | |||
| e78726854a | |||
| 9a8831726c | |||
| 472d0f376e | |||
| c8fde60ad4 | |||
| a4b6047e64 | |||
| 6387f955cf | |||
| 5691f36e98 | |||
| 74e7a378bb | |||
| fced9c5a2f | |||
| 20445ca031 | |||
| 6fc9405618 | |||
| 5d20ebe81f | |||
| 4524101689 | |||
| 22c587c8c5 | |||
| 4edaa41bfc |
+10
-109
@@ -31,20 +31,19 @@ import { Dynamic } from "solid-js/web"
|
||||
import { CommandProvider, useCommand, type CommandOption } from "@/context/command"
|
||||
import { CommentsProvider } from "@/context/comments"
|
||||
import { FileProvider } from "@/context/file"
|
||||
import { ServerSDKProvider, useServerSDK } from "@/context/server-sdk"
|
||||
import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
|
||||
import { ServerSDKProvider } from "@/context/server-sdk"
|
||||
import { ServerSyncProvider } from "@/context/server-sync"
|
||||
import { GlobalProvider, useGlobal } from "@/context/global"
|
||||
import { HighlightsProvider } from "@/context/highlights"
|
||||
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
||||
import { LayoutProvider } from "@/context/layout"
|
||||
import { ModelsProvider } from "@/context/models"
|
||||
import { NotificationProvider, useNotification } from "@/context/notification"
|
||||
import { NotificationProvider } from "@/context/notification"
|
||||
import { PermissionProvider } from "@/context/permission"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { PromptProvider } from "@/context/prompt"
|
||||
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
|
||||
import { SettingsProvider, useSettings } from "@/context/settings"
|
||||
import { TerminalProvider } from "@/context/terminal"
|
||||
import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs"
|
||||
import { SDKProvider, useSDK } from "@/context/sdk"
|
||||
import { WslServersProvider } from "@/wsl/context"
|
||||
@@ -53,16 +52,9 @@ import LegacyLayout from "@/pages/layout"
|
||||
import NewLayout from "@/pages/layout-new"
|
||||
import { ErrorPage } from "./pages/error"
|
||||
import { useCheckServerHealth } from "./utils/server-health"
|
||||
import {
|
||||
legacySessionHref,
|
||||
legacySessionServer,
|
||||
requireServerKey,
|
||||
selectSessionLineage,
|
||||
sessionHref,
|
||||
} from "./utils/session-route"
|
||||
import { isSessionNotFoundError } from "./utils/server-errors"
|
||||
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
||||
|
||||
import Session from "@/pages/session"
|
||||
import { SessionPage, TargetSessionRoute as TargetSessionRouteContent } from "@/pages/session"
|
||||
import { NewHome, LegacyHome } from "@/pages/home"
|
||||
|
||||
const NewSession = lazy(() => import("@/pages/new-session"))
|
||||
@@ -96,11 +88,7 @@ const SessionRoute = () => {
|
||||
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
||||
})
|
||||
|
||||
return (
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
</SessionProviders>
|
||||
)
|
||||
return <SessionPage />
|
||||
}
|
||||
|
||||
const TargetSessionRoute = () => {
|
||||
@@ -115,76 +103,13 @@ const TargetSessionRoute = () => {
|
||||
<Show when={requireServerKey(params.serverKey)} keyed>
|
||||
<ServerSDKProvider server={conn}>
|
||||
<ServerSyncProvider server={conn}>
|
||||
<ResolvedTargetSessionRoute />
|
||||
<TargetSessionRouteContent />
|
||||
</ServerSyncProvider>
|
||||
</ServerSDKProvider>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function ResolvedTargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
const settings = useSettings()
|
||||
const tabs = useTabs()
|
||||
const sync = useServerSync()
|
||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||
const cached = createMemo(() => sync().session.lineage.peek(params.id))
|
||||
const [resolved] = createResource(
|
||||
() => {
|
||||
if (cached()) return
|
||||
return { id: params.id, server: serverKey(), sync: sync() }
|
||||
},
|
||||
({ id, server, sync }) =>
|
||||
sync.session.lineage.resolve(id).catch((error) => {
|
||||
if (isSessionNotFoundError(error, id)) tabs.removeSessionTab({ server, sessionId: id })
|
||||
throw error
|
||||
}),
|
||||
)
|
||||
const current = createMemo(() => selectSessionLineage(params.id, cached(), resolved()))
|
||||
const directory = createMemo(() => current()?.session.directory)
|
||||
const targetDirectory = () => directory()!
|
||||
|
||||
createEffect(() => {
|
||||
const session = current()
|
||||
if (!session) return
|
||||
tabs.addSessionTab({
|
||||
server: serverKey(),
|
||||
sessionId: session.root.id,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<TargetServerScopedProviders directory={directory} sessionID={() => params.id}>
|
||||
<Show when={!!current() || resolved.state !== "errored"} fallback={<ErrorPage error={resolved.error} />}>
|
||||
<Show when={directory()}>
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
||||
>
|
||||
<SDKProvider directory={targetDirectory}>
|
||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||
<TargetSessionPage />
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
</TargetServerScopedProviders>
|
||||
)
|
||||
}
|
||||
|
||||
function TargetSessionPage() {
|
||||
const sdk = useSDK()
|
||||
const serverSDK = useServerSDK()
|
||||
return (
|
||||
<Show when={`${serverSDK().scope}\0${sdk().directory}`} keyed>
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
</SessionProviders>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -232,7 +157,7 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) {
|
||||
<Show when={`${props.draft.server}\0${props.draft.directory}`} keyed>
|
||||
<ServerSDKProvider server={conn}>
|
||||
<ServerSyncProvider server={conn}>
|
||||
<TargetServerScopedProviders directory={directory}>
|
||||
<DraftServerScopedProviders directory={directory}>
|
||||
<SDKProvider directory={directory}>
|
||||
<DirectoryDataProvider directory={directory} server={serverKey}>
|
||||
<DraftProviders>
|
||||
@@ -240,7 +165,7 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) {
|
||||
</DraftProviders>
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</TargetServerScopedProviders>
|
||||
</DraftServerScopedProviders>
|
||||
</ServerSyncProvider>
|
||||
</ServerSDKProvider>
|
||||
</Show>
|
||||
@@ -365,38 +290,14 @@ function NewAppLayout(props: ParentProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function TargetServerScopedProviders(props: ServerScopedShellProps) {
|
||||
function DraftServerScopedProviders(props: ParentProps<{ directory?: () => string | undefined }>) {
|
||||
return (
|
||||
<PermissionProvider directory={props.directory}>
|
||||
<MarkSessionNotificationsViewed sessionID={props.sessionID} />
|
||||
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
|
||||
</PermissionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function MarkSessionNotificationsViewed(props: { sessionID?: () => string | undefined }) {
|
||||
const notification = useNotification()
|
||||
createEffect(() => {
|
||||
const sessionID = props.sessionID?.()
|
||||
if (!notification.ready() || !sessionID) return
|
||||
if (notification.session.unseenCount(sessionID) === 0) return
|
||||
notification.session.markViewed(sessionID)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
function SessionProviders(props: ParentProps) {
|
||||
return (
|
||||
<TerminalProvider>
|
||||
<FileProvider>
|
||||
<PromptProvider>
|
||||
<CommentsProvider>{props.children}</CommentsProvider>
|
||||
</PromptProvider>
|
||||
</FileProvider>
|
||||
</TerminalProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// The draft page only renders the prompt composer, so it drops TerminalProvider.
|
||||
// FileProvider and CommentsProvider stay because PromptInput uses file search and comment context.
|
||||
function DraftProviders(props: ParentProps) {
|
||||
|
||||
@@ -59,7 +59,6 @@ const ModelList: Component<{
|
||||
class="w-full"
|
||||
placement="right-start"
|
||||
gutter={12}
|
||||
openDelay={0}
|
||||
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
|
||||
>
|
||||
{node}
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
||||
</span>
|
||||
}
|
||||
placement="top"
|
||||
openDelay={2000}
|
||||
openDelay={800}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
|
||||
@@ -92,11 +92,17 @@ export function PromptWorkspaceSelector(props: {
|
||||
</MenuV2.Content>
|
||||
</MenuV2.Portal>
|
||||
</MenuV2>
|
||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
||||
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||
<span class="min-w-0 truncate">{props.branch || "main"}</span>
|
||||
</div>
|
||||
<Show when={props.branch}>
|
||||
{(branch) => (
|
||||
<>
|
||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
||||
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||
<span class="min-w-0 truncate">{branch()}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -541,6 +541,7 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) {
|
||||
</Show>
|
||||
<Show when={props.state.reviewVisible}>
|
||||
<TooltipV2
|
||||
class="shrink-0"
|
||||
placement="bottom"
|
||||
value={
|
||||
<>
|
||||
|
||||
@@ -21,6 +21,7 @@ export function TabNavItem(props: {
|
||||
href: string
|
||||
server: ServerConnection.Key
|
||||
session: () => Session | undefined
|
||||
fallbackTitle?: string
|
||||
onTitleChange?: (title: string) => void
|
||||
onTitleChangeFailed?: (title: string) => void
|
||||
onClose: () => void
|
||||
@@ -56,6 +57,7 @@ export function TabNavItem(props: {
|
||||
if (!session) return
|
||||
return projectForSession(session, serverCtx()?.projects.list() ?? [])
|
||||
})
|
||||
const title = createMemo(() => props.session()?.title ?? props.fallbackTitle)
|
||||
|
||||
const measureTitleOverflow = () => {
|
||||
if (!titleEl || editing()) {
|
||||
@@ -74,7 +76,7 @@ export function TabNavItem(props: {
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
props.session()?.title
|
||||
title()
|
||||
props.forceTruncate
|
||||
editing()
|
||||
scheduleTitleOverflow()
|
||||
@@ -133,9 +135,9 @@ export function TabNavItem(props: {
|
||||
createEffect(() => {
|
||||
if (editing()) return
|
||||
if (!titleEl) return
|
||||
const title = props.session()?.title
|
||||
if (title === undefined) return
|
||||
titleEl.textContent = title
|
||||
const value = title()
|
||||
if (value === undefined) return
|
||||
titleEl.textContent = value
|
||||
})
|
||||
|
||||
const openRename = (event: MouseEvent) => {
|
||||
@@ -196,26 +198,26 @@ export function TabNavItem(props: {
|
||||
closeTab(event)
|
||||
}}
|
||||
>
|
||||
<Show when={props.session()}>
|
||||
{(session) => {
|
||||
return (
|
||||
<a
|
||||
data-slot="tab-link"
|
||||
data-titlebar-tab-link
|
||||
href={props.href}
|
||||
draggable={false}
|
||||
onDragStart={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
if (editing()) return
|
||||
if (props.suppressNavigation?.()) return
|
||||
props.onNavigate()
|
||||
}}
|
||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
|
||||
>
|
||||
<Show when={title() !== undefined}>
|
||||
<a
|
||||
data-slot="tab-link"
|
||||
data-titlebar-tab-link
|
||||
href={props.href}
|
||||
draggable={false}
|
||||
onDragStart={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
if (editing()) return
|
||||
if (props.suppressNavigation?.()) return
|
||||
props.onNavigate()
|
||||
}}
|
||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
|
||||
>
|
||||
<Show when={props.session()}>
|
||||
{(session) => (
|
||||
<span data-slot="project-avatar-slot">
|
||||
<SessionTabAvatar
|
||||
project={project()}
|
||||
@@ -224,45 +226,45 @@ export function TabNavItem(props: {
|
||||
activeServer={props.activeServer}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
ref={(el) => {
|
||||
titleEl = el
|
||||
titleEl.textContent = session().title
|
||||
}}
|
||||
data-slot="tab-title"
|
||||
data-titlebar-tab-title
|
||||
class="min-w-0 flex-1 outline-none leading-4"
|
||||
classList={{
|
||||
"overflow-hidden text-clip whitespace-nowrap": !editing(),
|
||||
"select-text": editing(),
|
||||
}}
|
||||
contenteditable={editing() ? true : undefined}
|
||||
onDblClick={openRename}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
void closeRename(true)
|
||||
return
|
||||
}
|
||||
if (event.key !== "Escape") return
|
||||
event.preventDefault()
|
||||
titleEl.textContent = session().title
|
||||
void closeRename(false)
|
||||
}}
|
||||
onBlur={() => void closeRename(true)}
|
||||
onPointerDown={(event) => {
|
||||
if (!editing()) return
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (!editing()) return
|
||||
event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
)}
|
||||
</Show>
|
||||
<span
|
||||
ref={(el) => {
|
||||
titleEl = el
|
||||
titleEl.textContent = title() ?? ""
|
||||
}}
|
||||
data-slot="tab-title"
|
||||
data-titlebar-tab-title
|
||||
class="min-w-0 flex-1 outline-none leading-4"
|
||||
classList={{
|
||||
"overflow-hidden text-clip whitespace-nowrap": !editing(),
|
||||
"select-text": editing(),
|
||||
}}
|
||||
contenteditable={editing() ? true : undefined}
|
||||
onDblClick={openRename}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
void closeRename(true)
|
||||
return
|
||||
}
|
||||
if (event.key !== "Escape") return
|
||||
event.preventDefault()
|
||||
titleEl.textContent = props.session()?.title ?? ""
|
||||
void closeRename(false)
|
||||
}}
|
||||
onBlur={() => void closeRename(true)}
|
||||
onPointerDown={(event) => {
|
||||
if (!editing()) return
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (!editing()) return
|
||||
event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</Show>
|
||||
|
||||
<div data-slot="tab-close" class="group-hover:bg-[var(--tab-bg)] group-data-[active=true]:bg-[var(--tab-bg)]">
|
||||
|
||||
@@ -31,6 +31,7 @@ function SessionTabSlot(props: {
|
||||
onClose: () => void
|
||||
}) {
|
||||
const tabs = useTabs()
|
||||
const language = useLanguage()
|
||||
const sortable = useSortable({
|
||||
get id() {
|
||||
return props.id
|
||||
@@ -50,6 +51,7 @@ function SessionTabSlot(props: {
|
||||
({ id, ctx }) => ctx.sync.session.resolve(id).catch(() => undefined),
|
||||
)
|
||||
const session = createMemo(() => cachedSession() ?? loadedSession())
|
||||
const missingSession = createMemo(() => !!props.serverCtx() && !loadedSession.loading && !session())
|
||||
let prefetched = false
|
||||
|
||||
createEffect(() => {
|
||||
@@ -87,7 +89,7 @@ function SessionTabSlot(props: {
|
||||
data-tab-key={props.id}
|
||||
data-active={props.active()}
|
||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||
classList={{ hidden: !session() }}
|
||||
classList={{ hidden: !session() && !missingSession() }}
|
||||
>
|
||||
<TabNavItem
|
||||
ref={(el) => {
|
||||
@@ -96,6 +98,7 @@ function SessionTabSlot(props: {
|
||||
href={tabHref(props.tab)}
|
||||
server={props.tab.server}
|
||||
session={session}
|
||||
fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined}
|
||||
onTitleChange={(title) => {
|
||||
const value = session()
|
||||
const ctx = props.serverCtx()
|
||||
|
||||
@@ -572,7 +572,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
placement="bottom"
|
||||
title={language.t("command.session.new")}
|
||||
keybind={command.keybind("session.new")}
|
||||
openDelay={2000}
|
||||
openDelay={800}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -602,7 +602,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
>
|
||||
<Show when={hasProjects() && nav()}>
|
||||
<div class="flex items-center gap-0 transition-transform">
|
||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={800}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="chevron-left"
|
||||
@@ -612,7 +612,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
aria-label={language.t("common.goBack")}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={800}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="chevron-right"
|
||||
|
||||
@@ -604,7 +604,12 @@ export const dict = {
|
||||
"session.tab.session": "Session",
|
||||
"session.tab.review": "Review",
|
||||
"session.tab.context": "Context",
|
||||
"session.tab.unknown": "Unknown Session",
|
||||
"session.panel.reviewAndFiles": "Review and files",
|
||||
"session.error.notFound": "This session cannot be found",
|
||||
"session.error.notFound.description": "This tab points to a session that no longer exists on this server.",
|
||||
"session.error.notFound.closeTab": "Close Tab",
|
||||
"session.error.serverConnection": "Can't connect to this server",
|
||||
"session.review.filesChanged": "{{count}} Files Changed",
|
||||
"session.review.change.one": "Change",
|
||||
"session.review.change.other": "Changes",
|
||||
|
||||
@@ -277,7 +277,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center bg-background-base font-sans"
|
||||
class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center font-sans"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
|
||||
|
||||
@@ -1006,8 +1006,8 @@ function HomeSessionLeading(props: {
|
||||
<Show when={hasOpenTab()}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute top-1/2 h-[7px] w-[3px] -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
||||
style={{ right: "calc(100% + 5px)" }}
|
||||
class="pointer-events-none absolute top-1/2 h-3 w-0.5 -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
||||
style={{ right: "calc(100% + 4px)" }}
|
||||
/>
|
||||
</Show>
|
||||
<SessionTabAvatar
|
||||
@@ -1163,13 +1163,7 @@ function HomeSessionSearch(props: {
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<label
|
||||
class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out"
|
||||
classList={{
|
||||
"focus-within:shadow-[0_0_0_0.5px_var(--v2-border-border-focus),var(--v2-elevation-raised)]": !props.open,
|
||||
"shadow-[0_0_0_0.5px_var(--v2-border-border-focus)]": props.open,
|
||||
}}
|
||||
>
|
||||
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02">
|
||||
<IconV2 name="magnifying-glass" />
|
||||
<input
|
||||
ref={input}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query"
|
||||
import {
|
||||
batch,
|
||||
ErrorBoundary,
|
||||
onCleanup,
|
||||
Show,
|
||||
Match,
|
||||
@@ -10,8 +11,10 @@ import {
|
||||
createMemo,
|
||||
createEffect,
|
||||
createComputed,
|
||||
createResource,
|
||||
on,
|
||||
onMount,
|
||||
type ParentProps,
|
||||
untrack,
|
||||
} from "solid-js"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
@@ -19,29 +22,37 @@ import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { debounce } from "@solid-primitives/scheduled"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { FileProvider, selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
||||
import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
||||
import { useLocation, useNavigate, useSearchParams } from "@solidjs/router"
|
||||
import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||
import { NewSessionView, SessionHeader } from "@/components/session"
|
||||
import { useComments } from "@/context/comments"
|
||||
import { ErrorPage } from "@/pages/error"
|
||||
import { CommentsProvider, useComments } from "@/context/comments"
|
||||
import { DirectoryDataProvider } from "@/pages/directory-layout"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { usePrompt } from "@/context/prompt"
|
||||
import { ModelsProvider } from "@/context/models"
|
||||
import { useNotification } from "@/context/notification"
|
||||
import { PermissionProvider } from "@/context/permission"
|
||||
import { PromptProvider, usePrompt } from "@/context/prompt"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { SDKProvider, useSDK } from "@/context/sdk"
|
||||
import { useServerSDK } from "@/context/server-sdk"
|
||||
import { ServerConnection, serverName, useServer } from "@/context/server"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useTerminal } from "@/context/terminal"
|
||||
import { useTabs } from "@/context/tabs"
|
||||
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||
@@ -73,8 +84,8 @@ import { Identifier } from "@/utils/id"
|
||||
import { diffs as list } from "@/utils/diffs"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { extractPromptFromParts } from "@/utils/prompt"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { legacySessionHref, requireServerKey, sessionHref } from "@/utils/session-route"
|
||||
import { formatServerError, isSessionNotFoundError } from "@/utils/server-errors"
|
||||
import { legacySessionHref, requireServerKey, selectSessionLineage, sessionHref } from "@/utils/session-route"
|
||||
import { useUsageExceededDialogs } from "./session/usage-exceeded-dialogs"
|
||||
import { createSessionOwnership } from "./session/session-ownership"
|
||||
|
||||
@@ -91,6 +102,15 @@ const sessionViewState = () => ({
|
||||
changes: "git" as ChangeMode,
|
||||
})
|
||||
|
||||
function isLocalSessionNotFoundError(error: unknown, sessionID: string) {
|
||||
return error instanceof Error && error.message === `Session not found: ${sessionID}`
|
||||
}
|
||||
|
||||
function isCurrentSessionNotFoundError(error: unknown, sessionID: string | undefined) {
|
||||
if (!sessionID) return false
|
||||
return isSessionNotFoundError(error, sessionID) || isLocalSessionNotFoundError(error, sessionID)
|
||||
}
|
||||
|
||||
async function runPromptRollbackMutation<T, R>(input: {
|
||||
capturePrompt: () => { current: () => T[]; set: (value: T[]) => void; reset: () => void }
|
||||
optimistic: (prompt: { set: (value: T[]) => void; reset: () => void }) => void
|
||||
@@ -114,6 +134,206 @@ async function runPromptRollbackMutation<T, R>(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export function SessionPage() {
|
||||
return (
|
||||
<SessionProviders>
|
||||
<Page />
|
||||
</SessionProviders>
|
||||
)
|
||||
}
|
||||
|
||||
export function TargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
return (
|
||||
<Show when={`${params.serverKey}\0${params.id}`} keyed>
|
||||
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)} padded>
|
||||
<ResolvedTargetSessionRoute />
|
||||
</SessionRouteErrorBoundary>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionRouteErrorBoundary(
|
||||
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
||||
) {
|
||||
const settings = useSettings()
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={(error) =>
|
||||
settings.general.newLayoutDesigns() ? (
|
||||
<SessionRouteFrame padded={props.padded}>
|
||||
<SessionPanelFrame newLayout raised={!!props.sessionID}>
|
||||
<SessionErrorFallback error={error} sessionID={props.sessionID} serverKey={props.serverKey} />
|
||||
</SessionPanelFrame>
|
||||
</SessionRouteFrame>
|
||||
) : (
|
||||
<ErrorPage error={error} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionErrorFallback(props: { error: unknown; sessionID?: string; serverKey?: ServerConnection.Key }) {
|
||||
const language = useLanguage()
|
||||
const server = useServer()
|
||||
const tabs = useTabs()
|
||||
const displayServer = createMemo(() => {
|
||||
const key = props.serverKey ?? server.key
|
||||
const conn = server.list.find((item) => ServerConnection.key(item) === key)
|
||||
return conn ? serverName(conn) : key
|
||||
})
|
||||
const closeTab = () => {
|
||||
if (!props.sessionID) return
|
||||
tabs.removeSessionTab({ server: props.serverKey ?? server.key, sessionId: props.sessionID })
|
||||
}
|
||||
if (isCurrentSessionNotFoundError(props.error, props.sessionID)) {
|
||||
return (
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<div class="h-full px-6 pb-42 -mt-4 flex flex-col items-center justify-center text-center gap-4">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="text-16-medium text-text max-w-md">{language.t("session.error.notFound")}</div>
|
||||
<div class="text-13-regular text-text-weak max-w-md">
|
||||
{language.t("session.error.notFound.description")}
|
||||
</div>
|
||||
</div>
|
||||
<Show when={props.sessionID}>
|
||||
{(sessionID) => (
|
||||
<div class="max-w-full flex flex-col items-center gap-1">
|
||||
<div class="max-w-full text-11-regular text-text-faint break-all">
|
||||
{displayServer()}
|
||||
</div>
|
||||
<code class="max-w-full rounded-[4px] px-1 py-0.5 font-mono text-xs font-medium leading-4 text-text-base break-all bg-[color-mix(in_oklch,var(--v2-text-text-base)_8%,transparent)]">
|
||||
{sessionID()}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<ButtonV2 variant="neutral" size="normal" icon="xmark-small" onClick={closeTab}>
|
||||
{language.t("session.error.notFound.closeTab")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <ErrorPage error={props.error} />
|
||||
}
|
||||
|
||||
function ResolvedTargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
const settings = useSettings()
|
||||
const tabs = useTabs()
|
||||
const sync = useServerSync()
|
||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||
const cached = createMemo(() => sync().session.lineage.peek(params.id))
|
||||
const [resolved] = createResource(
|
||||
() => {
|
||||
if (cached()) return
|
||||
return { id: params.id, sync: sync() }
|
||||
},
|
||||
({ id, sync }) => sync.session.lineage.resolve(id),
|
||||
)
|
||||
const current = createMemo(() => selectSessionLineage(params.id, cached(), resolved()))
|
||||
const directory = createMemo(() => current()?.session.directory)
|
||||
const targetDirectory = () => directory()!
|
||||
|
||||
createEffect(() => {
|
||||
const session = current()
|
||||
if (!session) return
|
||||
tabs.addSessionTab({
|
||||
server: serverKey(),
|
||||
sessionId: session.root.id,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<TargetServerScopedProviders directory={directory} sessionID={() => params.id}>
|
||||
<Show when={directory()}>
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
||||
>
|
||||
<SDKProvider directory={targetDirectory}>
|
||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||
<TargetSessionPage />
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</Show>
|
||||
</Show>
|
||||
</TargetServerScopedProviders>
|
||||
)
|
||||
}
|
||||
|
||||
function TargetSessionPage() {
|
||||
const sdk = useSDK()
|
||||
const serverSDK = useServerSDK()
|
||||
return (
|
||||
<Show when={`${serverSDK().scope}\0${sdk().directory}`} keyed>
|
||||
<SessionPage />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function TargetServerScopedProviders(
|
||||
props: ParentProps<{ directory?: () => string | undefined; sessionID?: () => string | undefined }>,
|
||||
) {
|
||||
return (
|
||||
<PermissionProvider directory={props.directory}>
|
||||
<MarkSessionNotificationsViewed sessionID={props.sessionID} />
|
||||
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
|
||||
</PermissionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function MarkSessionNotificationsViewed(props: { sessionID?: () => string | undefined }) {
|
||||
const notification = useNotification()
|
||||
createEffect(() => {
|
||||
const sessionID = props.sessionID?.()
|
||||
if (!notification.ready() || !sessionID) return
|
||||
if (notification.session.unseenCount(sessionID) === 0) return
|
||||
notification.session.markViewed(sessionID)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
function SessionProviders(props: ParentProps) {
|
||||
return (
|
||||
<TerminalProvider>
|
||||
<FileProvider>
|
||||
<PromptProvider>
|
||||
<CommentsProvider>{props.children}</CommentsProvider>
|
||||
</PromptProvider>
|
||||
</FileProvider>
|
||||
</TerminalProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionRouteFrame(props: ParentProps<{ padded?: boolean }>) {
|
||||
return (
|
||||
<div class="relative size-full overflow-hidden flex flex-col" classList={{ "p-2": props.padded }}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionPanelFrame(props: ParentProps<{ newLayout: boolean; raised?: boolean }>) {
|
||||
return (
|
||||
<div
|
||||
classList={{
|
||||
"flex-1 min-h-0 flex flex-col": true,
|
||||
"bg-v2-background-bg-base": props.newLayout,
|
||||
"bg-background-stronger": !props.newLayout,
|
||||
"rounded-[10px] overflow-hidden": props.newLayout,
|
||||
"shadow-[var(--v2-elevation-raised)]": props.newLayout && props.raised,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const serverSync = useServerSync()
|
||||
const layout = useLayout()
|
||||
@@ -1695,9 +1915,88 @@ export default function Page() {
|
||||
() => !isDesktop() && settings.general.newLayoutDesigns() && settings.general.mobileTitlebarPosition() === "bottom",
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="relative size-full overflow-hidden flex flex-col">
|
||||
const sessionErrorFallback = (error: unknown, reset: () => void) => {
|
||||
createEffect(on(sessionKey, reset, { defer: true }))
|
||||
return <SessionErrorFallback error={error} sessionID={params.id} />
|
||||
}
|
||||
|
||||
const sessionPanelContent = () => (
|
||||
<>
|
||||
{sessionSync() ?? ""}
|
||||
<Show when={!isDesktop() && !!params.id && settings.general.newLayoutDesigns() && !mobileTabsBottom()}>
|
||||
{mobileTabs(true)}
|
||||
</Show>
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<Switch>
|
||||
<Match when={params.id && mobileChanges()}>
|
||||
<div class="relative h-full overflow-hidden">
|
||||
{reviewContent({
|
||||
diffStyle: "unified",
|
||||
classes: {
|
||||
root: "pb-8 [&_[data-slot=session-review-list]]:pb-0",
|
||||
header: "px-4 !h-16 !pb-4",
|
||||
container: "px-4",
|
||||
},
|
||||
loadingClass: "px-4 py-4 text-text-weak",
|
||||
emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6",
|
||||
})}
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={params.id}>
|
||||
<Show when={messagesReady() ? params.id : undefined} keyed>
|
||||
{(_id) => (
|
||||
<MessageTimeline
|
||||
actions={actions}
|
||||
scroll={ui.scroll}
|
||||
onResumeScroll={resumeScroll}
|
||||
setScrollRef={setScrollRef}
|
||||
onScheduleScrollState={scheduleScrollState}
|
||||
onAutoScrollHandleScroll={autoScroll.handleScroll}
|
||||
onMarkScrollGesture={markScrollGesture}
|
||||
hasScrollGesture={hasScrollGesture}
|
||||
onUserScroll={markUserScroll}
|
||||
onHistoryScroll={onHistoryScroll}
|
||||
onAutoScrollInteraction={autoScroll.handleInteraction}
|
||||
shouldAnchorBottom={() =>
|
||||
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||
}
|
||||
centered={centered()}
|
||||
setContentRef={(el) => {
|
||||
content = el
|
||||
autoScroll.contentRef(el)
|
||||
|
||||
const root = scroller
|
||||
if (root) scheduleScrollState(root)
|
||||
}}
|
||||
userMessages={visibleUserMessages()}
|
||||
setHistoryAnchor={(handlers) => {
|
||||
captureHistoryAnchor = handlers.capture
|
||||
restoreHistoryAnchor = handlers.restore
|
||||
}}
|
||||
anchor={anchor}
|
||||
setRevealMessage={(fn) => {
|
||||
revealMessage = fn
|
||||
}}
|
||||
setScrollToEnd={(fn) => {
|
||||
scrollToEnd = fn
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<NewSessionView worktree={newSessionWorktree()} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<Show when={(params.id || !newSessionDesign()) && !mobileChanges()}>{(_) => composerRegion()}</Show>
|
||||
<Show when={!!params.id && mobileTabsBottom()}>{mobileTabs(true, true)}</Show>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<SessionRouteFrame>
|
||||
<SessionHeader />
|
||||
<div
|
||||
class="flex-1 min-h-0 flex flex-col md:flex-row"
|
||||
@@ -1717,85 +2016,13 @@ export default function Page() {
|
||||
width: sessionPanelWidth(),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"flex-1 min-h-0 flex flex-col": true,
|
||||
"bg-v2-background-bg-base": settings.general.newLayoutDesigns(),
|
||||
"bg-background-stronger": !settings.general.newLayoutDesigns(),
|
||||
"rounded-[10px] overflow-hidden": settings.general.newLayoutDesigns(),
|
||||
"shadow-[var(--v2-elevation-raised)]": settings.general.newLayoutDesigns() && !!params.id,
|
||||
}}
|
||||
>
|
||||
<Show when={!isDesktop() && !!params.id && settings.general.newLayoutDesigns() && !mobileTabsBottom()}>
|
||||
{mobileTabs(true)}
|
||||
</Show>
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<Switch>
|
||||
<Match when={params.id && mobileChanges()}>
|
||||
<div class="relative h-full overflow-hidden">
|
||||
{reviewContent({
|
||||
diffStyle: "unified",
|
||||
classes: {
|
||||
root: "pb-8 [&_[data-slot=session-review-list]]:pb-0",
|
||||
header: "px-4 !h-16 !pb-4",
|
||||
container: "px-4",
|
||||
},
|
||||
loadingClass: "px-4 py-4 text-text-weak",
|
||||
emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6",
|
||||
})}
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={params.id}>
|
||||
<Show when={messagesReady() ? params.id : undefined} keyed>
|
||||
{(_id) => (
|
||||
<MessageTimeline
|
||||
actions={actions}
|
||||
scroll={ui.scroll}
|
||||
onResumeScroll={resumeScroll}
|
||||
setScrollRef={setScrollRef}
|
||||
onScheduleScrollState={scheduleScrollState}
|
||||
onAutoScrollHandleScroll={autoScroll.handleScroll}
|
||||
onMarkScrollGesture={markScrollGesture}
|
||||
hasScrollGesture={hasScrollGesture}
|
||||
onUserScroll={markUserScroll}
|
||||
onHistoryScroll={onHistoryScroll}
|
||||
onAutoScrollInteraction={autoScroll.handleInteraction}
|
||||
shouldAnchorBottom={() =>
|
||||
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||
}
|
||||
centered={centered()}
|
||||
setContentRef={(el) => {
|
||||
content = el
|
||||
autoScroll.contentRef(el)
|
||||
|
||||
const root = scroller
|
||||
if (root) scheduleScrollState(root)
|
||||
}}
|
||||
userMessages={visibleUserMessages()}
|
||||
setHistoryAnchor={(handlers) => {
|
||||
captureHistoryAnchor = handlers.capture
|
||||
restoreHistoryAnchor = handlers.restore
|
||||
}}
|
||||
anchor={anchor}
|
||||
setRevealMessage={(fn) => {
|
||||
revealMessage = fn
|
||||
}}
|
||||
setScrollToEnd={(fn) => {
|
||||
scrollToEnd = fn
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<NewSessionView worktree={newSessionWorktree()} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<Show when={(params.id || !newSessionDesign()) && !mobileChanges()}>{(_) => composerRegion()}</Show>
|
||||
<Show when={!!params.id && mobileTabsBottom()}>{mobileTabs(true, true)}</Show>
|
||||
</div>
|
||||
<SessionPanelFrame newLayout={settings.general.newLayoutDesigns()} raised={!!params.id}>
|
||||
{settings.general.newLayoutDesigns() ? (
|
||||
<ErrorBoundary fallback={sessionErrorFallback}>{sessionPanelContent()}</ErrorBoundary>
|
||||
) : (
|
||||
sessionPanelContent()
|
||||
)}
|
||||
</SessionPanelFrame>
|
||||
|
||||
<Show when={desktopReviewOpen()}>
|
||||
<div onPointerDown={() => size.start()}>
|
||||
@@ -1832,6 +2059,6 @@ export default function Page() {
|
||||
</div>
|
||||
|
||||
<TerminalPanel />
|
||||
</div>
|
||||
</SessionRouteFrame>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { NodeHttpServer } from "@effect/platform-node"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||
import { Context, Layer, Option } from "effect"
|
||||
import * as Effect from "effect/Effect"
|
||||
@@ -38,8 +40,7 @@ function bind(hostname: string, port: number, password: string) {
|
||||
return Layer.build(
|
||||
HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe(
|
||||
Layer.provideMerge(NodeHttpServer.layer(() => createServer(), { port, host: hostname })),
|
||||
Layer.provide(Credential.defaultLayer),
|
||||
Layer.provide(PermissionSaved.defaultLayer),
|
||||
Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node]))),
|
||||
),
|
||||
).pipe(Effect.map((context) => Context.get(context, HttpServer.HttpServer).address))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const Handlers = Runtime.handlers(Commands, {
|
||||
})
|
||||
|
||||
Runtime.run(Commands, Handlers, { version: "local" }).pipe(
|
||||
Effect.provide(Daemon.defaultLayer),
|
||||
Effect.provide(Daemon.layer),
|
||||
Effect.provide(NodeServices.layer),
|
||||
Effect.scoped,
|
||||
NodeRuntime.runMain,
|
||||
|
||||
@@ -189,6 +189,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export * as Daemon from "./daemon"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { run } from "@opencode-ai/tui"
|
||||
import { TuiConfig } from "@opencode-ai/tui/config"
|
||||
import { Effect } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
|
||||
export function runTui(transport: { url: string; headers: RequestInit["headers"] }) {
|
||||
@@ -14,7 +15,7 @@ export function runTui(transport: { url: string; headers: RequestInit["headers"]
|
||||
async start() {},
|
||||
async dispose() {},
|
||||
},
|
||||
}).pipe(Effect.provide(Global.defaultLayer))
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
|
||||
}
|
||||
|
||||
const legacyDefaults: Record<string, unknown> = {
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface Interface extends State.Transformable<Draft> {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const state = State.create<Data, Draft>({
|
||||
|
||||
@@ -233,5 +233,3 @@ export const locationLayer = Layer.effect(
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer: locationLayer, deps: [] })
|
||||
|
||||
export const defaultLayer = locationLayer
|
||||
|
||||
@@ -360,8 +360,6 @@ export const make = Effect.gen(function* () {
|
||||
return Service.of({ list, get, start, extend, wait, waitForPromotion, promote, cancel })
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(Service, make)
|
||||
|
||||
export const defaultLayer = layer
|
||||
const layer = Layer.effect(Service, make)
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [] })
|
||||
|
||||
@@ -61,7 +61,7 @@ export interface Interface extends State.Transformable<Draft> {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Catalog") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface Interface extends State.Transformable<Draft> {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Command") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.sync(() => {
|
||||
const state = State.create<Data, Draft>({
|
||||
|
||||
@@ -132,14 +132,14 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Config") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const global = yield* Global.Service
|
||||
const location = yield* Location.Service
|
||||
const policy = yield* Policy.Service
|
||||
const names = ["config.json", "opencode.json", "opencode.jsonc"]
|
||||
const names = ["opencode.json", "opencode.jsonc"]
|
||||
const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
|
||||
const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)
|
||||
const decodeV1Info = Schema.decodeUnknownOption(ConfigV1.Info, decodeOptions)
|
||||
|
||||
@@ -66,7 +66,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ControlPlaneMoveSession") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const git = yield* Git.Service
|
||||
@@ -141,13 +141,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer,
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Credential") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
@@ -135,6 +135,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
|
||||
|
||||
@@ -497,12 +497,11 @@ export const make = Effect.gen(function* () {
|
||||
return makeSpawner(spawnCommand)
|
||||
})
|
||||
|
||||
export const layer: Layer.Layer<ChildProcessSpawner, never, FileSystem.FileSystem | Path.Path> = Layer.effect(
|
||||
const layer: Layer.Layer<ChildProcessSpawner, never, FileSystem.FileSystem | Path.Path> = Layer.effect(
|
||||
ChildProcessSpawner,
|
||||
make,
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer), Layer.provide(NodePath.layer))
|
||||
export const node = makeGlobalNode({ service: ChildProcessSpawner, layer, deps: [filesystem, path] })
|
||||
|
||||
export * as CrossSpawnSpawner from "./cross-spawn-spawner"
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDatabase
|
||||
@@ -54,10 +54,4 @@ export function path() {
|
||||
return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
|
||||
}
|
||||
|
||||
export const defaultLayer = Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
return layerFromPath(path())
|
||||
}),
|
||||
).pipe(Layer.provide(Global.defaultLayer))
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer: layerFromPath(path()), deps: [] })
|
||||
|
||||
@@ -634,7 +634,5 @@ export const layerWith = (options?: LayerOptions) =>
|
||||
}),
|
||||
)
|
||||
|
||||
export const layer = layerWith()
|
||||
const layer = layerWith()
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] })
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
||||
@@ -71,7 +71,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
||||
* write under the same process-local lock so cooperating OpenCode mutations do
|
||||
* not overwrite changes made from the same stale content.
|
||||
*/
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
||||
@@ -111,10 +111,6 @@ const baseLayer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const layer = baseLayer.pipe(Layer.provide(FileSystemSearch.locationLayer), Layer.provide(FSUtil.defaultLayer))
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: baseLayer,
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface Interface {}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/FileWatcher") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return Service.of({})
|
||||
@@ -133,8 +133,6 @@ export const layer = Layer.effect(
|
||||
),
|
||||
)
|
||||
|
||||
export const locationLayer = layer.pipe(Layer.provide(Config.locationLayer), Layer.provide(Git.defaultLayer))
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
|
||||
@@ -49,7 +49,7 @@ export namespace FSUtil {
|
||||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FileSystem.FileSystem
|
||||
@@ -200,7 +200,6 @@ export namespace FSUtil {
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer))
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [filesystem] })
|
||||
|
||||
// Pure helpers that don't need Effect (path manipulation, sync operations)
|
||||
|
||||
@@ -172,7 +172,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/GitV2") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -943,7 +943,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(AppProcess.defaultLayer))
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [FSUtil.node, AppProcess.node] })
|
||||
|
||||
interface Result {
|
||||
|
||||
@@ -71,12 +71,11 @@ export function make(input: Partial<Interface> = {}): Interface {
|
||||
}
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.sync(() => Service.of(make())),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] })
|
||||
|
||||
export const layerWith = (input: Partial<Interface>) =>
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
||||
@@ -19,7 +19,7 @@ class File extends Schema.Class<File>("InstructionContext.File")({
|
||||
const Files = Schema.Array(File)
|
||||
const key = SystemContext.Key.make("core/instructions")
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const global = yield* Global.Service
|
||||
|
||||
@@ -76,7 +76,7 @@ interface ResolvedPath {
|
||||
|
||||
const slash = (value: string) => value.replaceAll("\\", "/")
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
||||
@@ -16,7 +16,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Lo
|
||||
|
||||
export const node = LayerNode.unbound(Service, tags.values.location)
|
||||
|
||||
export const layer = (ref: Ref) =>
|
||||
const layer = (ref: Ref) =>
|
||||
Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -120,7 +120,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ModelsDev") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -239,11 +239,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
)
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [FSUtil.node, EventV2.node, httpClient] })
|
||||
|
||||
export * as ModelsDev from "./models-dev"
|
||||
|
||||
@@ -69,7 +69,7 @@ interface ArboristTree {
|
||||
edgesOut: Map<string, { to?: ArboristNode }>
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const afs = yield* FSUtil.Service
|
||||
@@ -248,12 +248,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(EffectFlock.layer),
|
||||
Layer.provide(FSUtil.layer),
|
||||
Layer.provide(Global.layer),
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
)
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
||||
@@ -106,7 +106,7 @@ interface Pending {
|
||||
readonly deferred: Deferred.Deferred<void, RejectedError | CorrectedError>
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
EffectRuntime.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
@@ -34,7 +34,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/PermissionSaved") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
@@ -76,6 +76,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Plugin") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
@@ -149,7 +149,6 @@ export const locationLayer = layer.pipe(
|
||||
Layer.provideMerge(CommandV2.locationLayer),
|
||||
Layer.provideMerge(Integration.locationLayer),
|
||||
Layer.provideMerge(Reference.locationLayer),
|
||||
Layer.provideMerge(SkillV2.locationLayer),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
|
||||
@@ -125,9 +125,7 @@ const layer = Layer.effectDiscard(
|
||||
)
|
||||
|
||||
export const locationLayer = layer.pipe(
|
||||
Layer.provideMerge(PluginV2.locationLayer),
|
||||
Layer.provideMerge(Config.locationLayer),
|
||||
Layer.provideMerge(FileSystem.locationLayer),
|
||||
Layer.provideMerge(FetchHttpClient.layer),
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Policy") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
EffectRuntime.gen(function* () {
|
||||
let statements: Info[] = []
|
||||
|
||||
@@ -136,7 +136,7 @@ export const collectStream = (stream: Stream.Stream<Uint8Array, PlatformError>,
|
||||
},
|
||||
).pipe(Effect.map((x) => ({ buffer: Buffer.concat(x.chunks), truncated: x.truncated })))
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const spawner = yield* ChildProcessSpawner
|
||||
@@ -256,7 +256,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(CrossSpawnSpawner.defaultLayer))
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [CrossSpawnSpawner.node] })
|
||||
|
||||
export * as AppProcess from "./process"
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ProjectV2") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -129,11 +129,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provideMerge(ProjectDirectories.defaultLayer),
|
||||
)
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
||||
@@ -125,7 +125,7 @@ export const refreshAfterBoot = Effect.gen(function* () {
|
||||
)
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ProjectDirectories") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const db = (yield* Database.Service).db
|
||||
@@ -155,5 +155,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] })
|
||||
|
||||
@@ -89,7 +89,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Pty") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
@@ -51,7 +51,6 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL) =>
|
||||
})
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(Service, make())
|
||||
const layer = Layer.effect(Service, make())
|
||||
|
||||
export const defaultLayer = layer
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] })
|
||||
|
||||
@@ -72,7 +72,7 @@ interface Pending {
|
||||
* layer once per embedded Location so replies cannot settle another Location's
|
||||
* deferred request.
|
||||
*/
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface Interface extends State.Transformable<Draft> {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Reference") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const global = yield* Global.Service
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/ReferenceGuidance") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const references = yield* Reference.Service
|
||||
|
||||
@@ -121,7 +121,7 @@ export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(functi
|
||||
})
|
||||
})
|
||||
|
||||
export const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFlock.Service | Global.Service> =
|
||||
const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFlock.Service | Global.Service> =
|
||||
Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
@@ -223,13 +223,6 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | E
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
||||
Layer.provide(EffectFlock.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Global.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer,
|
||||
|
||||
@@ -89,7 +89,7 @@ const failure = (message: string, cause?: unknown) => new Error({ message, cause
|
||||
const isInvalidPattern = (stderr: string) =>
|
||||
stderr.includes("regex parse error") || stderr.includes("error parsing regex")
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const process = yield* AppProcess.Service
|
||||
@@ -278,5 +278,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Layer.merge(RipgrepBinary.defaultLayer, AppProcess.defaultLayer)))
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [RipgrepBinary.node, AppProcess.node] })
|
||||
|
||||
@@ -28,7 +28,7 @@ export namespace RipgrepBinary {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/RipgrepBinary") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -124,12 +124,6 @@ export namespace RipgrepBinary {
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
||||
@@ -181,7 +181,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Session") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const database = yield* Database.Service
|
||||
@@ -457,15 +457,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
Layer.provide(SessionProjector.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.orDie,
|
||||
)
|
||||
|
||||
const resolvePrompt = (input: PromptInput.Prompt) =>
|
||||
Prompt.make({
|
||||
text: input.text,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SessionStore } from "../store"
|
||||
import { SessionExecution } from "../execution"
|
||||
|
||||
/** Current-process routing for implicit-local Locations. Future remote placement belongs here. */
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
SessionExecution.Service,
|
||||
Effect.gen(function* () {
|
||||
const store = yield* SessionStore.Service
|
||||
@@ -37,8 +37,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(SessionStore.defaultLayer))
|
||||
|
||||
export const node = makeGlobalNode({
|
||||
service: SessionExecution.Service,
|
||||
layer,
|
||||
|
||||
@@ -208,7 +208,7 @@ function insertMessage(db: DatabaseService, event: SessionEvent.Event, message:
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
const { db } = yield* Database.Service
|
||||
@@ -455,5 +455,4 @@ export const layer = Layer.effectDiscard(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2.defaultLayer), Layer.provide(Database.defaultLayer))
|
||||
export const node = makeGlobalNode({ name: "session-projector", layer, deps: [EventV2.node, Database.node] })
|
||||
|
||||
@@ -89,7 +89,7 @@ import { llmClient } from "../../effect/app-node-platform"
|
||||
* explicit loop starts the next provider turn after local settlement. Configured agent step limits bound the loop.
|
||||
*/
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
@@ -406,8 +406,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/SessionStore") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
@@ -60,6 +60,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/SessionTodo") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
@@ -75,6 +75,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2.defaultLayer), Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [EventV2.node, Database.node] })
|
||||
|
||||
@@ -53,7 +53,7 @@ export interface Interface extends State.Transformable<Draft> {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Skill") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const discovery = yield* SkillDiscovery.Service
|
||||
@@ -129,6 +129,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer.pipe(Layer.provide(SkillDiscovery.defaultLayer))
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [SkillDiscovery.node, FSUtil.node] })
|
||||
|
||||
@@ -68,7 +68,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/SkillDiscovery") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -210,10 +210,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Global.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [httpClient, FSUtil.node, Global.node] })
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/SkillGuidance") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const skills = yield* SkillV2.Service
|
||||
|
||||
@@ -83,7 +83,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Snapshot") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
||||
@@ -43,14 +43,8 @@ const builtIns = Layer.effectDiscard(
|
||||
}),
|
||||
)
|
||||
|
||||
export const layer = Layer.mergeAll(builtIns, InstructionContext.layer).pipe(
|
||||
Layer.provideMerge(SystemContextRegistry.layer),
|
||||
)
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({
|
||||
name: "system-context-builtins",
|
||||
layer,
|
||||
layer: builtIns,
|
||||
deps: [Location.node, SystemContextRegistry.node, InstructionContext.node, FSUtil.node, Global.node],
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/SystemContextRegistry") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const entries = yield* Ref.make<ReadonlyArray<Entry>>([])
|
||||
|
||||
@@ -109,7 +109,7 @@ const lineCount = (text: string) => {
|
||||
return count
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -192,8 +192,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(Global.defaultLayer))
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.node, Global.node, Config.node] })
|
||||
|
||||
export const nodeWithoutConfig = makeLocationNode({ service: Service, layer, deps: [FSUtil.node, Global.node] })
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ApplicationTools") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const state = State.create<Data, Draft>({
|
||||
|
||||
@@ -56,7 +56,7 @@ type Prepared =
|
||||
readonly after: string
|
||||
})
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const mutation = yield* LocationMutation.Service
|
||||
|
||||
@@ -90,7 +90,7 @@ const externalCommandDirectories = (command: string, cwd: string) => {
|
||||
return [...directories]
|
||||
}
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const mutation = yield* LocationMutation.Service
|
||||
|
||||
@@ -9,7 +9,6 @@ import { GlobTool } from "./glob"
|
||||
import { GrepTool } from "./grep"
|
||||
import { QuestionTool } from "./question"
|
||||
import { ReadTool } from "./read"
|
||||
import { ReadToolFileSystem } from "./read-filesystem"
|
||||
import { SkillTool } from "./skill"
|
||||
import { TodoWriteTool } from "./todowrite"
|
||||
import { WebFetchTool } from "./webfetch"
|
||||
@@ -29,21 +28,6 @@ import { WriteTool } from "./write"
|
||||
* repo_clone, repo_overview, plan_exit, and Rune/code mode. Keep MCP and plugin
|
||||
* transforms separate from this static built-in list.
|
||||
*/
|
||||
export const locationLayer = Layer.mergeAll(
|
||||
ApplyPatchTool.layer,
|
||||
BashTool.layer,
|
||||
EditTool.layer,
|
||||
GlobTool.layer,
|
||||
GrepTool.layer,
|
||||
QuestionTool.layer,
|
||||
ReadTool.layer.pipe(Layer.provide(ReadToolFileSystem.layer)),
|
||||
SkillTool.layer,
|
||||
TodoWriteTool.layer,
|
||||
WebFetchTool.layer,
|
||||
WebSearchTool.layer.pipe(Layer.provide(WebSearchTool.defaultConfigLayer)),
|
||||
WriteTool.layer,
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
name: "built-in-tools",
|
||||
layer: Layer.empty,
|
||||
|
||||
@@ -87,7 +87,7 @@ export const toModelOutput = (output: Output, oldString: string, newString: stri
|
||||
// TODO: Add snapshots / undo after design exists.
|
||||
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const mutation = yield* LocationMutation.Service
|
||||
|
||||
@@ -35,7 +35,7 @@ export const toModelOutput = (output: ModelOutput) => {
|
||||
}
|
||||
|
||||
/** Glob leaf that defaults its filesystem root to the active Location. */
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const ripgrep = yield* Ripgrep.Service
|
||||
|
||||
@@ -50,7 +50,7 @@ export const toModelOutput = (output: ModelOutput) => {
|
||||
}
|
||||
|
||||
/** Grep leaf that defaults its filesystem root to the active Location. */
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
|
||||
@@ -44,7 +44,7 @@ export const toModelOutput = (
|
||||
return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
|
||||
}
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const question = yield* QuestionV2.Service
|
||||
|
||||
@@ -351,7 +351,7 @@ export const list = Effect.fn("ReadTool.list")(function* (fs: FSUtil.Interface,
|
||||
return new ListPage({ entries: selected, truncated, ...(truncated ? { next: offset + selected.length } : {}) })
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
||||
@@ -27,7 +27,7 @@ const LocationInput = Schema.Struct({
|
||||
const Input = LocationInput
|
||||
const Output = Schema.Union([FileSystem.Content, ReadToolFileSystem.TextPage, ReadToolFileSystem.ListPage])
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const reader = yield* ReadToolFileSystem.Service
|
||||
|
||||
@@ -124,7 +124,7 @@ const registryLayer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Tools.Service,
|
||||
Service.use((registry) => Effect.succeed(Tools.Service.of({ register: registry.register }))),
|
||||
).pipe(Layer.provideMerge(registryLayer))
|
||||
@@ -134,11 +134,6 @@ function whollyDisabled(action: string, rules: PermissionV2.Ruleset) {
|
||||
return rule?.resource === "*" && rule.effect === "deny"
|
||||
}
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(ApplicationTools.layer),
|
||||
Layer.provide(ToolOutputStore.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
|
||||
@@ -54,7 +54,7 @@ export const toModelOutput = (skill: SkillV2.Info, files: ReadonlyArray<string>)
|
||||
const unableToLoad = (name: string, error?: unknown) =>
|
||||
new ToolFailure({ message: `Unable to load skill ${name}`, error })
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
|
||||
@@ -22,7 +22,7 @@ export type Output = typeof Output.Type
|
||||
|
||||
export const toModelOutput = (output: Output) => JSON.stringify(output.todos, null, 2)
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const todos = yield* SessionTodo.Service
|
||||
|
||||
@@ -115,7 +115,7 @@ const convert = (content: string, contentType: string, format: Format) => {
|
||||
return content
|
||||
}
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const http = yield* HttpClient.HttpClient
|
||||
|
||||
@@ -189,7 +189,7 @@ const Output = Schema.Struct({
|
||||
text: Schema.String,
|
||||
})
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const http = yield* HttpClient.HttpClient
|
||||
|
||||
@@ -44,7 +44,7 @@ export const toModelOutput = (output: Output) =>
|
||||
// TODO: Add snapshots / undo after design exists.
|
||||
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const mutation = yield* LocationMutation.Service
|
||||
|
||||
@@ -94,7 +94,7 @@ export namespace EffectFlock {
|
||||
|
||||
const isPathGone = (e: PlatformError) => e.reason._tag === "NotFound" || e.reason._tag === "Unknown"
|
||||
|
||||
export const layer: Layer.Layer<Service, never, Global.Service | FSUtil.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, Global.Service | FSUtil.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const global = yield* Global.Service
|
||||
@@ -280,6 +280,5 @@ export namespace EffectFlock {
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(Global.layer))
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Global.node, FSUtil.node] })
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ describe("Config", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("loads JSON and JSONC files from lowest to highest priority", () =>
|
||||
it.live("loads opencode JSON and JSONC files from lowest to highest priority", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
@@ -170,13 +170,9 @@ describe("Config", () => {
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(() =>
|
||||
Promise.all([
|
||||
fs.writeFile(
|
||||
path.join(tmp.path, "config.json"),
|
||||
JSON.stringify({ $schema: "base", providers: { base: provider } }),
|
||||
),
|
||||
fs.writeFile(
|
||||
path.join(tmp.path, "opencode.json"),
|
||||
JSON.stringify({ $schema: "middle", providers: { middle: provider } }),
|
||||
JSON.stringify({ $schema: "base", providers: { base: provider } }),
|
||||
),
|
||||
fs.writeFile(
|
||||
path.join(tmp.path, "opencode.jsonc"),
|
||||
@@ -192,12 +188,12 @@ describe("Config", () => {
|
||||
const config = yield* Config.Service
|
||||
const documents = (yield* config.entries()).filter((entry) => entry.type === "document")
|
||||
|
||||
expect(documents).toHaveLength(3)
|
||||
expect(documents.map((document) => document.type)).toEqual(["document", "document", "document"])
|
||||
expect(documents.map((document) => document.info.$schema)).toEqual(["base", "middle", "last"])
|
||||
expect(documents).toHaveLength(2)
|
||||
expect(documents.map((document) => document.type)).toEqual(["document", "document"])
|
||||
expect(documents.map((document) => document.info.$schema)).toEqual(["base", "last"])
|
||||
expect(documents[0]).toBeInstanceOf(Config.Document)
|
||||
expect(documents[0]?.path).toBe(path.join(tmp.path, "config.json"))
|
||||
expect(documents[2]?.info.providers?.last).toBeInstanceOf(ConfigProvider.Info)
|
||||
expect(documents[0]?.path).toBe(path.join(tmp.path, "opencode.json"))
|
||||
expect(documents[1]?.info.providers?.last).toBeInstanceOf(ConfigProvider.Info)
|
||||
|
||||
yield* Effect.promise(() =>
|
||||
fs.writeFile(path.join(tmp.path, "opencode.jsonc"), JSON.stringify({ $schema: "changed" })),
|
||||
@@ -206,7 +202,29 @@ describe("Config", () => {
|
||||
(yield* config.entries())
|
||||
.filter((entry) => entry.type === "document")
|
||||
.map((document) => document.info.$schema),
|
||||
).toEqual(["base", "middle", "last"])
|
||||
).toEqual(["base", "last"])
|
||||
}).pipe(Effect.provide(testLayer(tmp.path)))
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("does not load legacy config.json files", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
).pipe(
|
||||
Effect.flatMap((tmp) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(() =>
|
||||
fs.writeFile(path.join(tmp.path, "config.json"), JSON.stringify({ $schema: "legacy" })),
|
||||
)
|
||||
|
||||
return yield* Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
const documents = (yield* config.entries()).filter((entry) => entry.type === "document")
|
||||
|
||||
expect(documents).toHaveLength(0)
|
||||
}).pipe(Effect.provide(testLayer(tmp.path)))
|
||||
}),
|
||||
),
|
||||
@@ -648,7 +666,7 @@ describe("Config", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("ignores invalid files while loading valid config values", () =>
|
||||
it.live("ignores an invalid file while loading valid config values", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
@@ -657,9 +675,8 @@ describe("Config", () => {
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(() =>
|
||||
Promise.all([
|
||||
fs.writeFile(path.join(tmp.path, "config.json"), JSON.stringify({ $schema: "base" })),
|
||||
fs.writeFile(path.join(tmp.path, "opencode.json"), "{ invalid"),
|
||||
fs.writeFile(path.join(tmp.path, "opencode.jsonc"), JSON.stringify({ providers: { invalid: true } })),
|
||||
fs.writeFile(path.join(tmp.path, "opencode.json"), JSON.stringify({ $schema: "base" })),
|
||||
fs.writeFile(path.join(tmp.path, "opencode.jsonc"), "{ invalid"),
|
||||
]),
|
||||
)
|
||||
return yield* Effect.gen(function* () {
|
||||
@@ -728,7 +745,7 @@ describe("Config", () => {
|
||||
fs.writeFile(path.join(global, "opencode.json"), JSON.stringify({ $schema: "global" })),
|
||||
fs.writeFile(path.join(root, "opencode.json"), JSON.stringify({ $schema: "root" })),
|
||||
fs.writeFile(path.join(parent, "opencode.jsonc"), JSON.stringify({ $schema: "parent" })),
|
||||
fs.writeFile(path.join(directory, "config.json"), JSON.stringify({ $schema: "directory" })),
|
||||
fs.writeFile(path.join(directory, "opencode.json"), JSON.stringify({ $schema: "directory" })),
|
||||
fs.writeFile(path.join(root, ".opencode", "opencode.json"), JSON.stringify({ $schema: "root-dot" })),
|
||||
fs.writeFile(
|
||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("ConfigExternalPlugin", () => {
|
||||
const location = yield* Location.Service
|
||||
const npm = yield* Npm.Service
|
||||
const host = yield* PluginHost.make(plugins)
|
||||
const document = path.join(import.meta.dir, "config.json")
|
||||
const document = path.join(import.meta.dir, "opencode.json")
|
||||
|
||||
yield* ConfigExternalPlugin.Plugin.effect(host).pipe(
|
||||
Effect.provideService(PluginV2.Service, plugins),
|
||||
@@ -82,7 +82,7 @@ describe("ConfigExternalPlugin", () => {
|
||||
Effect.succeed([
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
path: path.join(import.meta.dir, "config.json"),
|
||||
path: path.join(import.meta.dir, "opencode.json"),
|
||||
info: decode({
|
||||
plugins: [
|
||||
{
|
||||
@@ -125,7 +125,7 @@ describe("ConfigExternalPlugin", () => {
|
||||
Effect.succeed([
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
path: path.join(import.meta.dir, "config.json"),
|
||||
path: path.join(import.meta.dir, "opencode.json"),
|
||||
info: decode({
|
||||
plugins: [
|
||||
"../plugin/fixtures/missing-plugin.ts",
|
||||
|
||||
@@ -238,7 +238,7 @@ const inspectFakeProvider = Effect.gen(function* () {
|
||||
// Provide the LLM runtime and the HTTP request executor once. Keep one path
|
||||
// enabled at a time so the tutorial can demonstrate generate, prepare, stream,
|
||||
// or tool-loop behavior without spending tokens on every example.
|
||||
const requestExecutorLayer = RequestExecutor.defaultLayer
|
||||
const requestExecutorLayer = RequestExecutor.fetchLayer
|
||||
const llmDeps = Layer.mergeAll(requestExecutorLayer, WebSocketExecutor.layer)
|
||||
const llmClientLayer = LLMClient.layer.pipe(Layer.provide(llmDeps))
|
||||
|
||||
|
||||
@@ -380,6 +380,6 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient> = Layer.e
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FetchHttpClient.layer))
|
||||
export const fetchLayer = layer.pipe(Layer.provide(FetchHttpClient.layer))
|
||||
|
||||
export * as RequestExecutor from "./executor"
|
||||
|
||||
@@ -240,11 +240,20 @@ export const RunCommand = effectCmd({
|
||||
default: false,
|
||||
})
|
||||
.option("auto", {
|
||||
alias: ["yolo", "dangerously-skip-permissions"],
|
||||
type: "boolean",
|
||||
describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
|
||||
default: false,
|
||||
})
|
||||
.option("yolo", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("dangerously-skip-permissions", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("demo", {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
@@ -262,6 +271,7 @@ export const RunCommand = effectCmd({
|
||||
yield* Effect.promise(async () => {
|
||||
const rawMessage = [...args.message, ...(args["--"] || [])].join(" ")
|
||||
const interactive = args.mini
|
||||
const auto = args.auto || args.yolo || args["dangerously-skip-permissions"]
|
||||
const thinking = interactive ? (args.thinking ?? true) : (args.thinking ?? false)
|
||||
const die = (message: string): never => {
|
||||
UI.error(message)
|
||||
@@ -787,7 +797,7 @@ export const RunCommand = effectCmd({
|
||||
const permission = event.properties
|
||||
if (permission.sessionID !== sessionID) continue
|
||||
|
||||
if (args.auto) {
|
||||
if (auto) {
|
||||
await client.permission.reply({
|
||||
requestID: permission.id,
|
||||
reply: "once",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// variant and the persisted file.
|
||||
import path from "path"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
@@ -135,7 +136,7 @@ function state(value: unknown): ModelState {
|
||||
}
|
||||
}
|
||||
|
||||
function createLayer(fs = FSUtil.defaultLayer) {
|
||||
function createLayer(fs = AppNodeBuilder.build(FSUtil.node)) {
|
||||
return Layer.fresh(
|
||||
Layer.effect(
|
||||
Service,
|
||||
@@ -196,7 +197,7 @@ function createLayer(fs = FSUtil.defaultLayer) {
|
||||
}
|
||||
|
||||
/** @internal Exported for testing. */
|
||||
export function createVariantRuntime(fs = FSUtil.defaultLayer): VariantRuntime {
|
||||
export function createVariantRuntime(fs = AppNodeBuilder.build(FSUtil.node)): VariantRuntime {
|
||||
const runtime = makeRuntime(Service, createLayer(fs))
|
||||
return {
|
||||
resolveSavedVariant: (model) => runtime.runPromise((svc) => svc.resolveSavedVariant(model)).catch(() => undefined),
|
||||
|
||||
@@ -106,11 +106,20 @@ export const TuiThreadCommand = cmd({
|
||||
describe: "agent to use",
|
||||
})
|
||||
.option("auto", {
|
||||
alias: ["yolo", "dangerously-skip-permissions"],
|
||||
type: "boolean",
|
||||
describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
|
||||
default: false,
|
||||
})
|
||||
.option("yolo", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("dangerously-skip-permissions", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("mini", {
|
||||
type: "boolean",
|
||||
describe: "start the minimal interactive interface",
|
||||
@@ -278,7 +287,7 @@ export const TuiThreadCommand = cmd({
|
||||
model: args.model,
|
||||
prompt,
|
||||
fork: args.fork,
|
||||
auto: args.auto,
|
||||
auto: args.auto || args.yolo || args["dangerously-skip-permissions"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { run as runTui, type TuiInput } from "@opencode-ai/tui"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export function run(input: TuiInput) {
|
||||
return runTui(input).pipe(Effect.provide(Global.defaultLayer))
|
||||
return runTui(input).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ import { waitEvent } from "./util"
|
||||
import { WorkspaceRef } from "@/effect/instance-ref"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime"
|
||||
import { AppNodeBuilderV1 } from "@/effect/app-node-builder-v1"
|
||||
import { WorkspaceEvent } from "@opencode-ai/schema/workspace-event"
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
@@ -601,11 +601,7 @@ const layer = Layer.effect(
|
||||
}),
|
||||
fallback: "",
|
||||
response: "text",
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]),
|
||||
),
|
||||
)
|
||||
}).pipe(Effect.provide(AppNodeBuilderV1.build(InstanceStore.node)))
|
||||
: ""
|
||||
|
||||
if (sourcePatch) {
|
||||
@@ -621,11 +617,7 @@ const layer = Layer.effect(
|
||||
body: HttpBody.jsonUnsafe({ patch: sourcePatch }),
|
||||
}),
|
||||
fallback: { applied: false },
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]]),
|
||||
),
|
||||
)
|
||||
}).pipe(Effect.provide(AppNodeBuilderV1.build(InstanceStore.node)))
|
||||
}
|
||||
|
||||
if (input.workspaceID === null) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
|
||||
const bootstrapReplacement = [InstanceStore.bootstrapNode, InstanceBootstrap.node] as const
|
||||
|
||||
export function build<A, E>(root: LayerNode.Node<A, E, any>, replacements: LayerNode.Replacements = []) {
|
||||
return AppNodeBuilder.build(root, replacements.concat([bootstrapReplacement]))
|
||||
}
|
||||
|
||||
export * as AppNodeBuilderV1 from "./app-node-builder-v1"
|
||||
@@ -38,7 +38,6 @@ import { Command } from "@/command"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Format } from "@/format"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { Project } from "@/project/project"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
@@ -53,10 +52,10 @@ import { BackgroundJob } from "@/background/job"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { AppNodeBuilderV1 } from "./app-node-builder-v1"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
|
||||
export const AppLayer = AppNodeBuilder.build(
|
||||
export const AppLayer = AppNodeBuilderV1.build(
|
||||
LayerNode.group([
|
||||
Npm.node,
|
||||
FSUtil.node,
|
||||
@@ -107,8 +106,7 @@ export const AppLayer = AppNodeBuilder.build(
|
||||
ShareNext.node,
|
||||
SessionShare.node,
|
||||
]),
|
||||
[[InstanceStore.bootstrapNode, InstanceBootstrap.node]],
|
||||
).pipe(Layer.provideMerge(AppNodeBuilder.build(Ripgrep.node)), Layer.provideMerge(Observability.layer))
|
||||
).pipe(Layer.provideMerge(AppNodeBuilderV1.build(Ripgrep.node)), Layer.provideMerge(Observability.layer))
|
||||
|
||||
const rt = ManagedRuntime.make(AppLayer, { memoMap })
|
||||
type Runtime = Pick<typeof rt, "runSync" | "runPromise" | "runPromiseExit" | "runFork" | "runCallback" | "dispose">
|
||||
|
||||
@@ -14,9 +14,9 @@ export type Shape<Fields extends ConfigMap> = {
|
||||
*/
|
||||
export type ServiceClass<Self, Id extends string, Service> = Context.ServiceClass<Self, Id, Service> & {
|
||||
/** Provide already-parsed config, useful in tests. */
|
||||
readonly layer: (input: Service) => Layer.Layer<Self>
|
||||
readonly configLayer: (input: Service) => Layer.Layer<Self>
|
||||
/** Parse config once from the active Effect ConfigProvider and provide the service. */
|
||||
readonly defaultLayer: Layer.Layer<Self, Config.ConfigError>
|
||||
readonly layer: Layer.Layer<Self, Config.ConfigError>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,19 +35,19 @@ export type ServiceClass<Self, Id extends string, Service> = Context.ServiceClas
|
||||
* },
|
||||
* ) {}
|
||||
*
|
||||
* const live = ServerAuthConfig.defaultLayer
|
||||
* const test = ServerAuthConfig.layer({ password: Option.some("secret"), username: "kit" })
|
||||
* const live = ServerAuthConfig.layer
|
||||
* const test = ServerAuthConfig.configLayer({ password: Option.some("secret"), username: "kit" })
|
||||
* ```
|
||||
*/
|
||||
export const Service =
|
||||
<Self>() =>
|
||||
<const Id extends string, const Fields extends ConfigMap>(id: Id, fields: Fields) => {
|
||||
class ConfigTag extends Context.Service<Self, Shape<Fields>>()(id) {
|
||||
static layer(input: Shape<Fields>) {
|
||||
static configLayer(input: Shape<Fields>) {
|
||||
return Layer.succeed(this, this.of(input))
|
||||
}
|
||||
|
||||
static get defaultLayer() {
|
||||
static get layer() {
|
||||
const tag = this
|
||||
return Layer.effect(
|
||||
tag,
|
||||
|
||||
@@ -57,7 +57,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
|
||||
|
||||
export type Info = Context.Service.Shape<typeof Service>
|
||||
|
||||
const emptyConfigLayer = Service.defaultLayer.pipe(
|
||||
const emptyConfigLayer = Service.layer.pipe(
|
||||
Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({}))),
|
||||
Layer.orDie,
|
||||
)
|
||||
@@ -71,7 +71,7 @@ export const layer = (overrides: Partial<Info> = {}) =>
|
||||
}),
|
||||
).pipe(Layer.provide(emptyConfigLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: Service.defaultLayer.pipe(Layer.orDie), deps: [] })
|
||||
export const node = LayerNode.make({ service: Service, layer: Service.layer.pipe(Layer.orDie), deps: [] })
|
||||
|
||||
export * as RuntimeFlags from "./runtime-flags"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
@@ -22,7 +22,6 @@ import { McpAuth } from "@/mcp/auth"
|
||||
import { Permission } from "@/permission"
|
||||
import { Plugin } from "@/plugin"
|
||||
import { PluginPtyEnvironment } from "@/plugin/pty-environment"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { Project } from "@/project/project"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
@@ -52,6 +51,7 @@ import { Worktree } from "@/worktree"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { MoveSession } from "@opencode-ai/core/control-plane/move-session"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilderV1 } from "@/effect/app-node-builder-v1"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
@@ -64,6 +64,7 @@ import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@opencode-ai/server/cors"
|
||||
@@ -99,7 +100,7 @@ import { sessionHandlers } from "./handlers/session"
|
||||
import { syncHandlers } from "./handlers/sync"
|
||||
import { tuiHandlers } from "./handlers/tui"
|
||||
import { handlers } from "@opencode-ai/server/handlers"
|
||||
import { locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { buildLocationServiceMap, LocationServiceMap } from "@opencode-ai/core/location-services"
|
||||
import { layer as locationLayer } from "@opencode-ai/server/location"
|
||||
import { sessionLocationLayer } from "@opencode-ai/server/middleware/session-location"
|
||||
import { PtyEnvironment } from "@opencode-ai/server/pty-environment"
|
||||
@@ -132,10 +133,10 @@ const cors = (corsOptions?: CorsOptions) =>
|
||||
// - ptyConnectApiRoutes: typed WebSocket upgrade route with ticket-aware auth.
|
||||
// - instanceApiRoutes: remaining typed instance routes.
|
||||
// - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it.
|
||||
const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const serverHttpApiAuthLayer = serverAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const serverHttpApiAuthLayer = serverAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const workspaceRoutingLive = workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal))
|
||||
const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(
|
||||
Layer.provide([controlHandlers, controlPlaneHandlers, globalHandlers]),
|
||||
@@ -270,6 +271,8 @@ const app = LayerNode.group([
|
||||
export function createRoutes(
|
||||
corsOptions?: CorsOptions,
|
||||
): Layer.Layer<never, EffectConfig.ConfigError, RouteRequirements> {
|
||||
const locationServiceMapV2 = buildLocationServiceMap()
|
||||
|
||||
return Layer.mergeAll(
|
||||
rootApiRoutes,
|
||||
eventApiRoutes,
|
||||
@@ -285,7 +288,7 @@ export function createRoutes(
|
||||
corsVaryFix,
|
||||
fenceLayer,
|
||||
cors(corsOptions),
|
||||
MoveSession.defaultLayer,
|
||||
AppNodeBuilderV1.build(MoveSession.node, [[LocationServiceMap.node, locationServiceMapV2]]),
|
||||
HttpServer.layerServices,
|
||||
]),
|
||||
Layer.provide(Layer.succeed(CorsConfig)(corsOptions)),
|
||||
@@ -295,14 +298,14 @@ export function createRoutes(
|
||||
Layer.provide(locationLayer),
|
||||
Layer.provide(PtyEnvironment.layer),
|
||||
Layer.provide(
|
||||
SessionV2.defaultLayer.pipe(
|
||||
Layer.provide(SessionExecutionLocal.defaultLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
),
|
||||
AppNodeBuilderV1.build(SessionV2.node, [
|
||||
[LocationServiceMap.node, locationServiceMapV2],
|
||||
[SessionExecution.node, SessionExecutionLocal.node],
|
||||
]),
|
||||
),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(locationServiceMapV2),
|
||||
|
||||
Layer.provide(LayerNode.compile(app, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
|
||||
Layer.provide(AppNodeBuilderV1.build(app)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user