From 0fc33e2a069d0fc9b77efe8e71ef6ee629278537 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:35:50 +0800 Subject: [PATCH 001/157] feat(app): /new-session route for new design (#31457) --- packages/app/src/app.tsx | 92 +++++++++++++++++-- packages/app/src/components/file-tree.test.ts | 2 + packages/app/src/components/prompt-input.tsx | 15 ++- .../components/prompt-input/submit.test.ts | 12 +++ .../app/src/components/prompt-input/submit.ts | 16 +++- packages/app/src/components/titlebar.tsx | 85 +++++++++++++++-- packages/app/src/context/comments.test.ts | 2 + packages/app/src/context/layout.tsx | 11 ++- packages/app/src/context/tabs.tsx | 25 +++-- packages/app/src/context/terminal.test.ts | 2 + packages/app/src/pages/directory-layout.tsx | 4 +- packages/app/src/pages/new-session.tsx | 78 ++++++++++++++++ packages/app/src/pages/session.tsx | 19 +--- .../pages/session/new-session-layout.test.ts | 14 --- .../src/pages/session/new-session-layout.ts | 4 - 15 files changed, 320 insertions(+), 61 deletions(-) create mode 100644 packages/app/src/pages/new-session.tsx delete mode 100644 packages/app/src/pages/session/new-session-layout.test.ts diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index ad9aa3543f..5bf18b657a 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -9,7 +9,7 @@ import { Font } from "@opencode-ai/ui/font" import { Splash } from "@opencode-ai/ui/logo" import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { MetaProvider } from "@solidjs/meta" -import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" +import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { Effect } from "effect" import { @@ -43,25 +43,88 @@ 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 } from "@/context/tabs" +import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs" +import { SDKProvider, useSDK } from "@/context/sdk" import { WslServersProvider } from "@/wsl/context" -import DirectoryLayout from "@/pages/directory-layout" +import DirectoryLayout, { DirectoryDataProvider } from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" import { useCheckServerHealth } from "./utils/server-health" const HomeRoute = lazy(() => import("@/pages/home")) const Session = lazy(() => import("@/pages/session")) +const NewSession = lazy(() => import("@/pages/new-session")) const SessionRoute = Object.assign( - () => ( - - - - ), + () => { + const settings = useSettings() + const params = useParams() + const [search] = useSearchParams<{ draftId?: string; prompt?: string }>() + const sdk = useSDK() + const server = useServer() + const tabs = useTabs() + + // When the new layout is enabled, the legacy new-session route (/:dir/session with no id) + // is replaced by a draft at /new-session?draftId=… + createEffect(() => { + if (!settings.general.newLayoutDesigns()) return + if (params.id || search.draftId) return + if (!tabs.ready() || !sdk.directory) return + tabs.newDraft({ server: server.key, directory: sdk.directory }, search.prompt) + }) + + return ( + + + + ) + }, { preload: Session.preload }, ) +function DraftRoute() { + const [search] = useSearchParams<{ draftId?: string }>() + const tabs = useTabs() + return ( + + }> + {(draftID) => } + + + ) +} + +function ResolvedDraftRoute(props: { draftID: string }) { + const server = useServer() + const tabs = useTabs() + const draft = createMemo(() => + tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === props.draftID), + ) + + createEffect(() => { + const current = draft() + if (current && current.server !== server.key) server.setActive(current.server) + }) + + // Key on the directory so retargeting the draft's project re-instantiates the + // SDK/data providers for the new directory while keeping the same draft id. + const directory = () => draft()?.directory + + return ( + + {(dir) => ( + + + + + + + + )} + + ) +} + function UiI18nBridge(props: ParentProps) { const language = useLanguage() return {props.children} @@ -141,6 +204,18 @@ function SessionProviders(props: ParentProps) { ) } +// 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) { + return ( + + + {props.children} + + + ) +} + function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) { return ( @@ -335,6 +410,7 @@ export function AppInterface(props: { )} > + } /> diff --git a/packages/app/src/components/file-tree.test.ts b/packages/app/src/components/file-tree.test.ts index 29e20b4807..20bffc41a3 100644 --- a/packages/app/src/components/file-tree.test.ts +++ b/packages/app/src/components/file-tree.test.ts @@ -8,6 +8,8 @@ beforeAll(async () => { mock.module("@solidjs/router", () => ({ useNavigate: () => () => undefined, useParams: () => ({}), + useLocation: () => ({}), + useSearchParams: () => [{}, () => undefined], })) mock.module("@/context/file", () => ({ useFile: () => ({ diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index bdf55fee05..835bc23819 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -31,9 +31,10 @@ import { FileAttachmentPart, } from "@/context/prompt" import { useLayout } from "@/context/layout" -import { useNavigate } from "@solidjs/router" +import { useNavigate, useSearchParams } from "@solidjs/router" import { useSDK } from "@/context/sdk" import { useServer } from "@/context/server" +import { useTabs } from "@/context/tabs" import { useSync } from "@/context/sync" import { useComments } from "@/context/comments" import { Button } from "@opencode-ai/ui/button" @@ -144,6 +145,8 @@ export const PromptInput: Component = (props) => { const platform = usePlatform() const pickDirectory = useDirectoryPicker() const settings = useSettings() + const tabsStore = useTabs() + const [search] = useSearchParams<{ draftId?: string }>() const { params, tabs, view } = useSessionLayout() let editorRef!: HTMLDivElement let fileInputRef: HTMLInputElement | undefined @@ -1398,6 +1401,16 @@ export const PromptInput: Component = (props) => { } layout.projects.open(worktree) server.projects.touch(worktree) + + // On the draft route, retarget the existing draft in place so we keep the same + // draft id (and its tab/prompt) instead of spawning a new draft for the new directory. + const draftID = search.draftId + if (draftID) { + tabsStore.updateDraft(draftID, { server: server.key, directory: worktree }) + restoreFocus() + return + } + navigate(`/${base64Encode(worktree)}/session`) } const addProject = () => { diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index 092731a9ea..3e5f0ff1a4 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -61,6 +61,8 @@ beforeAll(async () => { mock.module("@solidjs/router", () => ({ useNavigate: () => () => undefined, useParams: () => params, + useLocation: () => ({}), + useSearchParams: () => [{}, () => undefined], })) mock.module("@opencode-ai/sdk/v2/client", () => ({ @@ -103,6 +105,16 @@ beforeAll(async () => { }), })) + mock.module("@/context/server", () => ({ + useServer: () => ({ key: "server-key" }), + })) + + mock.module("@/context/tabs", () => ({ + useTabs: () => ({ + promoteDraft: () => undefined, + }), + })) + mock.module("@/context/prompt", () => ({ usePrompt: () => ({ current: () => promptValue, diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 024bdd7ae2..bd4d985088 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -2,9 +2,11 @@ import type { Message, Session } from "@opencode-ai/sdk/v2/client" import { showToast } from "@/utils/toast" import { base64Encode } from "@opencode-ai/core/util/encode" import { Binary } from "@opencode-ai/core/util/binary" -import { useNavigate, useParams } from "@solidjs/router" +import { useNavigate, useParams, useSearchParams } from "@solidjs/router" import { batch, type Accessor } from "solid-js" import type { FileSelection } from "@/context/file" +import { useServer } from "@/context/server" +import { useTabs } from "@/context/tabs" import { useServerSync } from "@/context/server-sync" import { useLanguage } from "@/context/language" import { useLayout } from "@/context/layout" @@ -213,6 +215,9 @@ export function createPromptSubmit(input: PromptSubmitInput) { const layout = useLayout() const language = useLanguage() const params = useParams() + const [search] = useSearchParams<{ draftId?: string }>() + const server = useServer() + const tabs = useTabs() const pendingKey = (sessionID: string) => ScopedKey.from(sdk.scope, sessionID) const errorMessage = (err: unknown) => { @@ -381,7 +386,14 @@ export function createPromptSubmit(input: PromptSubmitInput) { if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory) local.session.promote(sessionDirectory, session.id) layout.handoff.setTabs(base64Encode(sessionDirectory), session.id) - navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`) + const draftID = search.draftId + if (draftID) + tabs.promoteDraft(draftID, { + server: server.key, + dirBase64: base64Encode(sessionDirectory), + sessionId: session.id, + }) + else navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`) } } if (!session) { diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 019f4f4708..a4fcde5aac 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -280,7 +280,8 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { const matchRoute = (route: LayoutRoute) => { if (route.type === "home") return - if (route.type === "dir-new-sesssion") { + if (route.type === "draft") { + return tabsStore.find((item) => item.type === "draft" && item.draftID === route.draftID) } if (route.type === "session") { const main = tabsStore.find( @@ -447,13 +448,33 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { refreshTabsAreOverflowing() }) - if (tab.type !== "session") return null + const divider = () => + i() !== 0 && ( +
+ ) + + if (tab.type === "draft") { + return ( + <> + {divider()} + { + navigateTab(tab) + ref.scrollIntoView({ behavior: "instant" }) + }} + onClose={() => tabsStoreActions.removeTab(i())} + /> + + ) + } return ( <> - {i() !== 0 && ( -
- )} + {divider()} {(session) => { - console.log({ session: session() }) const project = createMemo(() => projectForSession(session(), serverCtx()?.projects.list() ?? [])) return ( @@ -853,6 +873,59 @@ function ProjectTabAvatar(props: { ) } +function DraftTabItem(props: { + ref?: HTMLDivElement + href: string + title: string + active?: boolean + onNavigate: () => void + onClose: () => void +}) { + const closeTab = (event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + props.onClose() + } + return ( +
{ + if (event.button !== 1) return + closeTab(event) + }} + > + { + event.preventDefault() + props.onNavigate() + }} + class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden text-[13px] font-medium leading-5 text-v2-text-text-faint group-data-[active='true']:text-[var(--v2-text-text-base)]" + > + + + + {props.title} + +
+ { + event.preventDefault() + event.stopPropagation() + }} + onClick={closeTab} + icon={} + aria-label="Close tab" + /> +
+
+ ) +} + function NewSessionTabItem(props: { ref?: HTMLDivElement; href: string; title: string; onClose: () => void }) { const closeTab = (event: MouseEvent) => { event.preventDefault() diff --git a/packages/app/src/context/comments.test.ts b/packages/app/src/context/comments.test.ts index 82fa170f2f..5050409a81 100644 --- a/packages/app/src/context/comments.test.ts +++ b/packages/app/src/context/comments.test.ts @@ -8,6 +8,8 @@ beforeAll(async () => { mock.module("@solidjs/router", () => ({ useNavigate: () => () => undefined, useParams: () => ({}), + useLocation: () => ({}), + useSearchParams: () => [{}, () => undefined], })) mock.module("@opencode-ai/ui/context", () => ({ createSimpleContext: () => ({ diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 49d04df546..f9e954b98b 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -77,6 +77,7 @@ export type ReviewDiffStyle = "unified" | "split" export type LayoutRoute = | { type: "home" } + | { type: "draft"; draftID: string; server?: ServerConnection.Key } | { type: "dir-new-sesssion"; dir: string; dirBase64: string; server?: ServerConnection.Key } | { type: "session"; dir: string; dirBase64: string; sessionId: string; server?: ServerConnection.Key } @@ -120,10 +121,16 @@ const normalizeStoredSessionTabs = (key: string, tabs: SessionTabs) => { } } -const currentRoute = (pathname: string): LayoutRoute => { +const currentRoute = (pathname: string, search: string): LayoutRoute => { const parts = pathname.split("/").filter(Boolean) if (parts.length === 0) return { type: "home" } + if (parts[0] === "new-session") { + const draftID = new URLSearchParams(search).get("draftId") + if (!draftID) return { type: "home" } + return { type: "draft", draftID } + } + const dirBase64 = parts[0] const dir = decode64(dirBase64) if (!dir) return { type: "home" } @@ -145,7 +152,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const platform = usePlatform() const location = useLocation() const route = createMemo(() => { - const value = currentRoute(location.pathname) + const value = currentRoute(location.pathname, location.search) if (value.type === "home") return value return { ...value, server: server.key } }) diff --git a/packages/app/src/context/tabs.tsx b/packages/app/src/context/tabs.tsx index cc43bac03b..7f6274cf99 100644 --- a/packages/app/src/context/tabs.tsx +++ b/packages/app/src/context/tabs.tsx @@ -5,7 +5,7 @@ import { createStore, produce } from "solid-js/store" import { Persist, persisted, removePersisted, draftPersistedKeys } from "@/utils/persist" import { ServerConnection, useServer } from "./server" import { createEffect, startTransition } from "solid-js" -import { useNavigate, useParams } from "@solidjs/router" +import { useLocation, useNavigate, useParams } from "@solidjs/router" import { usePlatform } from "./platform" import { uuid } from "@/utils/uuid" import { SessionTabsRemovedDetail } from "@/components/titlebar-session-events" @@ -65,6 +65,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({ const params = useParams() const navigate = useNavigate() + const location = useLocation() const closing = new Set() @@ -123,14 +124,20 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({ ) }, promoteDraft(draftID: string, session: Omit) { - const active = `${location.pathname}${location.search}` === draftHref(draftID) - setStore( - produce((tabs) => { - const index = tabs.findIndex((tab) => tab.type === "draft" && tab.draftID === draftID) - if (index !== -1) tabs[index] = { type: "session", ...session } - }), - ) - if (active) navigateTab({ type: "session", ...session }) + // We're viewing this draft when /new-session?draftId=… points at it. Promoting + // replaces the draft tab with a session tab, so the draft route would stop resolving + // and fall back home. Navigate to the new session first so we leave /new-session + // before the draft is removed from the store. + const active = location.pathname === "/new-session" && location.query.draftId === draftID + startTransition(() => { + setStore( + produce((tabs) => { + const index = tabs.findIndex((tab) => tab.type === "draft" && tab.draftID === draftID) + if (index !== -1) tabs[index] = { type: "session", ...session } + }), + ) + if (active) navigateTab({ type: "session", ...session }) + }) removeDraftPersisted(draftID) }, removeTab: (index: number) => { diff --git a/packages/app/src/context/terminal.test.ts b/packages/app/src/context/terminal.test.ts index 4f16953c79..25ef35dd41 100644 --- a/packages/app/src/context/terminal.test.ts +++ b/packages/app/src/context/terminal.test.ts @@ -9,6 +9,8 @@ beforeAll(async () => { mock.module("@solidjs/router", () => ({ useNavigate: () => () => undefined, useParams: () => ({}), + useLocation: () => ({}), + useSearchParams: () => [{}, () => undefined], })) mock.module("@opencode-ai/ui/context", () => ({ createSimpleContext: () => ({ diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index 2d0dfd81dc..e03d5c206f 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -10,7 +10,7 @@ import { useSync } from "@/context/sync" import { decode64 } from "@/utils/base64" import { Schema } from "effect" -function DirectoryDataProvider(props: ParentProps<{ directory: string }>) { +export function DirectoryDataProvider(props: ParentProps<{ directory: string; draftID?: string }>) { const location = useLocation() const navigate = useNavigate() const params = useParams() @@ -18,6 +18,8 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) { const slug = createMemo(() => base64Encode(props.directory)) createEffect(() => { + // A draft lives at /new-session?draftId=… and has no directory segment to normalize. + if (props.draftID) return const next = sync.data.path.directory if (!next || next === props.directory) return const path = location.pathname.slice(slug().length + 1) diff --git a/packages/app/src/pages/new-session.tsx b/packages/app/src/pages/new-session.tsx new file mode 100644 index 0000000000..16f30af378 --- /dev/null +++ b/packages/app/src/pages/new-session.tsx @@ -0,0 +1,78 @@ +import { createEffect, createMemo, onMount, untrack } from "solid-js" +import { createStore } from "solid-js/store" +import { useSearchParams } from "@solidjs/router" +import { NewSessionDesignView } from "@/components/session" +import { useComments } from "@/context/comments" +import { usePrompt } from "@/context/prompt" +import { useSDK } from "@/context/sdk" +import { useSync } from "@/context/sync" +import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer" + +/** + * The `/new-session` draft page. Unlike `session.tsx`, this only renders the prompt + * composer for a brand-new session — no terminal, review pane, file tree, or message + * timeline. Submitting promotes the draft into a real session (see prompt-input/submit). + */ +export default function NewSessionPage() { + const prompt = usePrompt() + const sdk = useSDK() + const sync = useSync() + const comments = useComments() + const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>() + + let inputRef: HTMLDivElement | undefined + + const composer = createSessionComposerState() + + const [store, setStore] = createStore({ + worktree: "main", + }) + + const newSessionWorktree = createMemo(() => { + if (store.worktree === "create") return "create" + const project = sync.project + if (project && sdk.directory !== project.worktree) return sdk.directory + return "main" + }) + + createEffect(() => { + if (!prompt.ready()) return + untrack(() => { + const text = searchParams.prompt + if (!text) return + prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length) + setSearchParams({ ...searchParams, prompt: undefined }) + }) + }) + + onMount(() => { + requestAnimationFrame(() => inputRef?.focus()) + }) + + return ( +
+
+
+
+ + { + inputRef = el + }} + newSessionWorktree={newSessionWorktree()} + onNewSessionWorktreeReset={() => setStore("worktree", "main")} + onSubmit={() => comments.clear()} + onResponseSubmit={() => {}} + setPromptDockRef={() => {}} + /> + +
+
+
+
+ ) +} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index cc80a27031..f7bcc93bdc 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -31,7 +31,7 @@ import { Button } from "@opencode-ai/ui/button" import { showToast } from "@/utils/toast" import { checksum } from "@opencode-ai/core/util/encode" import { useLocation, useSearchParams } from "@solidjs/router" -import { NewSessionDesignView, NewSessionView, SessionHeader } from "@/components/session" +import { NewSessionView, SessionHeader } from "@/components/session" import { useComments } from "@/context/comments" import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch" import { useServerSync } from "@/context/server-sync" @@ -63,7 +63,6 @@ import { SessionSidePanel } from "@/pages/session/session-side-panel" import { TerminalPanel } from "@/pages/session/terminal-panel" import { useSessionCommands } from "@/pages/session/use-session-commands" import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll" -import { shouldUseV2NewSessionPage } from "@/pages/session/new-session-layout" import { Identifier } from "@/utils/id" import { diffs as list } from "@/utils/diffs" import { Persist, persisted } from "@/utils/persist" @@ -271,13 +270,10 @@ export default function Page() { const isDesktop = createMediaQuery("(min-width: 768px)") const size = createSizing() - const isV2NewSessionPage = () => - shouldUseV2NewSessionPage({ newLayoutDesigns: newSessionDesign(), sessionID: params.id }) - const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened() && !isV2NewSessionPage()) + const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened()) const desktopFileTreeOpen = createMemo( () => isDesktop() && - !isV2NewSessionPage() && shouldShowFileTree({ desktopV2: platform.platform === "desktop" && settings.general.newLayoutDesigns(), showFileTree: settings.general.showFileTree(), @@ -1757,10 +1753,9 @@ export default function Page() {
- }> - {composerRegion("inline")} - +
diff --git a/packages/app/src/pages/session/new-session-layout.test.ts b/packages/app/src/pages/session/new-session-layout.test.ts deleted file mode 100644 index 436e0a59c7..0000000000 --- a/packages/app/src/pages/session/new-session-layout.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { shouldUseV2NewSessionPage } from "./new-session-layout" - -describe("shouldUseV2NewSessionPage", () => { - test("keeps disabled pages on the legacy layout", () => { - expect(shouldUseV2NewSessionPage({ newLayoutDesigns: false, sessionID: "ses_123" })).toBe(false) - expect(shouldUseV2NewSessionPage({ newLayoutDesigns: false })).toBe(false) - }) - - test("uses the v2 layout only for enabled new-session pages", () => { - expect(shouldUseV2NewSessionPage({ newLayoutDesigns: true })).toBe(true) - expect(shouldUseV2NewSessionPage({ newLayoutDesigns: true, sessionID: "ses_123" })).toBe(false) - }) -}) diff --git a/packages/app/src/pages/session/new-session-layout.ts b/packages/app/src/pages/session/new-session-layout.ts index 7429c7c7e8..edd953ed2d 100644 --- a/packages/app/src/pages/session/new-session-layout.ts +++ b/packages/app/src/pages/session/new-session-layout.ts @@ -1,6 +1,2 @@ /** Inline new-session content width — keep in sync with session composer `placement === "inline"`. */ export const NEW_SESSION_CONTENT_WIDTH = "w-full max-w-[720px] px-0" - -export function shouldUseV2NewSessionPage(input: { newLayoutDesigns: boolean; sessionID?: string }) { - return input.newLayoutDesigns && !input.sessionID -} From 8a2cfc00c93afc32a79979b7a928bc55d6483934 Mon Sep 17 00:00:00 2001 From: Dax Date: Wed, 10 Jun 2026 00:08:26 -0400 Subject: [PATCH 002/157] feat(core): add project reference guidance (#31601) --- .opencode/opencode.jsonc | 11 +- packages/core/src/config/plugin/reference.ts | 4 + packages/core/src/config/reference.ts | 4 + packages/core/src/location-layer.ts | 24 ++- packages/core/src/location.ts | 7 +- .../src/plugin/skill/customize-opencode.md | 46 +++++ packages/core/src/reference.ts | 40 ++++- packages/core/src/reference/guidance.ts | 69 ++++++++ packages/core/src/ripgrep.ts | 8 +- packages/core/src/session/runner/llm.ts | 8 +- packages/core/src/v1/config/config.ts | 4 +- packages/core/src/v1/config/migrate.ts | 3 +- packages/core/src/v1/config/reference.ts | 24 --- packages/core/test/config/config.test.ts | 8 +- packages/core/test/location-layer.test.ts | 18 +- packages/core/test/reference-guidance.test.ts | 77 +++++++++ packages/core/test/reference.test.ts | 39 ++++- packages/core/test/ripgrep.test.ts | 26 ++- .../core/test/session-runner-recorded.test.ts | 3 + packages/core/test/session-runner.test.ts | 3 + packages/opencode/src/agent/agent.ts | 24 ++- packages/opencode/src/cli/cmd/debug/file.ts | 3 +- packages/opencode/src/cli/cmd/debug/v2.ts | 9 +- .../routes/instance/httpapi/handlers/file.ts | 4 +- .../routes/instance/httpapi/handlers/pty.ts | 13 +- packages/opencode/src/session/system.ts | 36 +++- packages/opencode/test/agent/agent.test.ts | 23 ++- .../agent/plugin-agent-regression.test.ts | 2 + .../test/server/httpapi-reference.test.ts | 10 +- packages/opencode/test/session/system.test.ts | 2 + packages/sdk/js/src/v2/gen/types.gen.ts | 43 ++--- packages/sdk/openapi.json | 108 +++++++----- packages/server/src/groups/location.ts | 4 +- .../server/src/middleware/session-location.ts | 11 +- .../tui/src/component/prompt/autocomplete.tsx | 40 ++--- packages/web/astro.config.mjs | 1 + packages/web/src/content/docs/references.mdx | 157 ++++++++++++++++++ packages/web/src/content/docs/tui.mdx | 2 +- 38 files changed, 753 insertions(+), 165 deletions(-) create mode 100644 packages/core/src/reference/guidance.ts delete mode 100644 packages/core/src/v1/config/reference.ts create mode 100644 packages/core/test/reference-guidance.test.ts create mode 100644 packages/web/src/content/docs/references.mdx diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index 7f07577f8c..b0f7d59447 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -2,8 +2,15 @@ "$schema": "https://opencode.ai/config.json", "provider": {}, "permission": {}, - "reference": { - "effect": "github.com/Effect-TS/effect-smol", + "references": { + "effect": { + "repository": "github.com/Effect-TS/effect-smol", + "description": "Use for Effect v4 and effect-smol implementation details", + }, + "opencode-local": { + "path": "~/.local/share/opencode", + "description": "Contains opencode logs and data", + }, }, "mcp": {}, "tools": { diff --git a/packages/core/src/config/plugin/reference.ts b/packages/core/src/config/plugin/reference.ts index 81e0804b4e..22c7664996 100644 --- a/packages/core/src/config/plugin/reference.ts +++ b/packages/core/src/config/plugin/reference.ts @@ -33,11 +33,15 @@ export const Plugin = { path: AbsolutePath.make( localPath(directory, global.home, typeof entry === "string" ? entry : entry.path), ), + description: typeof entry === "string" ? undefined : entry.description, + hidden: typeof entry === "string" ? undefined : entry.hidden, }) : new Reference.GitSource({ type: "git", repository: typeof entry === "string" ? entry : entry.repository, branch: typeof entry === "string" ? undefined : entry.branch, + description: typeof entry === "string" ? undefined : entry.description, + hidden: typeof entry === "string" ? undefined : entry.hidden, }), ) } diff --git a/packages/core/src/config/reference.ts b/packages/core/src/config/reference.ts index 040169855f..4518eb4f87 100644 --- a/packages/core/src/config/reference.ts +++ b/packages/core/src/config/reference.ts @@ -5,10 +5,14 @@ import { Schema } from "effect" export class Git extends Schema.Class("ConfigV2.Reference.Git")({ repository: Schema.String, branch: Schema.String.pipe(Schema.optional), + description: Schema.String.pipe(Schema.optional), + hidden: Schema.Boolean.pipe(Schema.optional), }) {} export class Local extends Schema.Class("ConfigV2.Reference.Local")({ path: Schema.String, + description: Schema.String.pipe(Schema.optional), + hidden: Schema.Boolean.pipe(Schema.optional), }) {} export const Entry = Schema.Union([Schema.String, Git, Local]) diff --git a/packages/core/src/location-layer.ts b/packages/core/src/location-layer.ts index a0578066a0..2bf32fd755 100644 --- a/packages/core/src/location-layer.ts +++ b/packages/core/src/location-layer.ts @@ -1,4 +1,4 @@ -import { Layer, LayerMap } from "effect" +import { Effect, Layer, LayerMap } from "effect" import { Location } from "./location" import { Policy } from "./policy" import { Config } from "./config" @@ -23,6 +23,7 @@ import { Watcher } from "./filesystem/watcher" import { LocationMutation } from "./location-mutation" import { FileMutation } from "./file-mutation" import { Reference } from "./reference" +import { ReferenceGuidance } from "./reference/guidance" import { RepositoryCache } from "./repository-cache" import { Pty } from "./pty" import { SkillV2 } from "./skill" @@ -45,6 +46,9 @@ import { FetchHttpClient } from "effect/unstable/http" export class LocationServiceMap extends LayerMap.Service()("@opencode/example/LocationServiceMap", { lookup: (ref: Location.Ref) => { + const boot = Layer.effectDiscard( + Effect.logInfo("booting location services", { directory: ref.directory, workspaceID: ref.workspaceID }), + ) const location = Location.layer(ref) const systemContext = SystemContextBuiltIns.locationLayer const base = Layer.mergeAll( @@ -74,6 +78,7 @@ export class LocationServiceMap extends LayerMap.Service()(" const image = Image.layer.pipe(Layer.provide(services)) const mutation = FileMutation.locationLayer.pipe(Layer.provide(services)) const skillGuidance = SkillGuidance.locationLayer.pipe(Layer.provide(services)) + const referenceGuidance = ReferenceGuidance.locationLayer.pipe(Layer.provide(services)) const todos = SessionTodo.layer.pipe(Layer.provide(services)) const questions = QuestionV2.locationLayer.pipe(Layer.provide(services)) const builtInTools = BuiltInTools.locationLayer.pipe( @@ -89,10 +94,21 @@ export class LocationServiceMap extends LayerMap.Service()(" Layer.provide(services), Layer.provide(model), Layer.provide(skillGuidance), + Layer.provide(referenceGuidance), ) - return Layer.mergeAll(services, image, mutation, resources, todos, questions, model, runner, builtInTools).pipe( - Layer.fresh, - ) + return Layer.mergeAll( + boot, + services, + image, + mutation, + resources, + todos, + questions, + model, + runner, + builtInTools, + referenceGuidance, + ).pipe(Layer.fresh) }, idleTimeToLive: "60 minutes", dependencies: [ diff --git a/packages/core/src/location.ts b/packages/core/src/location.ts index b8020b3c78..ebfb096f13 100644 --- a/packages/core/src/location.ts +++ b/packages/core/src/location.ts @@ -5,11 +5,10 @@ import { WorkspaceV2 } from "./workspace" export * as Location from "./location" -export const Ref = Schema.Struct({ +export class Ref extends Schema.Class("Location.Ref")({ directory: AbsolutePath, - workspaceID: Schema.optional(WorkspaceV2.ID), -}).annotate({ identifier: "Location.Ref" }) -export type Ref = typeof Ref.Type + workspaceID: Schema.optional(WorkspaceV2.ID).pipe(Schema.withConstructorDefault(Effect.succeed(undefined))), +}) {} export class Info extends Schema.Class("Location.Info")({ directory: AbsolutePath, diff --git a/packages/core/src/plugin/skill/customize-opencode.md b/packages/core/src/plugin/skill/customize-opencode.md index 99b112d9a5..1c1cbdf3c2 100644 --- a/packages/core/src/plugin/skill/customize-opencode.md +++ b/packages/core/src/plugin/skill/customize-opencode.md @@ -73,6 +73,19 @@ Every field is optional. "urls": ["https://example.com/.well-known/skills/"] }, + "references": { + "docs": { + "path": "../docs", + "description": "Use for product behavior and documentation conventions" + }, + "sdk": { + "repository": "owner/sdk", + "branch": "main", + "description": "Use for SDK implementation details", + "hidden": true + } + }, + "agent": { "my-agent": { "model": "anthropic/claude-sonnet-4-6", @@ -136,6 +149,7 @@ Shape notes worth being explicit about: - `model` always carries a provider prefix: `"anthropic/claude-sonnet-4-6"`. - `skills` is an object with `paths` and/or `urls`, not an array. +- `references` is an object keyed by alias. Each value is a local path, Git repository, or string shorthand. - `agent` is an object keyed by agent name, not an array. - `plugin` is an array of strings or `[name, options]` tuples, not an object. - `mcp[name].command` is an array of strings, never a single string. `type` is required. @@ -172,6 +186,38 @@ Register skills from non-default locations via `skills.paths` (scanned recursively for `**/SKILL.md`) and `skills.urls` (each URL serves a list of skills). +## References + +References make local directories and Git repositories outside the active +project available as supporting context. Configure them under `references`, +keyed by the alias used in `@` autocomplete: + +```json +{ + "references": { + "docs": { + "path": "../product-docs", + "description": "Use for product behavior and terminology" + }, + "effect": { + "repository": "Effect-TS/effect", + "branch": "main", + "description": "Use for Effect implementation details" + } + } +} +``` + +Local `path` values may be relative to the declaring config, absolute, or use +`~/`. Git `repository` values accept Git URLs, host/path references, and GitHub +`owner/repo` shorthand; `branch` is optional. Both forms support optional +`description` and `hidden` fields. + +- Only references with a `description` are advertised to agents in system context. +- `hidden: true` removes a reference from TUI `@` autocomplete only. It remains available to agents and by direct path. +- Reference directories are automatically allowed through the external-directory boundary; normal read/edit/tool permissions still apply. +- String shorthand is supported: use `"docs": "../docs"` for local paths or `"effect": "Effect-TS/effect"` for Git repositories. + ## Agents Two ways to define an agent. Use the file form for anything non-trivial. diff --git a/packages/core/src/reference.ts b/packages/core/src/reference.ts index 9c354f55c3..66eb160eb4 100644 --- a/packages/core/src/reference.ts +++ b/packages/core/src/reference.ts @@ -9,21 +9,19 @@ import { RepositoryCache } from "./repository-cache" import { AbsolutePath } from "./schema" import { State } from "./state" -export class Info extends Schema.Class("Reference.Info")({ - name: Schema.String, - path: AbsolutePath, - source: Schema.suspend(() => Source), -}) {} - export class LocalSource extends Schema.Class("Reference.LocalSource")({ type: Schema.Literal("local"), path: AbsolutePath, + description: Schema.String.pipe(Schema.optional), + hidden: Schema.Boolean.pipe(Schema.optional), }) {} export class GitSource extends Schema.Class("Reference.GitSource")({ type: Schema.Literal("git"), repository: Schema.String, branch: Schema.String.pipe(Schema.optional), + description: Schema.String.pipe(Schema.optional), + hidden: Schema.Boolean.pipe(Schema.optional), }) {} export const Source = Schema.Union([LocalSource, GitSource]).pipe(Schema.toTaggedUnion("type")) @@ -33,6 +31,14 @@ export const Event = { Updated: EventV2.define({ type: "reference.updated", schema: {} }), } +export class Info extends Schema.Class("Reference.Info")({ + name: Schema.String, + path: AbsolutePath, + description: Schema.String.pipe(Schema.optional), + hidden: Schema.Boolean.pipe(Schema.optional), + source: Source, +}) {} + type Data = { sources: Map } @@ -71,7 +77,16 @@ export const layer = Layer.effect( const seen = new Map() for (const [name, source] of editor.list()) { if (source.type === "local") { - materialized.set(name, new Info({ name, path: source.path, source })) + materialized.set( + name, + new Info({ + name, + path: source.path, + description: source.description, + hidden: source.hidden, + source, + }), + ) continue } const repository = Repository.parse(source.repository) @@ -86,7 +101,16 @@ export const layer = Layer.effect( const target = Repository.cachePath(global.repos, repository) if (seen.has(target) && seen.get(target) !== source.branch) continue seen.set(target, source.branch) - materialized.set(name, new Info({ name, path: AbsolutePath.make(target), source })) + materialized.set( + name, + new Info({ + name, + path: AbsolutePath.make(target), + description: source.description, + hidden: source.hidden, + source, + }), + ) yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe( Effect.catchCause((cause) => Effect.logWarning("failed to materialize reference", { diff --git a/packages/core/src/reference/guidance.ts b/packages/core/src/reference/guidance.ts new file mode 100644 index 0000000000..f567264768 --- /dev/null +++ b/packages/core/src/reference/guidance.ts @@ -0,0 +1,69 @@ +export * as ReferenceGuidance from "./guidance" + +import { Context, Effect, Layer, Schema } from "effect" +import { PluginBoot } from "../plugin/boot" +import { Reference } from "../reference" +import { SystemContext } from "../system-context/index" + +const Summary = Schema.Struct({ + name: Schema.String, + path: Schema.String, + description: Schema.String.pipe(Schema.optional), +}) + +const render = (references: ReadonlyArray) => + [ + "Project references provide additional directories that can be accessed when relevant.", + "", + ...references.flatMap((reference) => [ + " ", + ` ${reference.name}`, + ` ${reference.path}`, + ...(reference.description === undefined ? [] : [` ${reference.description}`]), + " ", + ]), + "", + ].join("\n") + +export interface Interface { + readonly load: () => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/v2/ReferenceGuidance") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const boot = yield* PluginBoot.Service + const references = yield* Reference.Service + + return Service.of({ + load: Effect.fn("ReferenceGuidance.load")(function* () { + yield* boot.wait() + const available = (yield* references.list()) + .filter((reference) => reference.description !== undefined) + .map((reference) => ({ + name: reference.name, + path: reference.path, + description: reference.description, + })) + .toSorted((a, b) => a.name.localeCompare(b.name)) + if (available.length === 0) return SystemContext.empty + return SystemContext.make({ + key: SystemContext.Key.make("core/reference-guidance"), + codec: Schema.toCodecJson(Schema.Array(Summary)), + load: Effect.succeed(available), + baseline: render, + update: (_previous, current) => + [ + "The available project references have changed. This list supersedes the previous reference list.", + render(current), + ].join("\n"), + removed: () => "Project reference guidance is no longer available. Do not use previously listed references.", + }) + }), + }) + }), +) + +export const locationLayer = layer diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index 18b7b29870..822c80458c 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -161,10 +161,10 @@ export const layer = Layer.effect( args: [ "--no-config", "--files", - "--glob=!**/.git/**", ...(input.hidden ? ["--hidden"] : []), ...(input.follow ? ["--follow"] : []), `--glob=${input.pattern}`, + "--glob=!**/.git/**", ".", ], parse: (line) => @@ -195,10 +195,10 @@ export const layer = Layer.effect( args: [ "--no-config", "--files", - "--glob=!**/.git/**", ...(input.hidden ? ["--hidden"] : []), ...(input.follow ? ["--follow"] : []), - `--glob=${input.pattern}`, + ...(input.pattern === "*" ? [] : [`--glob=${input.pattern}`]), + "--glob=!**/.git/**", ".", ], parse: (line) => { @@ -226,9 +226,9 @@ export const layer = Layer.effect( "--no-config", "--json", "--hidden", - "--glob=!**/.git/**", "--no-messages", ...(input.include ? [`--glob=${input.include}`] : []), + "--glob=!**/.git/**", "--", input.pattern, input.file ?? ".", diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 88ba79098a..02a1eb3fed 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -19,6 +19,7 @@ import { QuestionV2 } from "../../question" import { SystemContext } from "../../system-context/index" import { SystemContextRegistry } from "../../system-context/registry" import { SkillGuidance } from "../../skill/guidance" +import { ReferenceGuidance } from "../../reference/guidance" import { ToolRegistry } from "../../tool/registry" import { ToolOutputStore } from "../../tool-output-store" import { SessionContextEpoch } from "../context-epoch" @@ -98,6 +99,7 @@ export const layer = Layer.effect( const location = yield* Location.Service const systemContext = yield* SystemContextRegistry.Service const skillGuidance = yield* SkillGuidance.Service + const referenceGuidance = yield* ReferenceGuidance.Service const config = yield* Config.Service const db = (yield* Database.Service).db const compaction = SessionCompaction.make({ events, llm, config: yield* config.entries() }) @@ -166,9 +168,9 @@ export const layer = Layer.effect( const sameModel = Schema.toEquivalence(Schema.UndefinedOr(ModelV2.Ref)) const loadSystemContext = (agent: AgentV2.Selection) => - Effect.all([systemContext.load(), skillGuidance.load(agent)], { concurrency: "unbounded" }).pipe( - Effect.map(SystemContext.combine), - ) + Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load()], { + concurrency: "unbounded", + }).pipe(Effect.map(SystemContext.combine)) const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* ( sessionID: SessionSchema.ID, diff --git a/packages/core/src/v1/config/config.ts b/packages/core/src/v1/config/config.ts index cea9d3454b..5c520846da 100644 --- a/packages/core/src/v1/config/config.ts +++ b/packages/core/src/v1/config/config.ts @@ -3,6 +3,7 @@ export * as ConfigV1 from "./config" import { Schema } from "effect" import { NonNegativeInt, PositiveInt, type DeepMutable } from "../../schema" import { ConfigExperimental } from "../../config/experimental" +import { ConfigReference } from "../../config/reference" import { ConfigAgentV1 } from "./agent" import { ConfigAttachmentV1 } from "./attachment" import { ConfigCommandV1 } from "./command" @@ -13,7 +14,6 @@ import { ConfigMCPV1 } from "./mcp" import { ConfigPermissionV1 } from "./permission" import { ConfigPluginV1 } from "./plugin" import { ConfigProviderV1 } from "./provider" -import { ConfigReferenceV1 } from "./reference" import { ConfigServerV1 } from "./server" import { ConfigSkillsV1 } from "./skills" @@ -42,7 +42,7 @@ export const Info = Schema.Struct({ description: "Command configuration, see https://opencode.ai/docs/commands", }), skills: Schema.optional(ConfigSkillsV1.Info).annotate({ description: "Additional skill folder paths" }), - reference: Schema.optional(ConfigReferenceV1.Info).annotate({ + references: Schema.optional(ConfigReference.Info).annotate({ description: "Named git or local directory references", }), watcher: Schema.optional(Schema.Struct({ ignore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))) })), diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 417e0217e1..19bf48aa74 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -12,7 +12,6 @@ const keys = new Set([ "logLevel", "server", "command", - "reference", "snapshot", "plugin", "autoshare", @@ -63,7 +62,7 @@ export function migrate(info: typeof ConfigV1.Info.Type) { skills: info.skills && [...(info.skills.paths ?? []), ...(info.skills.urls ?? [])], commands: info.command, instructions: info.instructions, - references: info.reference, + references: info.references, plugins: info.plugin?.map((plugin) => typeof plugin === "string" ? plugin : { package: plugin[0], options: plugin[1] }, ), diff --git a/packages/core/src/v1/config/reference.ts b/packages/core/src/v1/config/reference.ts deleted file mode 100644 index 2e562b9f35..0000000000 --- a/packages/core/src/v1/config/reference.ts +++ /dev/null @@ -1,24 +0,0 @@ -export * as ConfigReferenceV1 from "./reference" - -import { Schema } from "effect" - -const Git = Schema.Struct({ - repository: Schema.String.annotate({ - description: "Git repository URL, host/path reference, or GitHub owner/repo shorthand", - }), - branch: Schema.optional(Schema.String).annotate({ - description: "Branch or ref to clone and inspect", - }), -}) - -const Local = Schema.Struct({ - path: Schema.String.annotate({ - description: "Absolute path, ~/ path, or workspace-relative path to a local reference directory", - }), -}) - -export const Entry = Schema.Union([Schema.String, Git, Local]).annotate({ identifier: "ReferenceConfigEntry" }) -export type Entry = Schema.Schema.Type - -export const Info = Schema.Record(Schema.String, Entry).annotate({ identifier: "ReferenceConfig" }) -export type Info = Schema.Schema.Type diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 04e1c062f6..5f62cbce61 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -464,7 +464,9 @@ describe("Config", () => { ["@my-org/audit-plugin", { endpoint: "https://audit.example.com" }], ], skills: { paths: ["./skills"], urls: ["https://example.com/.well-known/skills/"] }, - reference: { docs: { path: "../docs" } }, + references: { + docs: { path: "../docs", description: "Use for product documentation", hidden: true }, + }, attachment: { image: { auto_resize: false, max_width: 1200 } }, provider: { custom: { @@ -540,7 +542,9 @@ describe("Config", () => { { package: "@my-org/audit-plugin", options: { endpoint: "https://audit.example.com" } }, ]) expect(documents[0]?.info.skills).toEqual(["./skills", "https://example.com/.well-known/skills/"]) - expect(documents[0]?.info.references).toEqual({ docs: { path: "../docs" } }) + expect(documents[0]?.info.references).toEqual({ + docs: { path: "../docs", description: "Use for product documentation", hidden: true }, + }) expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } }) expect(documents[0]?.info.providers?.custom).toMatchObject({ request: { body: { apiKey: "secret" } }, diff --git a/packages/core/test/location-layer.test.ts b/packages/core/test/location-layer.test.ts index 1a348891d7..ecffef0992 100644 --- a/packages/core/test/location-layer.test.ts +++ b/packages/core/test/location-layer.test.ts @@ -1,10 +1,11 @@ import fs from "fs/promises" import path from "path" import { describe, expect } from "bun:test" -import { Effect, Layer, Schema } from "effect" +import { Effect, Equal, Hash, Layer, Schema } from "effect" import { Tool } from "@opencode-ai/core/public" import { Catalog } from "@opencode-ai/core/catalog" import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { Location } from "@opencode-ai/core/location" import { PluginBoot } from "@opencode-ai/core/plugin/boot" import { ProviderV2 } from "@opencode-ai/core/provider" import { AbsolutePath } from "@opencode-ai/core/schema" @@ -44,6 +45,16 @@ const it = testEffect( ) describe("LocationServiceMap", () => { + it.effect("compares equivalent location refs by value", () => + Effect.sync(() => { + const directory = AbsolutePath.make("/project") + expect(Equal.equals(Location.Ref.make({ directory }), Location.Ref.make({ directory }))).toBe(true) + expect(Hash.hash(Location.Ref.make({ directory }))).toBe( + Hash.hash(Location.Ref.make({ directory, workspaceID: undefined })), + ) + }), + ) + it.live("isolates location state while sharing location policy with catalog", () => Effect.acquireRelease( Effect.promise(() => Promise.all([tmpdir(), tmpdir()])), @@ -79,7 +90,10 @@ describe("LocationServiceMap", () => { providers: yield* catalog.provider.all(), tools: yield* toolDefinitions(yield* ToolRegistry.Service), } - }).pipe(Effect.scoped, Effect.provide(LocationServiceMap.get({ directory: AbsolutePath.make(directory) }))) + }).pipe( + Effect.scoped, + Effect.provide(LocationServiceMap.get(Location.Ref.make({ directory: AbsolutePath.make(directory) }))), + ) const blockedState = yield* update(blocked.path) expect(blockedState.providers.some((provider) => provider.id === ProviderV2.ID.make("test"))).toBe(false) diff --git a/packages/core/test/reference-guidance.test.ts b/packages/core/test/reference-guidance.test.ts new file mode 100644 index 0000000000..5e1aba1923 --- /dev/null +++ b/packages/core/test/reference-guidance.test.ts @@ -0,0 +1,77 @@ +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { PluginBoot } from "@opencode-ai/core/plugin/boot" +import { Reference } from "@opencode-ai/core/reference" +import { ReferenceGuidance } from "@opencode-ai/core/reference/guidance" +import { SystemContext } from "@opencode-ai/core/system-context/index" +import { it } from "./lib/effect" + +describe("ReferenceGuidance", () => { + it.effect("lists available references in the system context", () => + Effect.gen(function* () { + const guidance = yield* ReferenceGuidance.Service + const generation = yield* SystemContext.initialize(yield* guidance.load()) + + expect(generation.baseline).toContain("") + expect(generation.baseline).toContain("docs") + expect(generation.baseline).toContain("/docs") + expect(generation.baseline).toContain("Use for product documentation") + }).pipe( + Effect.provide(ReferenceGuidance.layer), + Effect.provide( + Layer.mock(Reference.Service, { + list: () => + Effect.succeed([ + new Reference.Info({ + name: "docs", + path: AbsolutePath.make("/docs"), + description: "Use for product documentation", + source: new Reference.LocalSource({ + type: "local", + path: AbsolutePath.make("/docs"), + description: "Use for product documentation", + }), + }), + ]), + }), + ), + Effect.provide(Layer.mock(PluginBoot.Service, { wait: () => Effect.void })), + ), + ) + + it.effect("omits guidance when no references are available", () => + Effect.gen(function* () { + const guidance = yield* ReferenceGuidance.Service + const generation = yield* SystemContext.initialize(yield* guidance.load()) + expect(generation.baseline).toBe("") + }).pipe( + Effect.provide(ReferenceGuidance.layer), + Effect.provide(Layer.mock(Reference.Service, { list: () => Effect.succeed([]) })), + Effect.provide(Layer.mock(PluginBoot.Service, { wait: () => Effect.void })), + ), + ) + + it.effect("omits references without descriptions", () => + Effect.gen(function* () { + const guidance = yield* ReferenceGuidance.Service + const generation = yield* SystemContext.initialize(yield* guidance.load()) + expect(generation.baseline).toBe("") + }).pipe( + Effect.provide(ReferenceGuidance.layer), + Effect.provide( + Layer.mock(Reference.Service, { + list: () => + Effect.succeed([ + new Reference.Info({ + name: "docs", + path: AbsolutePath.make("/docs"), + source: new Reference.LocalSource({ type: "local", path: AbsolutePath.make("/docs") }), + }), + ]), + }), + ), + Effect.provide(Layer.mock(PluginBoot.Service, { wait: () => Effect.void })), + ), + ) +}) diff --git a/packages/core/test/reference.test.ts b/packages/core/test/reference.test.ts index 58b28bf06b..dfa8a202a6 100644 --- a/packages/core/test/reference.test.ts +++ b/packages/core/test/reference.test.ts @@ -19,10 +19,16 @@ describe("Reference", () => { const scope = yield* Scope.make() const update = yield* references.transform().pipe(Effect.provideService(Scope.Scope, scope)) const path = AbsolutePath.make("/docs") - yield* update((editor) => editor.add("docs", new Reference.LocalSource({ type: "local", path }))) + const source = new Reference.LocalSource({ + type: "local", + path, + description: "Use for API documentation", + hidden: true, + }) + yield* update((editor) => editor.add("docs", source)) expect(yield* references.list()).toEqual([ - new Reference.Info({ name: "docs", path, source: new Reference.LocalSource({ type: "local", path }) }), + new Reference.Info({ name: "docs", path, description: "Use for API documentation", hidden: true, source }), ]) yield* Scope.close(scope, Exit.void) @@ -58,4 +64,33 @@ describe("Reference", () => { Effect.provide(Global.defaultLayer), ), ) + + it.effect("preserves configured Git descriptions", () => + Effect.gen(function* () { + const references = yield* Reference.Service + const update = yield* references.transform() + const repository = Repository.parseRemote("owner/repo") + const source = new Reference.GitSource({ + type: "git", + repository: "owner/repo", + description: "Use for SDK implementation details", + }) + yield* update((editor) => editor.add("sdk", source)) + + expect(yield* references.list()).toEqual([ + new Reference.Info({ + name: "sdk", + path: AbsolutePath.make(Repository.cachePath(Global.Path.repos, repository)), + description: "Use for SDK implementation details", + source, + }), + ]) + }).pipe( + Effect.scoped, + Effect.provide(Reference.layer), + Effect.provide(cache), + Effect.provide(EventV2.defaultLayer), + Effect.provide(Global.defaultLayer), + ), + ) }) diff --git a/packages/core/test/ripgrep.test.ts b/packages/core/test/ripgrep.test.ts index c91d1dd7c9..d7efe2a2e3 100644 --- a/packages/core/test/ripgrep.test.ts +++ b/packages/core/test/ripgrep.test.ts @@ -10,7 +10,27 @@ import { testEffect } from "./lib/effect" const it = testEffect(Ripgrep.defaultLayer) describe("Ripgrep", () => { - it.live("allows caller globs to re-include git metadata", () => + it.live("keeps ignored files out of catch-all find results", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "node_modules", "pkg"), { recursive: true })) + yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src"))) + yield* Effect.promise(() => Bun.$`git init -q ${tmp.path}`) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".gitignore"), "node_modules/\n")) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "node_modules", "pkg", "index.js"), "ignored\n")) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "index.js"), "included\n")) + + const files = yield* (yield* Ripgrep.Service).find({ cwd: tmp.path, pattern: "*", limit: 10 }) + expect(files.map((item) => item.path)).toContain(RelativePath.make("src/index.js")) + expect(files.map((item) => item.path)).not.toContain(RelativePath.make("node_modules/pkg/index.js")) + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + ) + + it.live("never includes git metadata", () => Effect.acquireUseRelease( Effect.promise(() => tmpdir()), (tmp) => @@ -23,7 +43,7 @@ describe("Ripgrep", () => { const files = yield* ripgrep.find({ cwd: tmp.path, pattern: "**/*", limit: 10 }) expect(files.map((item) => item.path)).toContain(RelativePath.make(".opencode/config")) - expect(files.map((item) => item.path)).toContain(RelativePath.make(".git/config")) + expect(files.map((item) => item.path)).not.toContain(RelativePath.make(".git/config")) const observed: string[] = [] const limited = yield* ripgrep.find({ @@ -36,7 +56,7 @@ describe("Ripgrep", () => { const matches = yield* ripgrep.grep({ cwd: tmp.path, pattern: "needle", include: "config", limit: 10 }) expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".opencode/config")) - expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".git/config")) + expect(matches.map((item) => item.entry.path)).not.toContain(RelativePath.make(".git/config")) }), (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), ), diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts index fc136d9607..e8da56a3a5 100644 --- a/packages/core/test/session-runner-recorded.test.ts +++ b/packages/core/test/session-runner-recorded.test.ts @@ -25,6 +25,7 @@ import { Location } from "@opencode-ai/core/location" import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" import { SystemContext } from "@opencode-ai/core/system-context" import { SkillGuidance } from "@opencode-ai/core/skill/guidance" +import { ReferenceGuidance } from "@opencode-ai/core/reference/guidance" import { describe, expect } from "bun:test" import { eq } from "drizzle-orm" import { Effect, Layer } from "effect" @@ -70,6 +71,7 @@ const models = SessionRunnerModel.layerWith(() => Effect.succeed(model)) const systemContext = SystemContextRegistry.layer const location = Location.layer({ directory: AbsolutePath.make("/project") }).pipe(Layer.provide(Project.defaultLayer)) const skillGuidance = Layer.mock(SkillGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) }) +const referenceGuidance = Layer.mock(ReferenceGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) }) const config = Layer.succeed(Config.Service, Config.Service.of({ entries: () => Effect.succeed([]) })) const runner = SessionRunnerLLM.defaultLayer.pipe( Layer.provide(database), @@ -82,6 +84,7 @@ const runner = SessionRunnerLLM.defaultLayer.pipe( Layer.provide(location), Layer.provide(agents), Layer.provide(skillGuidance), + Layer.provide(referenceGuidance), Layer.provide(config), ) const coordinator = SessionRunCoordinator.layer.pipe(Layer.provide(runner)) diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 73ff5f47ff..af17de1759 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -48,6 +48,7 @@ import { SessionStore } from "@opencode-ai/core/session/store" import { SystemContext } from "@opencode-ai/core/system-context" import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" import { SkillGuidance } from "@opencode-ai/core/skill/guidance" +import { ReferenceGuidance } from "@opencode-ai/core/reference/guidance" import { ModelV2 } from "@opencode-ai/core/model" import { Location } from "@opencode-ai/core/location" import { ProviderV2 } from "@opencode-ai/core/provider" @@ -215,6 +216,7 @@ const skillGuidance = Layer.mock(SkillGuidance.Service, { : SystemContext.empty, ), }) +const referenceGuidance = Layer.mock(ReferenceGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) }) const config = Layer.succeed( Config.Service, Config.Service.of({ @@ -243,6 +245,7 @@ const runner = SessionRunnerLLM.layer.pipe( Layer.provide(location), Layer.provide(agents), Layer.provide(skillGuidance), + Layer.provide(referenceGuidance), Layer.provide(config), ) const coordinator = SessionRunCoordinator.layer.pipe(Layer.provide(runner)) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 86dc417ca5..1424400871 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -24,9 +24,13 @@ import { Effect, Context, Layer, Schema } from "effect" import { InstanceState } from "@/effect/instance-state" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" -import { type DeepMutable } from "@opencode-ai/core/schema" +import { AbsolutePath, type DeepMutable } from "@opencode-ai/core/schema" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { PluginBoot } from "@opencode-ai/core/plugin/boot" +import { Reference } from "@opencode-ai/core/reference" +import { Location } from "@opencode-ai/core/location" export const Info = Schema.Struct({ name: Schema.String, @@ -89,15 +93,21 @@ export const layer = Layer.effect( const plugin = yield* Plugin.Service const skill = yield* Skill.Service const provider = yield* Provider.Service + const locations = yield* LocationServiceMap const state = yield* InstanceState.make( Effect.fn("Agent.state")(function* (ctx) { const cfg = yield* config.get() const skillDirs = yield* skill.dirs() + const referenceDirs = yield* Effect.gen(function* () { + yield* (yield* PluginBoot.Service).wait() + return (yield* (yield* Reference.Service).list()).map((reference) => reference.path) + }).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(ctx.directory) })))) const whitelistedDirs = [ Truncate.GLOB, path.join(Global.Path.tmp, "*"), ...skillDirs.map((dir) => path.join(dir, "*")), + ...referenceDirs.map((dir) => path.join(dir, "*")), ] const readonlyExternalDirectory = { "*": "ask", @@ -429,8 +439,18 @@ export const defaultLayer = layer.pipe( Layer.provide(Auth.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(Skill.defaultLayer), + Layer.provide(LocationServiceMap.layer), ) -export const node = LayerNode.make(layer, [Config.node, Auth.node, Plugin.node, Skill.node, Provider.node]) +const locationServiceMapNode = LayerNode.make(LocationServiceMap.layer, []) + +export const node = LayerNode.make(layer, [ + Config.node, + Auth.node, + Plugin.node, + Skill.node, + Provider.node, + locationServiceMapNode, +]) export * as Agent from "./agent" diff --git a/packages/opencode/src/cli/cmd/debug/file.ts b/packages/opencode/src/cli/cmd/debug/file.ts index 5ad31985df..21447ba468 100644 --- a/packages/opencode/src/cli/cmd/debug/file.ts +++ b/packages/opencode/src/cli/cmd/debug/file.ts @@ -2,13 +2,14 @@ import { EOL } from "os" import { Effect } from "effect" import { FileSystem } from "@opencode-ai/core/filesystem" import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { Location } from "@opencode-ai/core/location" import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema" import { effectCmd } from "../../effect-cmd" import { cmd } from "../cmd" const filesystem = (effect: Effect.Effect) => effect.pipe( - Effect.provide(LocationServiceMap.get({ directory: AbsolutePath.make(process.cwd()) })), + Effect.provide(LocationServiceMap.get(Location.Ref.make({ directory: AbsolutePath.make(process.cwd()) }))), Effect.provide(LocationServiceMap.layer), ) diff --git a/packages/opencode/src/cli/cmd/debug/v2.ts b/packages/opencode/src/cli/cmd/debug/v2.ts index aab7018982..74288529d4 100644 --- a/packages/opencode/src/cli/cmd/debug/v2.ts +++ b/packages/opencode/src/cli/cmd/debug/v2.ts @@ -2,6 +2,7 @@ import { EOL } from "os" import { Effect, Option } from "effect" import { Catalog } from "@opencode-ai/core/catalog" import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { Location } from "@opencode-ai/core/location" import { PluginBoot } from "@opencode-ai/core/plugin/boot" import { AbsolutePath } from "@opencode-ai/core/schema" import { effectCmd } from "../../effect-cmd" @@ -37,9 +38,11 @@ export const V2Command = effectCmd({ }).pipe( Effect.withSpan("Cli.debug.v2"), Effect.provide( - LocationServiceMap.get({ - directory: AbsolutePath.make(process.cwd()), - }), + LocationServiceMap.get( + Location.Ref.make({ + directory: AbsolutePath.make(process.cwd()), + }), + ), ), Effect.provide(LocationServiceMap.layer), ), diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts index fae43d10c5..556edcd5e3 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts @@ -18,7 +18,9 @@ export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handl const filesystem = Effect.fnUntraced(function* (effect: Effect.Effect) { return yield* effect.pipe( - Effect.provide(locations.get({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })), + Effect.provide( + locations.get(Location.Ref.make({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })), + ), ) }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts index d8f9e55a06..0058c66b52 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts @@ -7,6 +7,7 @@ import { handlePtyInput } from "@opencode-ai/core/pty/input" import { PtyID } from "@opencode-ai/core/pty/schema" import { PtyTicket } from "@opencode-ai/core/pty/ticket" import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { Location } from "@opencode-ai/core/location" import { AbsolutePath } from "@opencode-ai/core/schema" import { Shell } from "@/shell/shell" import { EffectBridge } from "@/effect/bridge" @@ -41,13 +42,15 @@ export const ptyHandlers = HttpApiBuilder.group(InstanceHttpApi, "pty", (handler const cors = yield* CorsConfig const locations = yield* LocationServiceMap const unregister = registerDisposer((directory) => - Effect.runPromise(locations.invalidate({ directory: AbsolutePath.make(directory) })), + Effect.runPromise(locations.invalidate(Location.Ref.make({ directory: AbsolutePath.make(directory) }))), ) yield* Effect.addFinalizer(() => Effect.sync(unregister)) const pty = Effect.fnUntraced(function* (effect: Effect.Effect) { return yield* effect.pipe( - Effect.provide(locations.get({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })), + Effect.provide( + locations.get(Location.Ref.make({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })), + ), ) }) @@ -158,13 +161,15 @@ export const ptyConnectHandlers = HttpApiBuilder.group(PtyConnectApi, "pty-conne const cors = yield* CorsConfig const locations = yield* LocationServiceMap const unregister = registerDisposer((directory) => - Effect.runPromise(locations.invalidate({ directory: AbsolutePath.make(directory) })), + Effect.runPromise(locations.invalidate(Location.Ref.make({ directory: AbsolutePath.make(directory) }))), ) yield* Effect.addFinalizer(() => Effect.sync(unregister)) const pty = Effect.fnUntraced(function* (effect: Effect.Effect) { return yield* effect.pipe( - Effect.provide(locations.get({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })), + Effect.provide( + locations.get(Location.Ref.make({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })), + ), ) }) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index cee669d145..74401779d3 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -16,6 +16,11 @@ import type { Provider } from "@/provider/provider" import type { Agent } from "@/agent/agent" import { Permission } from "@/permission" import { Skill } from "@/skill" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { Location } from "@opencode-ai/core/location" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { PluginBoot } from "@opencode-ai/core/plugin/boot" +import { Reference } from "@opencode-ai/core/reference" export function provider(model: Provider.Model) { if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3")) @@ -44,10 +49,15 @@ export const layer = Layer.effect( Service, Effect.gen(function* () { const skill = yield* Skill.Service + const locations = yield* LocationServiceMap return Service.of({ environment: Effect.fn("SystemPrompt.environment")(function* (model: Provider.Model) { const ctx = yield* InstanceState.context + const references = yield* Effect.gen(function* () { + yield* (yield* PluginBoot.Service).wait() + return (yield* (yield* Reference.Service).list()).filter((reference) => reference.description !== undefined) + }).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(ctx.directory) })))) return [ [ `You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`, @@ -60,7 +70,25 @@ export const layer = Layer.effect( ` Today's date: ${new Date().toDateString()}`, ``, ].join("\n"), - ] + references.length === 0 + ? undefined + : [ + "Project references provide additional directories that can be accessed when relevant.", + "", + ...references + .toSorted((a, b) => a.name.localeCompare(b.name)) + .flatMap((reference) => [ + " ", + ` ${reference.name}`, + ` ${reference.path}`, + ...(reference.description === undefined + ? [] + : [` ${reference.description}`]), + " ", + ]), + "", + ].join("\n"), + ].filter((part): part is string => part !== undefined) }), skills: Effect.fn("SystemPrompt.skills")(function* (agent: Agent.Info) { @@ -80,8 +108,10 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(Skill.defaultLayer)) +export const defaultLayer = layer.pipe(Layer.provide(Skill.defaultLayer), Layer.provide(LocationServiceMap.layer)) -export const node = LayerNode.make(layer, [Skill.node]) +const locationServiceMapNode = LayerNode.make(LocationServiceMap.layer, []) + +export const node = LayerNode.make(layer, [Skill.node, locationServiceMapNode]) export * as SystemPrompt from "./system" diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 3683229fc5..67071bce73 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -14,6 +14,7 @@ import { Plugin } from "../../src/plugin" import { Provider } from "../../src/provider/provider" import { Skill } from "../../src/skill" import { Truncate } from "../../src/tool/truncate" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" const agentLayer = (flags: Partial = {}) => Agent.layer.pipe( @@ -22,6 +23,7 @@ const agentLayer = (flags: Partial = {}) => Layer.provide(Auth.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(Skill.defaultLayer), + Layer.provide(LocationServiceMap.layer), Layer.provide(RuntimeFlags.layer(flags)), ) @@ -119,7 +121,7 @@ it.instance( }), { config: { - reference: { + references: { effect: "github.com/effect/effect-smol", effectFull: { repository: "Effect-TS/effect", @@ -601,6 +603,25 @@ description: Permission skill. { git: true }, ) +it.instance( + "project reference directories are allowed for external_directory", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const build = yield* load((svc) => svc.get("build")) + const target = path.resolve(test.directory, "../docs/reference/notes.md") + expect(Permission.evaluate("external_directory", target, build!.permission).action).toBe("allow") + }), + { + git: true, + config: { + references: { + docs: "../docs", + }, + }, + }, +) + it.instance("defaultAgent returns build when no default_agent config", () => Effect.gen(function* () { const agent = yield* load((svc) => svc.defaultAgent()) diff --git a/packages/opencode/test/agent/plugin-agent-regression.test.ts b/packages/opencode/test/agent/plugin-agent-regression.test.ts index 2421275716..4c894b0a01 100644 --- a/packages/opencode/test/agent/plugin-agent-regression.test.ts +++ b/packages/opencode/test/agent/plugin-agent-regression.test.ts @@ -1,5 +1,6 @@ import { expect } from "bun:test" import { FSUtil } from "@opencode-ai/core/fs-util" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" import { Effect, Layer } from "effect" import { FetchHttpClient } from "effect/unstable/http" import path from "path" @@ -43,6 +44,7 @@ const agentLayer = Agent.layer.pipe( Layer.provide(SkillTest.empty), Layer.provide(provider.layer), Layer.provide(pluginLayer), + Layer.provide(LocationServiceMap.layer), Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })), ) diff --git a/packages/opencode/test/server/httpapi-reference.test.ts b/packages/opencode/test/server/httpapi-reference.test.ts index 0c939bb70c..dcb260588b 100644 --- a/packages/opencode/test/server/httpapi-reference.test.ts +++ b/packages/opencode/test/server/httpapi-reference.test.ts @@ -16,7 +16,7 @@ describe("reference HttpApi", () => { config: { formatter: false, lsp: false, - reference: { + references: { docs: "./docs", effect: { repository: "Effect-TS/effect", branch: "main" }, bad: "not-a-repo", @@ -35,18 +35,26 @@ describe("reference HttpApi", () => { { name: "docs", path: path.join(tmp.path, "docs"), + description: null, + hidden: null, source: { type: "local", path: path.join(tmp.path, "docs"), + description: null, + hidden: null, }, }, { name: "effect", path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"), + description: null, + hidden: null, source: { type: "git", repository: "Effect-TS/effect", branch: "main", + description: null, + hidden: null, }, }, ]) diff --git a/packages/opencode/test/session/system.test.ts b/packages/opencode/test/session/system.test.ts index 28b1bcac82..69cec7bdcc 100644 --- a/packages/opencode/test/session/system.test.ts +++ b/packages/opencode/test/session/system.test.ts @@ -5,6 +5,7 @@ import { NamedError } from "@opencode-ai/core/util/error" import { Skill } from "../../src/skill" import { Permission } from "../../src/permission" import { SystemPrompt } from "../../src/session/system" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" import { testEffect } from "../lib/effect" const skills: Skill.Info[] = [ @@ -42,6 +43,7 @@ const build: Agent.Info = { const it = testEffect( SystemPrompt.layer.pipe( + Layer.provide(LocationServiceMap.layer), Layer.provide( Layer.succeed( Skill.Service, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2b4677e40a..9e9c74c6ec 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1685,26 +1685,6 @@ export type ServerConfig = { cors?: Array } -export type ReferenceConfigEntry = - | string - | { - /** - * Git repository URL, host/path reference, or GitHub owner/repo shorthand - */ - repository: string - branch?: string - } - | { - /** - * Absolute path, ~/ path, or workspace-relative path to a local reference directory - */ - path: string - } - -export type ReferenceConfig = { - [key: string]: ReferenceConfigEntry -} - export type PermissionActionConfig = "ask" | "allow" | "deny" export type PermissionObjectConfig = { @@ -1948,7 +1928,9 @@ export type Config = { paths?: Array urls?: Array } - reference?: ReferenceConfig + references?: { + [key: string]: string | ConfigV2ReferenceGit | ConfigV2ReferenceLocal + } watcher?: { ignore?: Array } @@ -3723,6 +3705,19 @@ export type SyncEventSessionNextCompactionEnded = { } } +export type ConfigV2ReferenceGit = { + repository: string + branch?: string + description?: string + hidden?: boolean +} + +export type ConfigV2ReferenceLocal = { + path: string + description?: string + hidden?: boolean +} + export type PolicyEffect = "allow" | "deny" export type ConfigV2ExperimentalPolicy = { @@ -4174,17 +4169,23 @@ export type QuestionV2Reply = { export type ReferenceLocalSource = { type: "local" path: string + description?: string + hidden?: boolean } export type ReferenceGitSource = { type: "git" repository: string branch?: string + description?: string + hidden?: boolean } export type ReferenceInfo = { name: string path: string + description?: string + hidden?: boolean source: ReferenceLocalSource | ReferenceGitSource } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 162e7d495f..3bb92d700f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -17628,44 +17628,6 @@ "additionalProperties": false, "description": "Server configuration for opencode serve and web commands" }, - "ReferenceConfigEntry": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "repository": { - "type": "string", - "description": "Git repository URL, host/path reference, or GitHub owner/repo shorthand" - }, - "branch": { - "type": "string" - } - }, - "required": ["repository"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Absolute path, ~/ path, or workspace-relative path to a local reference directory" - } - }, - "required": ["path"], - "additionalProperties": false - } - ] - }, - "ReferenceConfig": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/ReferenceConfigEntry" - } - }, "PermissionActionConfig": { "type": "string", "enum": ["ask", "allow", "deny"] @@ -18256,8 +18218,21 @@ }, "additionalProperties": false }, - "reference": { - "$ref": "#/components/schemas/ReferenceConfig" + "references": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/ConfigV2ReferenceGit" + }, + { + "$ref": "#/components/schemas/ConfigV2ReferenceLocal" + } + ] + } }, "watcher": { "type": "object", @@ -23918,6 +23893,41 @@ "required": ["type", "id", "syncEvent"], "additionalProperties": false }, + "ConfigV2ReferenceGit": { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "description": { + "type": "string" + }, + "hidden": { + "type": "boolean" + } + }, + "required": ["repository"], + "additionalProperties": false + }, + "ConfigV2ReferenceLocal": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "hidden": { + "type": "boolean" + } + }, + "required": ["path"], + "additionalProperties": false + }, "PolicyEffect": { "type": "string", "enum": ["allow", "deny"] @@ -25190,6 +25200,12 @@ }, "path": { "type": "string" + }, + "description": { + "type": "string" + }, + "hidden": { + "type": "boolean" } }, "required": ["type", "path"], @@ -25207,6 +25223,12 @@ }, "branch": { "type": "string" + }, + "description": { + "type": "string" + }, + "hidden": { + "type": "boolean" } }, "required": ["type", "repository"], @@ -25221,6 +25243,12 @@ "path": { "type": "string" }, + "description": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, "source": { "anyOf": [ { diff --git a/packages/server/src/groups/location.ts b/packages/server/src/groups/location.ts index 6a5815ca25..4ee082c717 100644 --- a/packages/server/src/groups/location.ts +++ b/packages/server/src/groups/location.ts @@ -57,12 +57,12 @@ export class LocationMiddleware extends HttpApiMiddleware.Service< function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref { const query = new URL(request.url, "http://localhost").searchParams const workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"] - return { + return Location.Ref.make({ directory: AbsolutePath.make( query.get("location[directory]") || request.headers["x-opencode-directory"] || process.cwd(), ), workspaceID: workspaceID ? WorkspaceV2.ID.make(workspaceID) : undefined, - } + }) } export const layer = Layer.effect( diff --git a/packages/server/src/middleware/session-location.ts b/packages/server/src/middleware/session-location.ts index e98c0db9da..7306cf76b8 100644 --- a/packages/server/src/middleware/session-location.ts +++ b/packages/server/src/middleware/session-location.ts @@ -1,5 +1,6 @@ import { Database } from "@opencode-ai/core/database/database" import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { Location } from "@opencode-ai/core/location" import { AbsolutePath } from "@opencode-ai/core/schema" import { SessionV2 } from "@opencode-ai/core/session" import { SessionTable } from "@opencode-ai/core/session/sql" @@ -54,10 +55,12 @@ export const sessionLocationLayer = Layer.effect( return yield* effect.pipe( Effect.provide( - locations.get({ - directory: AbsolutePath.make(row.directory), - workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined, - }), + locations.get( + Location.Ref.make({ + directory: AbsolutePath.make(row.directory), + workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined, + }), + ), ), ) }), diff --git a/packages/tui/src/component/prompt/autocomplete.tsx b/packages/tui/src/component/prompt/autocomplete.tsx index 1c177f8866..b543d7c38e 100644 --- a/packages/tui/src/component/prompt/autocomplete.tsx +++ b/packages/tui/src/component/prompt/autocomplete.tsx @@ -278,7 +278,7 @@ export function Autocomplete(props: { const { baseQuery } = extractLineRange(search()) const slash = baseQuery.indexOf("/") const alias = slash === -1 ? baseQuery : baseQuery.slice(0, slash) - return syncV2.data.reference.find((item) => item.name === alias) + return syncV2.data.reference.find((item) => !item.hidden && item.name === alias) }) function normalizeMentionPath(filePath: string) { @@ -411,25 +411,27 @@ export function Autocomplete(props: { }) const referenceAliases = createMemo(() => - syncV2.data.reference.map( - (reference): AutocompleteOption => ({ - display: "@" + reference.name, - description: " dir", - onSelect: () => { - insertPart(reference.name, { - type: "file", - mime: "application/x-directory", - filename: reference.name, - url: pathToFileURL(reference.path).href, - source: { + syncV2.data.reference + .filter((reference) => !reference.hidden) + .map( + (reference): AutocompleteOption => ({ + display: "@" + reference.name, + description: ` ${reference.source.type === "git" ? reference.source.repository : reference.source.path}`, + onSelect: () => { + insertPart(reference.name, { type: "file", - text: { start: 0, end: 0, value: "" }, - path: reference.name, - }, - }) - }, - }), - ), + mime: "application/x-directory", + filename: reference.name, + url: pathToFileURL(reference.path).href, + source: { + type: "file", + text: { start: 0, end: 0, value: "" }, + path: reference.name, + }, + }) + }, + }), + ), ) const commands = createMemo((): AutocompleteOption[] => { diff --git a/packages/web/astro.config.mjs b/packages/web/astro.config.mjs index 78b3587817..48d6f3c557 100644 --- a/packages/web/astro.config.mjs +++ b/packages/web/astro.config.mjs @@ -264,6 +264,7 @@ export default defineConfig({ "mcp-servers", "acp", "skills", + "references", "custom-tools", ], }, diff --git a/packages/web/src/content/docs/references.mdx b/packages/web/src/content/docs/references.mdx new file mode 100644 index 0000000000..2df002c331 --- /dev/null +++ b/packages/web/src/content/docs/references.mdx @@ -0,0 +1,157 @@ +--- +title: References +description: Add local directories and Git repositories as project references. +--- + +References give OpenCode access to directories outside the current project. Use them to make documentation, shared libraries, examples, or another repository available while you work. + +References are configured by alias in `opencode.json` or `opencode.jsonc`. + +```jsonc title="opencode.jsonc" +{ + "$schema": "https://opencode.ai/config.json", + "references": { + "docs": { + "path": "../product-docs", + "description": "Use for product behavior and documentation conventions", + }, + "sdk": { + "repository": "anomalyco/opencode-sdk-js", + "branch": "main", + "description": "Use for JavaScript SDK implementation details", + }, + }, +} +``` + +--- + +## Local directories + +Use `path` to reference a local directory. + +```jsonc title="opencode.jsonc" +{ + "references": { + "docs": { + "path": "../docs", + }, + }, +} +``` + +Paths can be: + +- Relative to the config file that defines the reference +- Absolute, such as `/home/user/docs` +- Relative to your home directory, such as `~/docs` + +You can also use a string shorthand: + +```jsonc title="opencode.jsonc" +{ + "references": { + "docs": "../docs", + }, +} +``` + +--- + +## Git repositories + +Use `repository` to reference a Git repository. OpenCode materializes the repository in its local repository cache and makes the checked-out source available as a reference directory. + +```jsonc title="opencode.jsonc" +{ + "references": { + "effect": { + "repository": "Effect-TS/effect", + "branch": "main", + }, + }, +} +``` + +`repository` accepts Git URLs, host/path references, and GitHub `owner/repo` shorthand. The optional `branch` field selects a branch or ref. Without `branch`, OpenCode uses the repository's default branch. + +You can use string shorthand when you do not need a branch, description, or other options: + +```jsonc title="opencode.jsonc" +{ + "references": { + "effect": "Effect-TS/effect", + }, +} +``` + +:::note +Git references are refreshed asynchronously. A newly configured repository may take a moment to finish cloning or updating. +::: + +--- + +## Describe usage + +Add `description` to explain when an agent should use a reference. + +```jsonc title="opencode.jsonc" +{ + "references": { + "design-system": { + "path": "../design-system", + "description": "Use when implementing UI components or design tokens", + }, + }, +} +``` + +OpenCode includes references with descriptions in agent context. Descriptions should be short and specific enough to distinguish references with similar content. References without descriptions remain available through autocomplete and direct use, but are not advertised to agents. + +--- + +## Hide autocomplete entries + +Set `hidden` to `true` to omit a reference from `@` autocomplete in the TUI. + +```jsonc title="opencode.jsonc" +{ + "references": { + "internal": { + "path": "../internal", + "description": "Use for internal implementation details", + "hidden": true, + }, + }, +} +``` + +`hidden` only affects autocomplete. A hidden reference with a description remains included in agent context. + +--- + +## Use references + +Configured references appear in TUI `@` autocomplete. Type `@alias` to attach the reference root, or `@alias/` to search for files inside it. + +```text +Compare this implementation with @sdk/src/client.ts +``` + +Agents also receive the resolved paths and descriptions of configured references that have descriptions in their system context, so they can inspect a reference when it is relevant without you attaching it manually. + +OpenCode automatically allows reference directories through its external-directory permission boundary. Normal tool permissions still apply; for example, an agent that cannot edit files does not gain edit access because a directory is configured as a reference. + +--- + +## Configure fields + +| Field | Local | Git | Description | +| ------------- | ----- | --- | ------------------------------------------------ | +| `path` | Yes | No | Local reference directory | +| `repository` | No | Yes | Git URL, host/path, or GitHub `owner/repo` value | +| `branch` | No | Yes | Optional Git branch or ref | +| `description` | Yes | Yes | Guidance describing when to use the reference | +| `hidden` | Yes | Yes | Hide the reference from TUI `@` autocomplete | + +Reference aliases cannot be empty or contain `/`, whitespace, backticks, or commas. diff --git a/packages/web/src/content/docs/tui.mdx b/packages/web/src/content/docs/tui.mdx index cd668a3bef..a03797b302 100644 --- a/packages/web/src/content/docs/tui.mdx +++ b/packages/web/src/content/docs/tui.mdx @@ -41,7 +41,7 @@ How is auth handled in @packages/functions/src/api/index.ts? The content of the file is added to the conversation automatically. -Configured references also appear in `@` autocomplete. Type `@alias` to add the reference root as context, or type `@alias/` to autocomplete files inside that reference. +Configured [references](/docs/references) also appear in `@` autocomplete. Type `@alias` to add the reference root as context, or type `@alias/` to autocomplete files inside that reference. ```text "@docs/README.md" Compare our setup with @docs/README.md From 91073360c6904448bce5b6da9e933604944ed015 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:24:41 -0500 Subject: [PATCH 003/157] fix(mcp): make client creation failure-safe (#31595) --- packages/opencode/src/mcp/index.ts | 62 ++++++++++++-------- packages/opencode/test/mcp/lifecycle.test.ts | 28 +++++++++ 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 63a91ffe4d..5ce4719ee5 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -27,7 +27,7 @@ import { EventV2Bridge } from "@/event-v2-bridge" import { EventV2 } from "@opencode-ai/core/event" import { TuiEvent } from "@/server/tui-event" import open from "open" -import { Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" +import { Cause, Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" @@ -446,31 +446,45 @@ export const layer = Layer.effect( ) }) - const create = Effect.fn("MCP.create")(function* (key: string, mcp: ConfigMCPV1.Info) { - if (mcp.enabled === false) { - return DISABLED_RESULT - } + const create = Effect.fn("MCP.create")( + function* (key: string, mcp: ConfigMCPV1.Info) { + if (mcp.enabled === false) { + return DISABLED_RESULT + } - const { client: mcpClient, status } = - mcp.type === "remote" - ? yield* connectRemote(key, mcp as ConfigMCPV1.Info & { type: "remote" }) - : yield* connectLocal(key, mcp as ConfigMCPV1.Info & { type: "local" }) + const { client: mcpClient, status } = + mcp.type === "remote" + ? yield* connectRemote(key, mcp as ConfigMCPV1.Info & { type: "remote" }) + : yield* connectLocal(key, mcp as ConfigMCPV1.Info & { type: "local" }) - if (!mcpClient) { - if (status.status !== "connected" && status.status !== "disabled") { - yield* Effect.logWarning("server unavailable", { key, type: mcp.type, status: status.status }) + if (!mcpClient) { + if (status.status !== "connected" && status.status !== "disabled") { + yield* Effect.logWarning("server unavailable", { key, type: mcp.type, status: status.status }) + } + return { status } satisfies CreateResult } - return { status } satisfies CreateResult - } - const listed = mcpClient.getServerCapabilities()?.tools ? yield* defs(mcpClient, mcp.timeout) : [] - if (!listed) { - yield* Effect.tryPromise(() => mcpClient.close()).pipe(Effect.ignore) - return { status: { status: "failed", error: "Failed to get tools" } } satisfies CreateResult - } - - return { mcpClient, status, defs: listed } satisfies CreateResult - }) + return yield* Effect.gen(function* () { + const listed = mcpClient.getServerCapabilities()?.tools ? yield* defs(mcpClient, mcp.timeout) : [] + if (!listed) { + return yield* Effect.fail(new Error("Failed to get tools")) + } + return { mcpClient, status, defs: listed } satisfies CreateResult + }).pipe( + Effect.catchCause((cause) => + Effect.tryPromise(() => mcpClient.close()).pipe(Effect.ignore, Effect.andThen(Effect.failCause(cause))), + ), + ) + }, + Effect.map((result): CreateResult => result), + Effect.catchCause((cause) => { + if (Cause.hasInterruptsOnly(cause)) return Effect.interrupt + const error = Cause.squash(cause) + return Effect.succeed({ + status: { status: "failed", error: error instanceof Error ? error.message : String(error) }, + }) + }), + ) const cfgSvc = yield* Config.Service const descendants = Effect.fnUntraced( @@ -537,9 +551,7 @@ export const layer = Layer.effect( return } - const result = yield* create(key, mcp).pipe(Effect.catch(() => Effect.void)) - if (!result) return - + const result = yield* create(key, mcp) s.status[key] = result.status if (result.mcpClient) { s.clients[key] = result.mcpClient diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index d4ae15c26b..da525d166e 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -8,6 +8,7 @@ import { testEffect } from "../lib/effect" // Per-client state for controlling mock behavior interface MockClientState { capabilities: { tools?: object; prompts?: object; resources?: object } + capabilitiesShouldThrow: boolean tools: Array<{ name: string; description?: string; inputSchema: object; outputSchema?: object }> listToolsCalls: number listPromptsCalls: number @@ -51,6 +52,7 @@ function getOrCreateClientState(name?: string): MockClientState { if (!state) { state = { capabilities: { tools: {}, prompts: {}, resources: {} }, + capabilitiesShouldThrow: false, tools: [{ name: "test_tool", description: "A test tool", inputSchema: { type: "object", properties: {} } }], listToolsCalls: 0, listPromptsCalls: 0, @@ -155,6 +157,7 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({ } getServerCapabilities() { + if (this._state?.capabilitiesShouldThrow) throw new Error("capability discovery failed") return this._state?.capabilities } @@ -566,6 +569,31 @@ it.instance( }, ) +it.instance( + "returns failed and closes the client when SDK initialization throws", + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "defective-server" + const serverState = getOrCreateClientState("defective-server") + serverState.capabilitiesShouldThrow = true + + const result = yield* mcp.add("defective-server", { + type: "local", + command: ["echo", "test"], + }) + + expect(statusName(result.status, "defective-server")).toBe("failed") + expect((yield* mcp.status())["defective-server"]).toEqual({ + status: "failed", + error: "capability discovery failed", + }) + expect(serverState.closed).toBe(true) + }), + ), + { config: { mcp: {} } }, +) + it.instance( "falls back when MCP output schema refs fail SDK tool discovery", () => From 954d618790816232505fca58848398dac969b484 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:46:01 -0500 Subject: [PATCH 004/157] fix(opencode): support Anthropic fallback responses (#31611) --- bun.lock | 16 +++++++--------- bunfig.toml | 2 +- packages/console/function/package.json | 2 +- packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/bun.lock b/bun.lock index 0aa3c67193..c3f1aed44e 100644 --- a/bun.lock +++ b/bun.lock @@ -175,7 +175,7 @@ "name": "@opencode-ai/console-function", "version": "1.17.0", "dependencies": { - "@ai-sdk/anthropic": "3.0.64", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", "@ai-sdk/openai-compatible": "2.0.37", "@openauthjs/openauth": "0.0.0-20250322224806", @@ -246,7 +246,7 @@ "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.112", - "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.49", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", @@ -504,7 +504,7 @@ "@agentclientprotocol/sdk": "0.21.0", "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.112", - "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.49", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", @@ -1014,7 +1014,7 @@ "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.112", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/openai": "3.0.67", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-PsSh7a6qW+3kQXPs1kD4wDwuZby0t1PIaB6j/1aMKmPFJ5LxcIcULLMF/bjITLt5o/8lc0t6TXIwG0zlhH7uZw=="], - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.82", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A=="], "@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="], @@ -5446,7 +5446,9 @@ "@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], + + "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], @@ -5808,8 +5810,6 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - "@opencode-ai/core/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="], - "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], "@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], @@ -6154,8 +6154,6 @@ "nypm/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], - "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="], - "opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], diff --git a/bunfig.toml b/bunfig.toml index 283e9fbfe7..23cfb62bbe 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -2,7 +2,7 @@ exact = true # Only install newly resolved package versions published at least 3 days ago. minimumReleaseAge = 259200 -minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "gitlab-ai-provider", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64"] +minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "gitlab-ai-provider", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64"] [test] root = "./do-not-run-tests-from-root" diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 960759e864..fd5d768907 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -17,7 +17,7 @@ "@typescript/native-preview": "catalog:" }, "dependencies": { - "@ai-sdk/anthropic": "3.0.64", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", "@ai-sdk/openai-compatible": "2.0.37", "@opencode-ai/console-core": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index ef527f634f..3ca96188c6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -62,7 +62,7 @@ "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.112", - "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.49", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d9652649bf..78240c6861 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -57,7 +57,7 @@ "@agentclientprotocol/sdk": "0.21.0", "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.112", - "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.49", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", From 174ab583434032688758d5d96035d18d46fe9f55 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:58:40 -0500 Subject: [PATCH 005/157] fix(mcp): apply timeouts to prompts and resources (#31612) --- packages/opencode/src/mcp/index.ts | 25 +++++++++----- packages/opencode/test/mcp/lifecycle.test.ts | 35 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 5ce4719ee5..d40594bace 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -740,7 +740,7 @@ export const layer = Layer.effect( const withClient = Effect.fnUntraced(function* ( clientName: string, - fn: (client: MCPClient) => Promise, + fn: (client: MCPClient, timeout?: number) => Promise, label: string, meta?: Record, ) { @@ -750,8 +750,11 @@ export const layer = Layer.effect( yield* Effect.logWarning(`client not found for ${label}`, { clientName }) return undefined } + const cfg = yield* cfgSvc.get() + const configured = cfg.mcp?.[clientName] + const staticTimeout = configured && isMcpConfigured(configured) ? configured.timeout : undefined return yield* Effect.tryPromise({ - try: () => fn(client), + try: () => fn(client, s.config[clientName]?.timeout ?? staticTimeout ?? cfg.experimental?.mcp_timeout), catch: (error) => error, }).pipe( Effect.tapError((error) => @@ -770,15 +773,21 @@ export const layer = Layer.effect( name: string, args?: Record, ) { - return yield* withClient(clientName, (client) => client.getPrompt({ name, arguments: args }), "getPrompt", { - promptName: name, - }) + return yield* withClient( + clientName, + (client, timeout) => client.getPrompt({ name, arguments: args }, { timeout }), + "getPrompt", + { promptName: name }, + ) }) const readResource = Effect.fn("MCP.readResource")(function* (clientName: string, resourceUri: string) { - return yield* withClient(clientName, (client) => client.readResource({ uri: resourceUri }), "readResource", { - resourceUri, - }) + return yield* withClient( + clientName, + (client, timeout) => client.readResource({ uri: resourceUri }, { timeout }), + "readResource", + { resourceUri }, + ) }) const getMcpConfig = Effect.fnUntraced(function* (mcpName: string) { diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index da525d166e..004909fb6e 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -13,6 +13,8 @@ interface MockClientState { listToolsCalls: number listPromptsCalls: number listResourcesCalls: number + getPromptTimeout?: number + readResourceTimeout?: number requestCalls: number listToolsShouldFail: boolean listToolsError: string @@ -206,6 +208,16 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({ return { resources: this._state?.resources ?? [] } } + async getPrompt(_params: unknown, options?: { timeout?: number }) { + if (this._state) this._state.getPromptTimeout = options?.timeout + return { messages: [] } + } + + async readResource(params: { uri: string }, options?: { timeout?: number }) { + if (this._state) this._state.readResourceTimeout = options?.timeout + return { contents: [{ uri: params.uri, text: "test" }] } + } + async close() { if (this._state) this._state.closed = true } @@ -758,6 +770,29 @@ it.instance( }, ) +it.instance( + "uses per-server timeouts for prompt and resource requests", + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "timeout-server" + const serverState = getOrCreateClientState("timeout-server") + + yield* mcp.add("timeout-server", { + type: "local", + command: ["echo", "test"], + timeout: 2500, + }) + yield* mcp.getPrompt("timeout-server", "test") + yield* mcp.readResource("timeout-server", "test://resource") + + expect(serverState.getPromptTimeout).toBe(2500) + expect(serverState.readResourceTimeout).toBe(2500) + }), + ), + { config: { mcp: {}, experimental: { mcp_timeout: 5000 } } }, +) + it.instance( "resource-only servers connect without listing tools", () => From 5e342f711467068548d408be13a0e134182689a3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 10 Jun 2026 05:00:09 +0000 Subject: [PATCH 006/157] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index eada8b6a90..f8b1c26627 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-jm6Xfv4q9qD+00QQYBlgKBJhn4G2mlJdCkbKXxYdfOk=", - "aarch64-linux": "sha256-V0+Rsj4XBqZkwj5Pioqt330t6MXDhB30NFcgxnNKuvU=", - "aarch64-darwin": "sha256-1RGsodkMU7jAqUWoUZIxGIYr5Q1qvvru/F51Ay00ypw=", - "x86_64-darwin": "sha256-HwhgDwb6P2C7WvekGyyejSbxT2P8C7v7eMvcxF+Q6Ao=" + "x86_64-linux": "sha256-q/4M9kQ0VlNepcHuLc9zxNPWkxFfUlw/7YlqEFRpx3g=", + "aarch64-linux": "sha256-HGqFQpr81+Bh8KHkDcDSphsn+NzQ7sl2OQbyYjtzaQM=", + "aarch64-darwin": "sha256-dqHXeuav5D0x7KuDVfuCMOlriSqPL19aZeScA7gWZRc=", + "x86_64-darwin": "sha256-yK3hzJF1FuC3KfmMYgG2zgyn2T2pRAlfG6M0P2ghPsA=" } } From e0449c0b9647c60810ccfa72a6367051a37ac359 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:49:11 +1000 Subject: [PATCH 007/157] fix(desktop): restore macOS auto-updates (#31621) --- bun.lock | 136 ++-------------------------------- bunfig.toml | 2 +- packages/desktop/package.json | 2 +- 3 files changed, 8 insertions(+), 132 deletions(-) diff --git a/bun.lock b/bun.lock index c3f1aed44e..b2a1b15ff4 100644 --- a/bun.lock +++ b/bun.lock @@ -357,7 +357,7 @@ "@typescript/native-preview": "catalog:", "@valibot/to-json-schema": "1.6.0", "electron": "42.3.3", - "electron-builder": "26.15.0", + "electron-builder": "26.15.2", "electron-vite": "^5", "solid-js": "catalog:", "sury": "11.0.0-alpha.4", @@ -1340,7 +1340,7 @@ "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="], - "@electron/rebuild": ["@electron/rebuild@4.0.3", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "got": "^11.7.0", "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^11.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^7.5.6", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA=="], + "@electron/rebuild": ["@electron/rebuild@4.0.4", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^12.2.0", "read-binary-file-arch": "^1.0.6" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg=="], "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="], @@ -2864,7 +2864,7 @@ "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="], - "app-builder-lib": ["app-builder-lib@26.15.0", "", { "dependencies": { "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@noble/hashes": "^2.2.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", "builder-util": "26.15.0", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.15.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.2.5", "pkijs": "^3.4.0", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "unzipper": "^0.12.3", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.15.0", "electron-builder-squirrel-windows": "26.15.0" } }, "sha512-j2+P6Lh+l/VuWfXZWSs7u+OAPqYJQGnZZO30M833XQQaRuyohm4RZk7Gw4nQXfeyQH9GqXaTwR16Y0LaVTlS+g=="], + "app-builder-lib": ["app-builder-lib@26.15.2", "", { "dependencies": { "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@noble/hashes": "^2.2.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", "builder-util": "26.15.0", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.15.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.2.5", "pkijs": "^3.4.0", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "unzipper": "^0.12.3", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.15.2", "electron-builder-squirrel-windows": "26.15.2" } }, "sha512-3mYfKOjr/ZY7gFESOcq8kylBMgGPpmlQYnpBVit4p6zIg0t/8bkWBILdMMtnjFyN2jllyBf225T8dLlz3D6oBQ=="], "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], @@ -2986,8 +2986,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], @@ -3106,8 +3104,6 @@ "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], - "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], - "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], @@ -3252,8 +3248,6 @@ "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], - "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], - "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], @@ -3306,7 +3300,7 @@ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - "dmg-builder": ["dmg-builder@26.15.0", "", { "dependencies": { "app-builder-lib": "26.15.0", "builder-util": "26.15.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } }, "sha512-oS8MWttbpIUF/2v8LOEY+f4ayL84ipMOarZvdRMl/pxlhLxAYjYMklTXHEXIl37Ig+qJv/bVF7HgyIoOoZyMWA=="], + "dmg-builder": ["dmg-builder@26.15.2", "", { "dependencies": { "app-builder-lib": "26.15.2", "builder-util": "26.15.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } }, "sha512-fMkjRqKyPtsz4Kzu/qGP0BGjqzMCIgp+/7kw/u6YH6lvn/8hvL3c0TXhoFayBoYdpPCnEinnCHztd4bW7/jetA=="], "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], @@ -3356,7 +3350,7 @@ "electron": ["electron@42.3.3", "", { "dependencies": { "@electron/get": "^5.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-0MwYp9wTb7TrtTalOYqeW+suqd9T/Znstr/nDLKqFGIjHdBZX339guo3mQqTPURRZ/UQmYM4uMpzKpI5wLptfQ=="], - "electron-builder": ["electron-builder@26.15.0", "", { "dependencies": { "app-builder-lib": "26.15.0", "builder-util": "26.15.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.15.0", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-zd4cfvjHmtyGqMaDudg5rAjNUkwIJDz8ICaCsz77hFKcjMQHcZNNNCs/C4phwN9+gEVwmhvpKMzNFum6fs/n6A=="], + "electron-builder": ["electron-builder@26.15.2", "", { "dependencies": { "app-builder-lib": "26.15.2", "builder-util": "26.15.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.15.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-veKM9+dCljaC5A74Pwc0ZWQ9arOHREXWh9hUIf8NGg49ch7x+IB4QhbMzIrV5ONZIXM2OEkaxW11cAPjPtoi4A=="], "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA=="], @@ -3368,7 +3362,7 @@ "electron-log": ["electron-log@5.4.4", "", {}, "sha512-istWgaXjBfURBSS8LWVW9C3jsc6+ac+tY1lXrQEOTp0lVj+a4OlO1Tmqb36GgnEUDv92DGC9VI1HNXwJinWpgA=="], - "electron-publish": ["electron-publish@26.15.0", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.15.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-pt6K3ol/a+o3HbqmYkL2NYlVH5pd34tL4FPRcgX8E88xQAqQyIsseXe4vWy7Pq2BaYy+iFGJrtInZe11FFAQwQ=="], + "electron-publish": ["electron-publish@26.15.1", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.15.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-BMgMHOyexWn0UnOC+Afffw0DMrr0yfLp4U8YsLXwoJ3Da7LS7WUnz21teYZqO0gaApE1KgsjREWmbPqvF5JcPg=="], "electron-store": ["electron-store@11.0.2", "", { "dependencies": { "conf": "^15.0.2", "type-fest": "^5.0.1" } }, "sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ=="], @@ -3788,8 +3782,6 @@ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], @@ -3864,8 +3856,6 @@ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], - "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], - "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -3894,8 +3884,6 @@ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], - "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], - "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -4058,8 +4046,6 @@ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], - "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], - "loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -4410,8 +4396,6 @@ "opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="], - "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], - "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="], @@ -4754,8 +4738,6 @@ "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], - "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], - "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], @@ -5186,10 +5168,6 @@ "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], - "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], - - "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], - "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], @@ -5312,8 +5290,6 @@ "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], - "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], @@ -5696,12 +5672,6 @@ "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], - "@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], - - "@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - "@electron/universal/fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], "@electron/universal/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -5982,10 +5952,6 @@ "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - - "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], @@ -6012,8 +5978,6 @@ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "defaults/clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], - "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "dir-compare/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -6116,8 +6080,6 @@ "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], @@ -6166,12 +6128,6 @@ "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - - "ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], @@ -6210,10 +6166,6 @@ "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], - "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], @@ -6444,20 +6396,6 @@ "@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], - "@electron/rebuild/node-gyp/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - - "@electron/rebuild/node-gyp/make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], - - "@electron/rebuild/node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], - - "@electron/rebuild/node-gyp/proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], - - "@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], - - "@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "@electron/universal/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], @@ -6788,8 +6726,6 @@ "electron-builder-squirrel-windows/app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], - "electron-builder-squirrel-windows/app-builder-lib/@electron/rebuild": ["@electron/rebuild@4.0.4", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^12.2.0", "read-binary-file-arch": "^1.0.6" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg=="], - "electron-builder-squirrel-windows/app-builder-lib/builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], "electron-builder-squirrel-windows/app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], @@ -6852,8 +6788,6 @@ "motion/framer-motion/motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], - "ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -6862,8 +6796,6 @@ "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -7022,26 +6954,6 @@ "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@electron/rebuild/node-gyp/make-fetch-happen/@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], - - "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], - - "@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - - "@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "@electron/rebuild/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "@electron/rebuild/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -7252,20 +7164,6 @@ "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], - "@electron/rebuild/node-gyp/make-fetch-happen/@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], - - "@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -7304,14 +7202,6 @@ "tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -7319,19 +7209,5 @@ "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], } } diff --git a/bunfig.toml b/bunfig.toml index 23cfb62bbe..b914f4b67b 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -2,7 +2,7 @@ exact = true # Only install newly resolved package versions published at least 3 days ago. minimumReleaseAge = 259200 -minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "gitlab-ai-provider", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64"] +minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "gitlab-ai-provider", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "app-builder-lib", "dmg-builder", "electron-builder", "electron-publish"] [test] root = "./do-not-run-tests-from-root" diff --git a/packages/desktop/package.json b/packages/desktop/package.json index f462070cdb..38977ce918 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -49,7 +49,7 @@ "@typescript/native-preview": "catalog:", "@valibot/to-json-schema": "1.6.0", "electron": "42.3.3", - "electron-builder": "26.15.0", + "electron-builder": "26.15.2", "electron-vite": "^5", "solid-js": "catalog:", "sury": "11.0.0-alpha.4", From 826419127ae0c2b742b9db866c4a9afb27a5ae2c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 10 Jun 2026 06:07:15 +0000 Subject: [PATCH 008/157] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index f8b1c26627..1bbea78c1b 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-q/4M9kQ0VlNepcHuLc9zxNPWkxFfUlw/7YlqEFRpx3g=", - "aarch64-linux": "sha256-HGqFQpr81+Bh8KHkDcDSphsn+NzQ7sl2OQbyYjtzaQM=", - "aarch64-darwin": "sha256-dqHXeuav5D0x7KuDVfuCMOlriSqPL19aZeScA7gWZRc=", - "x86_64-darwin": "sha256-yK3hzJF1FuC3KfmMYgG2zgyn2T2pRAlfG6M0P2ghPsA=" + "x86_64-linux": "sha256-nix3Ogrt4nFv+HSZpYZ3VqIQc+g1SdP+DSgu72Yjoqw=", + "aarch64-linux": "sha256-L634j49jyNoKhC1MDyF+Grxy++yhWgcbXsxKDgYhKAk=", + "aarch64-darwin": "sha256-ha692TeekwiV0irhIxdwE8/1x1bLKtzSwcjDvetiPqM=", + "x86_64-darwin": "sha256-JRwKof0cTFwOcOyLb6l+kF/HlCiFmMWzG1y3U1esyyM=" } } From 97e713e8aac75a0254c34d134f0608af5cb4935c Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 10 Jun 2026 04:05:03 -0400 Subject: [PATCH 009/157] zen: deepseek v4 pro --- packages/web/src/content/docs/ar/zen.mdx | 4 +++- packages/web/src/content/docs/bs/zen.mdx | 4 +++- packages/web/src/content/docs/da/zen.mdx | 4 +++- packages/web/src/content/docs/de/zen.mdx | 4 +++- packages/web/src/content/docs/es/zen.mdx | 4 +++- packages/web/src/content/docs/fr/zen.mdx | 4 +++- packages/web/src/content/docs/it/zen.mdx | 4 +++- packages/web/src/content/docs/ja/zen.mdx | 4 +++- packages/web/src/content/docs/ko/zen.mdx | 4 +++- packages/web/src/content/docs/nb/zen.mdx | 4 +++- packages/web/src/content/docs/pl/zen.mdx | 4 +++- packages/web/src/content/docs/pt-br/zen.mdx | 4 +++- packages/web/src/content/docs/ru/zen.mdx | 4 +++- packages/web/src/content/docs/th/zen.mdx | 4 +++- packages/web/src/content/docs/tr/zen.mdx | 4 +++- packages/web/src/content/docs/zen.mdx | 4 +++- packages/web/src/content/docs/zh-cn/zen.mdx | 4 +++- packages/web/src/content/docs/zh-tw/zen.mdx | 4 +++- 18 files changed, 54 insertions(+), 18 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index f8dd274d24..9ab89b935b 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -90,6 +90,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -139,7 +140,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 706c5993ef..51191e4099 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -95,6 +95,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 6d0bbbbad6..b306deea65 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -95,6 +95,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 434ba94ac5..c2ea51e555 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -86,6 +86,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 5eec9d2d53..bdec02a2d2 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -95,6 +95,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 8901f8d906..50ce5fa312 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -86,6 +86,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 46624c618a..fb99529e60 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -95,6 +95,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 37114b5c67..8b6958bf2b 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -86,6 +86,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index fa50766495..16b915f4ec 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -86,6 +86,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 12410210c7..11c7400e99 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -95,6 +95,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 698c2f7133..4516594cdc 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -95,6 +95,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index c33b7d83dc..c0b6faba09 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -86,6 +86,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index dad83c2dd0..a47b579a43 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -95,6 +95,7 @@ OpenCode Zen работает как любой другой провайдер | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 12c2b1f6dc..79cd87bc48 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -88,6 +88,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -137,7 +138,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 33b9f6d34f..84ba1c89da 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -86,6 +86,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index cff6ab545e..caa609912b 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -95,6 +95,7 @@ You can also access our models through the following API endpoints. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -146,7 +147,8 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 14765d7d6a..489ee60a67 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -86,6 +86,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -135,7 +136,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 93c4465f4f..6a271da570 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -90,6 +90,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -140,7 +141,8 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.03 | - | +| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | +| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | | Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | | Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | From 90fb32be30f12ea8c552980ddba8269ae9e45cf9 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:32:35 +1000 Subject: [PATCH 010/157] fix(core): accept deprecated reference config key (#31659) --- .opencode/opencode.jsonc | 4 ++- packages/core/src/v1/config/config.ts | 3 ++ packages/core/src/v1/config/migrate.ts | 3 +- packages/core/test/config/config.test.ts | 38 ++++++++++++++++++++ packages/opencode/test/config/config.test.ts | 20 +++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 3 ++ 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index b0f7d59447..ae68a477dc 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -2,7 +2,9 @@ "$schema": "https://opencode.ai/config.json", "provider": {}, "permission": {}, - "references": { + // TODO: flip back to `references` once a release containing the v1 `reference` migration ships. + // The release pipeline runs the latest published opencode against this file, which only knows `reference`. + "reference": { "effect": { "repository": "github.com/Effect-TS/effect-smol", "description": "Use for Effect v4 and effect-smol implementation details", diff --git a/packages/core/src/v1/config/config.ts b/packages/core/src/v1/config/config.ts index 5c520846da..2e773f71e2 100644 --- a/packages/core/src/v1/config/config.ts +++ b/packages/core/src/v1/config/config.ts @@ -45,6 +45,9 @@ export const Info = Schema.Struct({ references: Schema.optional(ConfigReference.Info).annotate({ description: "Named git or local directory references", }), + reference: Schema.optional(ConfigReference.Info).annotate({ + description: "@deprecated Use 'references' field instead. Named git or local directory references", + }), watcher: Schema.optional(Schema.Struct({ ignore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))) })), snapshot: Schema.optional(Schema.Boolean).annotate({ description: diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 19bf48aa74..3b4f13868e 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -12,6 +12,7 @@ const keys = new Set([ "logLevel", "server", "command", + "reference", "snapshot", "plugin", "autoshare", @@ -62,7 +63,7 @@ export function migrate(info: typeof ConfigV1.Info.Type) { skills: info.skills && [...(info.skills.paths ?? []), ...(info.skills.urls ?? [])], commands: info.command, instructions: info.instructions, - references: info.references, + references: info.references ?? info.reference, plugins: info.plugin?.map((plugin) => typeof plugin === "string" ? plugin : { package: plugin[0], options: plugin[1] }, ), diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 5f62cbce61..6275d8fed3 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -70,7 +70,9 @@ describe("Config", () => { Effect.sync(() => { expect(ConfigMigrateV1.isV1({ snapshot: false })).toBe(true) expect(ConfigMigrateV1.isV1({ snapshot: false, agents: {} })).toBe(true) + expect(ConfigMigrateV1.isV1({ reference: {} })).toBe(true) expect(ConfigMigrateV1.isV1({ shell: "/bin/zsh", model: "anthropic/claude" })).toBe(false) + expect(ConfigMigrateV1.isV1({ references: {} })).toBe(false) }), ) @@ -431,6 +433,42 @@ describe("Config", () => { ), ) + it.live("migrates the deprecated reference key into references", () => + 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, "opencode.json"), + JSON.stringify({ + reference: { + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }, + }), + ), + ) + + return yield* Effect.gen(function* () { + const config = yield* Config.Service + const documents = (yield* config.entries()).filter((entry) => entry.type === "document") + + expect(documents).toHaveLength(1) + expect(documents[0]?.info.references).toEqual({ + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }) + }).pipe(Effect.provide(testLayer(tmp.path))) + }), + ), + ), + ) + it.live("migrates v1 configuration when a v1-only key is present", () => Effect.acquireRelease( Effect.promise(() => tmpdir()), diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index c016431bc7..f4a0cd4a28 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -722,6 +722,26 @@ it.instance("migrates mode field to agent field", () => }), ) +it.instance("accepts the deprecated reference field", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + reference: { + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }, + }) + const config = yield* Config.use.get() + expect(config.reference).toEqual({ + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }) + }), +) + it.instance("loads config from .opencode directory", () => Effect.gen(function* () { const test = yield* TestInstance diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 9e9c74c6ec..7f5e765a41 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1931,6 +1931,9 @@ export type Config = { references?: { [key: string]: string | ConfigV2ReferenceGit | ConfigV2ReferenceLocal } + reference?: { + [key: string]: string | ConfigV2ReferenceGit | ConfigV2ReferenceLocal + } watcher?: { ignore?: Array } From 2cf68f32b53ba48f9d56d739e839065c2729f4ed Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 10 Jun 2026 10:34:23 +0000 Subject: [PATCH 011/157] chore: generate --- packages/sdk/openapi.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 3bb92d700f..1dffe1ef5d 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -18234,6 +18234,22 @@ ] } }, + "reference": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/ConfigV2ReferenceGit" + }, + { + "$ref": "#/components/schemas/ConfigV2ReferenceLocal" + } + ] + } + }, "watcher": { "type": "object", "properties": { From 4c9abff445e827b1fb350429d7e14dafa0cb1996 Mon Sep 17 00:00:00 2001 From: opencode Date: Wed, 10 Jun 2026 11:37:58 +0000 Subject: [PATCH 012/157] sync release versions for v1.17.1 --- bun.lock | 52 ++++++++++----------- packages/app/package.json | 2 +- packages/cli/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/console/support/package.json | 2 +- packages/core/package.json | 2 +- packages/desktop/package.json | 2 +- packages/effect-drizzle-sqlite/package.json | 2 +- packages/effect-sqlite-node/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/function/package.json | 2 +- packages/http-recorder/package.json | 2 +- packages/llm/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/server/package.json | 2 +- packages/slack/package.json | 2 +- packages/stats/app/package.json | 2 +- packages/stats/core/package.json | 2 +- packages/stats/server/package.json | 2 +- packages/tui/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 28 files changed, 53 insertions(+), 53 deletions(-) diff --git a/bun.lock b/bun.lock index b2a1b15ff4..cbc4e7f7e7 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -85,7 +85,7 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.17.0", + "version": "1.17.1", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -110,7 +110,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -146,7 +146,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -173,7 +173,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -195,7 +195,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -219,7 +219,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -239,7 +239,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.17.0", + "version": "1.17.1", "bin": { "opencode": "./bin/opencode", }, @@ -330,7 +330,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@zip.js/zip.js": "2.7.62", "effect": "catalog:", @@ -384,7 +384,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -398,7 +398,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "effect": "catalog:", }, @@ -410,7 +410,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -441,7 +441,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -457,7 +457,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@effect/platform-node": "4.0.0-beta.74", "@effect/platform-node-shared": "4.0.0-beta.74", @@ -476,7 +476,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -494,7 +494,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.17.0", + "version": "1.17.1", "bin": { "opencode": "./bin/opencode", }, @@ -622,7 +622,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -660,7 +660,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "cross-spawn": "catalog:", }, @@ -675,7 +675,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@opencode-ai/core": "workspace:*", "drizzle-orm": "catalog:", @@ -689,7 +689,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -702,7 +702,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@ibm/plex": "6.4.1", "@opencode-ai/stats-core": "workspace:*", @@ -735,7 +735,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -754,7 +754,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -794,7 +794,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", @@ -822,7 +822,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -871,7 +871,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index b2e729baec..46ef54c8fa 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.17.0", + "version": "1.17.1", "description": "", "type": "module", "exports": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 5ca011a88d..db71b46ea5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/cli", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "bin": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index fb6516e23c..90a33f3739 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 8de0eb9ea0..8f2af5f79b 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index fd5d768907..daaf063d56 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.17.0", + "version": "1.17.1", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 9f5ca70bed..fe7991eec1 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.17.0", + "version": "1.17.1", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/support/package.json b/packages/console/support/package.json index 00ad843980..e2d6296407 100644 --- a/packages/console/support/package.json +++ b/packages/console/support/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-support", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 3ca96188c6..723e7ee8fe 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.0", + "version": "1.17.1", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 38977ce918..ef1ba07738 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index c54bc51d3b..8ae0b1037e 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.0", + "version": "1.17.1", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index 1eceee8d7b..c1c49df152 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.0", + "version": "1.17.1", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 3d5f62cb09..9c85f5aea0 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/function/package.json b/packages/function/package.json index 8ef0a883b4..30ce5920e6 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.17.0", + "version": "1.17.1", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index b1843b9929..db40e0a229 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.0", + "version": "1.17.1", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/llm/package.json b/packages/llm/package.json index 6f5a2427f0..8118c79d8a 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.0", + "version": "1.17.1", "name": "@opencode-ai/llm", "type": "module", "license": "MIT", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 78240c6861..229739e312 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.0", + "version": "1.17.1", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index cf3707526c..ae6ce12cf6 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 88ac1d7ccb..5970822f84 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 88db3dd742..639c853c8d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/server", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 4ac2380428..621680eac3 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/stats/app/package.json b/packages/stats/app/package.json index 3e184fb618..c2f25df0e3 100644 --- a/packages/stats/app/package.json +++ b/packages/stats/app/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-app", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/core/package.json b/packages/stats/core/package.json index 2a9899dfc1..d26d6bbb81 100644 --- a/packages/stats/core/package.json +++ b/packages/stats/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-core", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/server/package.json b/packages/stats/server/package.json index 3cdd064df8..33438363c7 100644 --- a/packages/stats/server/package.json +++ b/packages/stats/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-server", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/tui/package.json b/packages/tui/package.json index 7b4729c5d3..266aa4a6c0 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/tui", - "version": "1.17.0", + "version": "1.17.1", "private": true, "type": "module", "license": "MIT", diff --git a/packages/ui/package.json b/packages/ui/package.json index 68826465b3..06609b9f6d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.17.0", + "version": "1.17.1", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 739c786993..0b5a2355d4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.17.0", + "version": "1.17.1", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index a54c9b6e5d..172b4cfd9a 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.17.0", + "version": "1.17.1", "publisher": "sst-dev", "repository": { "type": "git", From 5863e1254c13ef42d43a378f19a2adabc511788f Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 10 Jun 2026 10:13:54 -0400 Subject: [PATCH 013/157] put fff behind flag --- packages/core/src/filesystem/search.ts | 3 ++- packages/core/src/flag/flag.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index 850e0c1d5d..4280631908 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -9,6 +9,7 @@ import { FSUtil } from "../fs-util" import { Location } from "../location" import { Ripgrep } from "../ripgrep" import { RelativePath } from "../schema" +import { Flag } from "../flag/flag" export interface Interface { readonly find: (input: FileSystem.FindInput) => Effect.Effect @@ -236,4 +237,4 @@ export const fffLayer = Layer.effect( }), ) -export const defaultLayer = Layer.unwrap(Effect.sync(() => (Fff.available() ? fffLayer : ripgrepLayer))) +export const defaultLayer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_ENABLE_FFF ? fffLayer : ripgrepLayer))) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index b8b655c883..da0da4f212 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -30,6 +30,7 @@ export const Flag = { OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"], OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"], OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"], + OPENCODE_ENABLE_FFF: process.env["OPENCODE_ENABLE_FFF"], // Experimental OPENCODE_EXPERIMENTAL_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_FILEWATCHER").pipe( From c9e2a38bf42fda4813762b9ca2deb1edc5d62f89 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:14:27 -0500 Subject: [PATCH 014/157] ci: change model from gpt-5.4-nano to gpt-5.4-mini (#31695) --- .opencode/agent/triage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.opencode/agent/triage.md b/.opencode/agent/triage.md index 03df339cb8..11c4c816cf 100644 --- a/.opencode/agent/triage.md +++ b/.opencode/agent/triage.md @@ -1,7 +1,7 @@ --- mode: primary hidden: true -model: opencode/gpt-5.4-nano +model: opencode/gpt-5.4-mini color: "#44BA81" tools: "*": false From 1dad38d1b58ad88b239328f5897b60a4703ca9cc Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 10 Jun 2026 10:19:40 -0400 Subject: [PATCH 015/157] fix(core): do not gate fff on initial scan --- packages/core/src/filesystem/search.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index 4280631908..de13c37eb8 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -139,11 +139,6 @@ export const fffLayer = Layer.effect( }).pipe(Effect.orDie) if (!result.ok) return yield* Effect.die(result.error) yield* Effect.addFinalizer(() => Effect.sync(() => result.value.destroy()).pipe(Effect.ignore)) - const scanned = yield* Effect.tryPromise({ - try: () => result.value.waitForScan(5_000), - catch: (cause) => cause, - }).pipe(Effect.orDie) - if (!scanned.ok || !scanned.value) return yield* Effect.die(scanned.ok ? "fff scan timed out" : scanned.error) return Service.of({ glob: (input) => Effect.sync(() => { From 538cfaff0d4c43365417fd41cf4f387689dc58e8 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 10 Jun 2026 10:21:25 -0400 Subject: [PATCH 016/157] feat(core): enable fff by default --- packages/core/src/filesystem/search.ts | 2 +- packages/core/src/flag/flag.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index de13c37eb8..ce5f50cc6e 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -232,4 +232,4 @@ export const fffLayer = Layer.effect( }), ) -export const defaultLayer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_ENABLE_FFF ? fffLayer : ripgrepLayer))) +export const defaultLayer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_DISABLE_FFF ? ripgrepLayer : fffLayer))) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index da0da4f212..d27486c518 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -30,7 +30,7 @@ export const Flag = { OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"], OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"], OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"], - OPENCODE_ENABLE_FFF: process.env["OPENCODE_ENABLE_FFF"], + OPENCODE_DISABLE_FFF: truthy("OPENCODE_DISABLE_FFF"), // Experimental OPENCODE_EXPERIMENTAL_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_FILEWATCHER").pipe( From e4300e9b7433e068c3d57ac41fcb39bc5de3d32e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 10 Jun 2026 10:22:00 -0400 Subject: [PATCH 017/157] fix(core): disable fff by default on windows --- packages/core/src/flag/flag.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index d27486c518..a0eb78a13e 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -6,6 +6,7 @@ export function truthy(key: string) { } const copy = process.env["OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"] +const fff = process.env["OPENCODE_DISABLE_FFF"] function enabledByExperimental(key: string) { return process.env[key] === undefined ? truthy("OPENCODE_EXPERIMENTAL") : truthy(key) @@ -30,7 +31,7 @@ export const Flag = { OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"], OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"], OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"], - OPENCODE_DISABLE_FFF: truthy("OPENCODE_DISABLE_FFF"), + OPENCODE_DISABLE_FFF: fff === undefined ? process.platform === "win32" : truthy("OPENCODE_DISABLE_FFF"), // Experimental OPENCODE_EXPERIMENTAL_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_FILEWATCHER").pipe( From 3ad6923c61438faeb0c4677ee55f0301ddbe43ba Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:26:08 -0500 Subject: [PATCH 018/157] fix(opencode): let subagents use their own permissions (#31696) --- packages/opencode/src/agent/agent.ts | 3 + .../src/agent/subagent-permissions.ts | 16 +--- packages/opencode/src/tool/task.ts | 4 - packages/opencode/test/agent/agent.test.ts | 29 +++++++ .../agent/plan-mode-subagent-bypass.test.ts | 82 ++++--------------- 5 files changed, 50 insertions(+), 84 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 1424400871..b1430314ff 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -160,6 +160,9 @@ export const layer = Layer.effect( Permission.fromConfig({ question: "allow", plan_exit: "allow", + task: { + general: "deny", + }, external_directory: { [path.join(Global.Path.data, "plans", "*")]: "allow", }, diff --git a/packages/opencode/src/agent/subagent-permissions.ts b/packages/opencode/src/agent/subagent-permissions.ts index 56da42626c..b1b99b484e 100644 --- a/packages/opencode/src/agent/subagent-permissions.ts +++ b/packages/opencode/src/agent/subagent-permissions.ts @@ -1,31 +1,23 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" -import type { Permission } from "../permission" import type { Agent } from "./agent" /** * Build the `permission` ruleset for a subagent's session when it's spawned * via the task tool. Combines: * - * 1. The parent **agent's** edit-class deny rules — Plan Mode's file-edit - * restriction lives on the agent ruleset, not on the session, so a - * subagent that only inherited the parent SESSION's permission would - * silently bypass it. (#26514) - * 2. The parent **session's** deny rules and external_directory rules — - * same forwarding the original code already did. - * 3. Default `todowrite` and `task` denies if the subagent's own ruleset + * 1. The parent session's deny rules and external_directory rules. + * Parent agent restrictions only govern that agent; the subagent's own + * permissions determine its capabilities. + * 2. Default `todowrite` and `task` denies if the subagent's own ruleset * doesn't already permit them. */ export function deriveSubagentSessionPermission(input: { parentSessionPermission: PermissionV1.Ruleset - parentAgent: Agent.Info | undefined subagent: Agent.Info }): PermissionV1.Ruleset { const canTask = input.subagent.permission.some((rule) => rule.permission === "task") const canTodo = input.subagent.permission.some((rule) => rule.permission === "todowrite") - const parentAgentDenies = - input.parentAgent?.permission.filter((rule) => rule.action === "deny" && rule.permission === "edit") ?? [] return [ - ...parentAgentDenies, ...input.parentSessionPermission.filter( (rule) => rule.permission === "external_directory" || rule.action === "deny", ), diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index dac340184c..b0a866c90e 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -122,12 +122,8 @@ export const TaskTool = Tool.define( ? yield* sessions.get(SessionID.make(params.task_id)).pipe(Effect.catchCause(() => Effect.succeed(undefined))) : undefined const parent = yield* sessions.get(ctx.sessionID) - const parentAgent = parent.agent - ? yield* agent.get(parent.agent).pipe(Effect.catchCause(() => Effect.succeed(undefined))) - : undefined const childPermission = deriveSubagentSessionPermission({ parentSessionPermission: parent.permission ?? [], - parentAgent, subagent: next, }) const childToolDenies = [ diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 67071bce73..1df95b5c0f 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -85,6 +85,35 @@ it.instance("plan agent denies edits except .opencode/plans/*", () => }), ) +it.instance("plan agent denies the general subagent by default", () => + Effect.gen(function* () { + const plan = yield* load((svc) => svc.get("plan")) + expect(plan).toBeDefined() + expect(Permission.evaluate("task", "general", plan!.permission).action).toBe("deny") + expect(Permission.evaluate("task", "explore", plan!.permission).action).toBe("allow") + expect(Permission.evaluate("task", "custom", plan!.permission).action).toBe("allow") + }), +) + +it.instance( + "user permission can allow the general subagent from plan mode", + () => + Effect.gen(function* () { + const plan = yield* load((svc) => svc.get("plan")) + expect(plan).toBeDefined() + expect(Permission.evaluate("task", "general", plan!.permission).action).toBe("allow") + }), + { + config: { + permission: { + task: { + general: "allow", + }, + }, + }, + }, +) + it.instance("explore agent denies edit and write", () => Effect.gen(function* () { const explore = yield* load((svc) => svc.get("explore")) diff --git a/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts b/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts index de0e2cd46a..a58a5ddf2f 100644 --- a/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts +++ b/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts @@ -1,24 +1,4 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" -/** - * Reproducer for opencode issue #26514: - * - * In Plan Mode (the `plan` agent), the main agent's edit/write tools are - * blocked by the plan agent's permission ruleset (`edit: { "*": "deny" }`). - * However, when the plan agent spawns a subagent via the `task` tool, the - * subagent retains full file modification capabilities — a security bypass. - * - * This test replicates the permission ruleset that would govern a - * `general` subagent when launched from a `plan` parent session, mirroring - * the logic in `src/tool/task.ts` (filtered parent permissions ++ runtime - * subagent agent permissions, evaluated as in `session/prompt.ts`). - * - * The expected (secure) behavior is that the subagent inherits the plan - * mode read-only restriction and `edit`/`write` resolve to `deny`. On - * origin/dev this assertion fails because the parent **agent** permissions - * are not propagated to the subagent — only the parent **session** - * permissions are passed through, and Plan Mode's restrictions live on the - * agent, not the session. - */ import { expect } from "bun:test" import { Effect } from "effect" import { Agent } from "../../src/agent/agent" @@ -45,7 +25,7 @@ function testAgent(input: { // exercises the actual helper that task.ts uses to build the subagent's // session permission, so any regression in that helper trips this test. -it.instance("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", () => +it.instance("subagent permissions take precedence over parent agent restrictions", () => Effect.gen(function* () { const planAgent = yield* Agent.use.get("plan") const generalAgent = yield* Agent.use.get("general") @@ -57,15 +37,10 @@ it.instance("[#26514] subagent spawned from plan mode inherits read-only restric // tool layer — see Permission.disabled / EDIT_TOOLS.) expect(Permission.evaluate("edit", "/some/file.ts", planAgent!.permission).action).toBe("deny") - // Simulate the plan-mode parent session: in real flow the plan - // session's `permission` field is empty (Plan Mode lives on the agent - // ruleset, not the session). So we pass [] through as the parent - // session permission, exactly like the actual code path. const parentSessionPermission: PermissionV1.Ruleset = [] const subagentSessionPermission = deriveSubagentSessionPermission({ parentSessionPermission, - parentAgent: planAgent, subagent: generalAgent!, }) @@ -73,40 +48,29 @@ it.instance("[#26514] subagent spawned from plan mode inherits read-only restric // ruleset: Permission.merge(agent.permission, session.permission ?? []) const effective = Permission.merge(generalAgent!.permission, subagentSessionPermission) - expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny") - expect(Permission.evaluate("edit", "/another/path/index.tsx", effective).action).toBe("deny") + expect(Permission.evaluate("edit", "/some/file.ts", effective).action).not.toBe("deny") + expect(Permission.disabled(["edit", "write", "apply_patch"], effective)).toEqual(new Set()) }), ) -it.instance("[#26514] explore subagent launched from plan mode also stays read-only", () => - // Sibling check: even though `explore` is intrinsically read-only, the - // bug surface is the same. Including this case to document that the fix - // should propagate the parent **agent** permissions, not just deny edit - // when the subagent happens to already deny it. +it.instance("subagent's own read-only restriction remains effective", () => Effect.gen(function* () { - const planAgent = yield* Agent.use.get("plan") const explore = yield* Agent.use.get("explore") - expect(planAgent).toBeDefined() expect(explore).toBeDefined() const parentSessionPermission: PermissionV1.Ruleset = [] const subagentSessionPermission = deriveSubagentSessionPermission({ parentSessionPermission, - parentAgent: planAgent, subagent: explore!, }) const effective = Permission.merge(explore!.permission, subagentSessionPermission) - // Already deny — sanity check. expect(Permission.evaluate("edit", "/x.ts", effective).action).toBe("deny") }), ) it.instance( - "[#26514] custom user subagent launched from plan mode bypasses Plan Mode read-only", - // The most damaging case: a user-defined subagent with default - // permissions (allow-by-default, like `general`). The subagent must NOT - // be able to edit when the parent agent is `plan`. + "custom subagent can explicitly enable edits denied to its parent agent", () => Effect.gen(function* () { const planAgent = yield* Agent.use.get("plan") @@ -117,14 +81,13 @@ it.instance( const parentSessionPermission: PermissionV1.Ruleset = [] const subagentSessionPermission = deriveSubagentSessionPermission({ parentSessionPermission, - parentAgent: planAgent, subagent: my!, }) const effective = Permission.merge(my!.permission, subagentSessionPermission) - // BUG: on origin/dev edit resolves to "allow" because the plan - // agent's `edit: deny *` rule never reaches the subagent. - expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny") + expect(Permission.evaluate("edit", "/some/file.ts", planAgent!.permission).action).toBe("deny") + expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("allow") + expect(Permission.disabled(["edit", "write", "apply_patch"], effective)).toEqual(new Set()) }), { config: { @@ -132,29 +95,17 @@ it.instance( my_subagent: { description: "A user-defined subagent", mode: "subagent", + permission: { + edit: "allow", + }, }, }, }, }, ) -it.effect("[#26700] controller self-restrictions do not erase executor permissions", () => +it.effect("subagent self permissions are preserved", () => Effect.sync(() => { - const controller = testAgent({ - name: "controller", - mode: "primary", - permission: { - "*": "deny", - read: "deny", - bash: "deny", - task: { - "*": "deny", - executor: "allow", - }, - edit: "deny", - write: "deny", - }, - }) const executor = testAgent({ name: "executor", mode: "subagent", @@ -166,8 +117,7 @@ it.effect("[#26700] controller self-restrictions do not erase executor permissio "*": "deny", worker: "allow", }, - edit: "deny", - write: "deny", + edit: "allow", }, }) @@ -175,7 +125,6 @@ it.effect("[#26700] controller self-restrictions do not erase executor permissio executor.permission, deriveSubagentSessionPermission({ parentSessionPermission: [], - parentAgent: controller, subagent: executor, }), ) @@ -184,9 +133,7 @@ it.effect("[#26700] controller self-restrictions do not erase executor permissio expect(Permission.evaluate("bash", "git status", effective).action).toBe("allow") expect(Permission.evaluate("task", "worker", effective).action).toBe("allow") expect(Permission.evaluate("task", "other", effective).action).toBe("deny") - expect(Permission.disabled(["edit", "write", "apply_patch"], effective)).toEqual( - new Set(["edit", "write", "apply_patch"]), - ) + expect(Permission.disabled(["edit", "write", "apply_patch"], effective)).toEqual(new Set()) }), ) @@ -203,7 +150,6 @@ it.effect("subagent inherits parent session deny rules as hard runtime ceilings" executor.permission, deriveSubagentSessionPermission({ parentSessionPermission: Permission.fromConfig({ bash: "deny" }), - parentAgent: undefined, subagent: executor, }), ) From 02608a4e9791c256df7e32ae88ec72a7df16623e Mon Sep 17 00:00:00 2001 From: Ayush Thakur <51413362+Ayushlm10@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:32:09 +0530 Subject: [PATCH 019/157] fix: recover from expired enterprise auth on remote config load (#31661) --- packages/core/src/v1/config/error.ts | 5 +++ packages/opencode/src/cli/cmd/providers.ts | 5 ++- packages/opencode/src/cli/error.ts | 12 +++++++ packages/opencode/src/config/config.ts | 18 ++++++++--- packages/opencode/test/config/config.test.ts | 34 ++++++++++++++++++-- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/core/src/v1/config/error.ts b/packages/core/src/v1/config/error.ts index 268a6eb202..abf3a09276 100644 --- a/packages/core/src/v1/config/error.ts +++ b/packages/core/src/v1/config/error.ts @@ -32,3 +32,8 @@ export const DirectoryTypoError = NamedError.create("ConfigDirectoryTypoError", dir: Schema.String, suggestion: Schema.String, }) + +export const RemoteAuthError = NamedError.create("ConfigRemoteAuthError", { + url: Schema.String, + remote: Schema.String, +}) diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 620940b383..cec20a0230 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -1,3 +1,4 @@ +import type { Argv } from "yargs" import { Auth } from "../../auth" import { cmd } from "./cmd" import { CliError, effectCmd, fail } from "../effect-cmd" @@ -298,7 +299,9 @@ export const ProvidersListCommand = effectCmd({ export const ProvidersLoginCommand = effectCmd({ command: "login [url]", describe: "log in to a provider", - builder: (yargs) => + // URL login skips instance bootstrap, which would load remote config with the stale token and crash before re-auth. + instance: (args) => !args.url, + builder: (yargs: Argv) => yargs .positional("url", { describe: "opencode auth provider", diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index ef724bbbef..407547e4e5 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -94,6 +94,18 @@ export function FormatError(input: unknown): string | undefined { return stringField(configFrontmatter, "message") ?? "" } + // ConfigRemoteAuthError: { url: string, remote: string } + const remoteAuth = configData(input, "ConfigRemoteAuthError") + if (remoteAuth) { + const url = stringField(remoteAuth, "url") + const remote = stringField(remoteAuth, "remote") + return [ + `Failed to load remote config${remote ? ` from ${remote}` : ""}: the server returned a login page instead of JSON.`, + `Authentication is missing or has expired (the endpoint is likely behind an SSO or identity-aware proxy).`, + ...(url ? [`Run \`opencode auth login ${url}\` to re-authenticate.`] : []), + ].join("\n") + } + // ConfigInvalidError: { path?: string, message?: string, issues?: Array<{ message: string, path: string[] }> } const configInvalid = configData(input, "ConfigInvalidError") if (configInvalid) { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 480f08f81d..7f568f4920 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -19,10 +19,11 @@ import type { ConsoleState } from "@opencode-ai/core/v1/config/console-state" import { FSUtil } from "@opencode-ai/core/fs-util" import { InstanceState } from "@/effect/instance-state" import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect" -import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" +import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { containsPath, type InstanceContext } from "../project/instance-context" import { ConfigV1 } from "@opencode-ai/core/v1/config/config" +import { RemoteAuthError } from "@opencode-ai/core/v1/config/error" import { ConfigPermissionV1 } from "@opencode-ai/core/v1/config/permission" import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin" import { ConfigAgent } from "./agent" @@ -187,6 +188,7 @@ export const layer = Layer.effect( url: string, headers: Record | undefined, schema: S, + loginOrigin: string, ) { const response = yield* HttpClient.filterStatusOk(withTransientReadRetry(http)) .execute( @@ -195,7 +197,15 @@ export const layer = Layer.effect( .pipe( Effect.catch((error) => Effect.die(new Error(`failed to fetch remote config from ${url}: ${String(error)}`))), ) - return yield* HttpClientResponse.schemaBodyJson(schema)(response).pipe( + const body = yield* response.text.pipe( + Effect.catch((error) => Effect.die(new Error(`failed to read remote config from ${url}: ${String(error)}`))), + ) + // An auth proxy can answer with an HTML login page at HTTP 200 (passes filterStatusOk); treat it as a re-auth error, not a decode failure. + const contentType = (response.headers["content-type"] ?? "").toLowerCase() + if (contentType.includes("html") || /^\s* Effect.die(new Error(`failed to decode remote config from ${url}: ${String(error)}`))), ) }) @@ -348,7 +358,7 @@ export const layer = Layer.effect( authEnv[value.key] = value.token const wellknownURL = `${url}/.well-known/opencode` yield* Effect.logDebug("fetching remote config", { url: wellknownURL }) - const wellknown = yield* fetchRemoteJson(wellknownURL, undefined, ConfigV1.WellKnown) + const wellknown = yield* fetchRemoteJson(wellknownURL, undefined, ConfigV1.WellKnown, url) const remote = yield* Effect.promise(() => substituteWellKnownRemoteConfig({ value: wellknown.remote_config, @@ -360,7 +370,7 @@ export const layer = Layer.effect( const fetchedConfig = remote ? yield* Effect.gen(function* () { yield* Effect.logDebug("fetching remote config", { url: remote.url }) - const data = yield* fetchRemoteJson(remote.url, remote.headers, Schema.Json) + const data = yield* fetchRemoteJson(remote.url, remote.headers, Schema.Json, url) if (isRecord(data) && isRecord(data.config)) return data.config if (isRecord(data)) return data return yield* Effect.die( diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index f4a0cd4a28..02ace53668 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1,6 +1,7 @@ import { test, expect, describe, afterEach, beforeEach, spyOn } from "bun:test" import { ConfigV1 } from "@opencode-ai/core/v1/config/config" -import { Effect, Exit, Layer, Option } from "effect" +import { Cause, Effect, Exit, Layer, Option } from "effect" +import { NamedError } from "@opencode-ai/core/util/error" import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http" import { NodeFileSystem, NodePath } from "@effect/platform-node" import { Config } from "@/config/config" @@ -71,6 +72,7 @@ const wellKnownAuth = (url: string) => function remoteConfigClient(input: { wellKnown: unknown remote?: unknown + remoteHtml?: string seen: { wellKnown?: string; remote?: string; authorization?: string } }) { return HttpClient.make((request) => { @@ -78,9 +80,17 @@ function remoteConfigClient(input: { input.seen.wellKnown = request.url return Effect.succeed(json(request, input.wellKnown)) } - if (input.remote !== undefined && request.url.includes("config.example.com")) { + if (request.url.includes("config.example.com") && (input.remote !== undefined || input.remoteHtml !== undefined)) { input.seen.remote = request.url input.seen.authorization = request.headers.authorization + if (input.remoteHtml !== undefined) { + return Effect.succeed( + HttpClientResponse.fromWeb( + request, + new Response(input.remoteHtml, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } }), + ), + ) + } return Effect.succeed(json(request, input.remote)) } return Effect.succeed(json(request, {}, 404)) @@ -214,6 +224,7 @@ const wellKnown = (input: { config?: unknown remoteConfig?: { url: string; headers?: Record } remote?: unknown + remoteHtml?: string wellKnown?: unknown }) => { const seen: { wellKnown?: string; remote?: string; authorization?: string } = {} @@ -224,6 +235,7 @@ const wellKnown = (input: { ...(input.remoteConfig !== undefined ? { remote_config: input.remoteConfig } : {}), }, remote: input.remote, + remoteHtml: input.remoteHtml, }) return { seen, @@ -1635,6 +1647,24 @@ invalidRemoteWellKnown.it.instance("wellknown remote_config rejects non-object c }), ) +const loginPageWellKnown = wellKnown({ + remoteConfig: { url: "https://config.example.com/opencode.json" }, + remoteHtml: "Sign inLogin required", +}) + +loginPageWellKnown.it.instance( + "wellknown remote_config surfaces an actionable auth error when the gateway returns an HTML login page", + () => + Effect.gen(function* () { + const exit = yield* Config.use.get().pipe(Effect.exit) + expect(loginPageWellKnown.seen.remote).toBe("https://config.example.com/opencode.json") + expect(Exit.isFailure(exit)).toBe(true) + const error = Exit.isFailure(exit) ? Cause.squash(exit.cause) : undefined + expect(NamedError.hasName(error, "ConfigRemoteAuthError")).toBe(true) + expect((error as { data?: { url?: string } }).data?.url).toBe("https://example.com") + }), +) + describe("resolvePluginSpec", () => { test("keeps package specs unchanged", async () => { await using tmp = await tmpdir() From 649618c50ac382a763921201cb2b53e0dff88e0c Mon Sep 17 00:00:00 2001 From: mridul <65942753+rexdotsh@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:16:09 +0530 Subject: [PATCH 020/157] fix(app): restore device attachment picker (#31707) --- .../app/src/components/dialog-select-file.tsx | 5 --- packages/app/src/components/prompt-input.tsx | 41 ++++++------------- .../prompt-input/server-attachment.test.ts | 32 --------------- .../prompt-input/server-attachment.ts | 8 ---- 4 files changed, 12 insertions(+), 74 deletions(-) delete mode 100644 packages/app/src/components/prompt-input/server-attachment.test.ts delete mode 100644 packages/app/src/components/prompt-input/server-attachment.ts diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index b231d3dc86..dcc69435a8 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -264,7 +264,6 @@ function createSessionEntries(props: { export function DialogSelectFile(props: { mode?: DialogSelectFileMode onOpenFile?: (path: string) => void - onSelectFile?: (path: string) => void }) { const command = useCommand() const language = useLanguage() @@ -379,10 +378,6 @@ export function DialogSelectFile(props: { } if (!item.path) return - if (props.onSelectFile) { - props.onSelectFile(item.path) - return - } open(item.path) } diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 835bc23819..007f6bf5b0 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -53,7 +53,6 @@ import { usePermission } from "@/context/permission" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" -import { serverAttachmentFile } from "./prompt-input/server-attachment" import { useSessionLayout } from "@/pages/session/session-layout" import { createSessionTabs } from "@/pages/session/helpers" import { createTextFragment, getCursorPosition, setCursorPosition, setRangeEdge } from "./prompt-input/editor-dom" @@ -473,34 +472,18 @@ export const PromptInput: Component = (props) => { const escBlur = () => platform.platform === "desktop" && platform.os === "macos" const pick = () => { - if (server.isLocal()) { - pickAttachmentFiles({ - picker: platform.openAttachmentPickerDialog, - directory: () => sdk.directory, - fallback: () => fileInputRef?.click(), - onFile: addAttachment, - onError: (error) => - showToast({ - variant: "error", - title: language.t("common.requestFailed"), - description: error instanceof Error ? error.message : String(error), - }), - }) - return - } - void import("@/components/dialog-select-file").then((module) => - dialog.show(() => ( - { - void sdk.client.v2.fs - .read({ path }) - .then((response) => response.data?.data) - .then((data) => data && addAttachments([serverAttachmentFile(path, data)])) - }} - /> - )), - ) + pickAttachmentFiles({ + picker: platform.openAttachmentPickerDialog, + directory: () => sdk.directory, + fallback: () => fileInputRef?.click(), + onFile: addAttachment, + onError: (error) => + showToast({ + variant: "error", + title: language.t("common.requestFailed"), + description: error instanceof Error ? error.message : String(error), + }), + }) } const setMode = (mode: "normal" | "shell") => { diff --git a/packages/app/src/components/prompt-input/server-attachment.test.ts b/packages/app/src/components/prompt-input/server-attachment.test.ts deleted file mode 100644 index d4de9af0b8..0000000000 --- a/packages/app/src/components/prompt-input/server-attachment.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { serverAttachmentFile } from "./server-attachment" - -describe("serverAttachmentFile", () => { - test("creates a file from server text content", async () => { - const file = serverAttachmentFile("docs/readme.txt", { - uri: "file:///docs/readme.txt", - name: "readme.txt", - content: "hello", - encoding: "utf8", - mime: "text/plain", - }) - - expect(file.name).toBe("readme.txt") - expect(file.type).toBe("text/plain") - expect(await file.text()).toBe("hello") - }) - - test("creates a file from server base64 content", async () => { - const file = serverAttachmentFile("images/pixel.png", { - uri: "file:///images/pixel.png", - name: "pixel.png", - content: "aGVsbG8=", - encoding: "base64", - mime: "image/png", - }) - - expect(file.name).toBe("pixel.png") - expect(file.type).toBe("image/png") - expect(await file.text()).toBe("hello") - }) -}) diff --git a/packages/app/src/components/prompt-input/server-attachment.ts b/packages/app/src/components/prompt-input/server-attachment.ts deleted file mode 100644 index 1bd7c14824..0000000000 --- a/packages/app/src/components/prompt-input/server-attachment.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getFilename } from "@opencode-ai/core/util/path" -import type { FileSystemContent } from "@opencode-ai/sdk/v2" - -export function serverAttachmentFile(path: string, data: FileSystemContent) { - const content = - data.encoding === "utf8" ? data.content : Uint8Array.from(atob(data.content), (char) => char.charCodeAt(0)) - return new File([content], getFilename(path), { type: data.mime }) -} From e1073e5d1899a858f61b47c2a80eb2d077ec9d26 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 10 Jun 2026 15:48:14 +0000 Subject: [PATCH 021/157] chore: generate --- packages/app/src/components/dialog-select-file.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index dcc69435a8..4080c8ce85 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -261,10 +261,7 @@ function createSessionEntries(props: { return { sessions } } -export function DialogSelectFile(props: { - mode?: DialogSelectFileMode - onOpenFile?: (path: string) => void -}) { +export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { const command = useCommand() const language = useLanguage() const layout = useLayout() From 2e0f88d0e373f1841cc672196934a4eb7d6f8b2c Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:54:49 +0200 Subject: [PATCH 022/157] fix(desktop): restore linux launcher identity (#31709) --- .../desktop/electron-builder.config.test.ts | 48 +++++++++++++++++++ packages/desktop/electron-builder.config.ts | 45 +++++++++++++---- .../resources/linux/opencode-desktop.desktop | 10 ++++ 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 packages/desktop/electron-builder.config.test.ts create mode 100644 packages/desktop/resources/linux/opencode-desktop.desktop diff --git a/packages/desktop/electron-builder.config.test.ts b/packages/desktop/electron-builder.config.test.ts new file mode 100644 index 0000000000..eee2b825cb --- /dev/null +++ b/packages/desktop/electron-builder.config.test.ts @@ -0,0 +1,48 @@ +import { expect, test } from "bun:test" +import type { Configuration } from "electron-builder" + +const legacyDesktopEntry = "resources/linux/opencode-desktop.desktop" + +const channels = [ + { channel: "dev", appId: "ai.opencode.desktop.dev" }, + { channel: "beta", appId: "ai.opencode.desktop.beta" }, + { channel: "prod", appId: "ai.opencode.desktop" }, +] as const + +for (const channel of channels) { + test(`uses one Linux desktop identity for ${channel.channel}`, async () => { + const previous = process.env.OPENCODE_CHANNEL + process.env.OPENCODE_CHANNEL = channel.channel + + const module = await import(`./electron-builder.config.ts?channel=${channel.channel}`) + const config = module.default as Configuration + + if (previous === undefined) delete process.env.OPENCODE_CHANNEL + else process.env.OPENCODE_CHANNEL = previous + + expect(config.appId).toBe(channel.appId) + expect(config.extraMetadata?.desktopName).toBe(`${channel.appId}.desktop`) + expect(config.linux?.executableName).toBe(channel.appId) + expect(config.linux?.desktop?.entry?.StartupWMClass).toBe(channel.appId) + }) +} + +test("keeps a hidden prod launcher for old Linux pins", async () => { + const previous = process.env.OPENCODE_CHANNEL + process.env.OPENCODE_CHANNEL = "prod" + + const module = await import("./electron-builder.config.ts?compat=prod") + const config = module.default as Configuration + + if (previous === undefined) delete process.env.OPENCODE_CHANNEL + else process.env.OPENCODE_CHANNEL = previous + + expect(config.deb?.fpm?.[0]).toEndWith(`${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop`) + expect(config.rpm?.fpm?.[0]).toEndWith(`${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop`) + + const desktop = await Bun.file(legacyDesktopEntry).text() + expect(desktop).toContain("Exec=/opt/OpenCode/ai.opencode.desktop %U") + expect(desktop).toContain("Icon=ai.opencode.desktop") + expect(desktop).toContain("StartupWMClass=ai.opencode.desktop") + expect(desktop).toContain("NoDisplay=true") +}) diff --git a/packages/desktop/electron-builder.config.ts b/packages/desktop/electron-builder.config.ts index 7fd03ae621..23afe811c2 100644 --- a/packages/desktop/electron-builder.config.ts +++ b/packages/desktop/electron-builder.config.ts @@ -6,8 +6,14 @@ import { promisify } from "node:util" import type { Configuration } from "electron-builder" const execFileAsync = promisify(execFile) -const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..") +const packageDir = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.resolve(packageDir, "../..") const signScript = path.join(rootDir, "script", "sign-windows.ps1") +// The Electron 42 packaging update briefly installed Linux launchers/icons under +// "opencode-desktop". Keep that hidden desktop entry around so existing GNOME/KDE +// pins still resolve after the canonical app id changes back to ai.opencode.desktop. +const legacyDesktopEntry = path.join(packageDir, "resources", "linux", "opencode-desktop.desktop") +const legacyDesktopEntryFpm = `${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop` async function signWindows(configuration: { path: string }) { if (process.platform !== "win32") return @@ -26,12 +32,26 @@ const channel = (() => { return "dev" })() -const getBase = (): Configuration => ({ +const APP_IDS = { + dev: "ai.opencode.desktop.dev", + beta: "ai.opencode.desktop.beta", + prod: "ai.opencode.desktop", +} as const + +const getBase = (appId: string): Configuration => ({ artifactName: "opencode-desktop-${os}-${arch}.${ext}", directories: { output: "dist", buildResources: "resources", }, + // Linux launchers are .desktop files, so this is the desktop file name, + // not just the app id. For prod, app id "ai.opencode.desktop" becomes + // "ai.opencode.desktop.desktop". + // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html + // https://www.electron.build/docs/linux/ + extraMetadata: { + desktopName: `${appId}.desktop`, + }, files: ["out/**/*", "resources/**/*"], extraResources: [ { @@ -74,19 +94,27 @@ const getBase = (): Configuration => ({ linux: { icon: `resources/icons`, category: "Development", - executableName: "opencode-desktop", + executableName: appId, + desktop: { + entry: { + // Match the installed .desktop file and hicolor icon basename so + // Linux shells can associate the running Electron window with its launcher. + StartupWMClass: appId, + }, + }, target: ["AppImage", "deb", "rpm"], }, }) function getConfig() { - const base = getBase() + const appId = APP_IDS[channel] + const base = getBase(appId) switch (channel) { case "dev": { return { ...base, - appId: "ai.opencode.desktop.dev", + appId, productName: "OpenCode Dev", rpm: { packageName: "opencode-dev" }, } @@ -94,7 +122,7 @@ function getConfig() { case "beta": { return { ...base, - appId: "ai.opencode.desktop.beta", + appId, productName: "OpenCode Beta", protocols: { name: "OpenCode Beta", schemes: ["opencode"] }, publish: { provider: "github", owner: "anomalyco", repo: "opencode-beta", channel: "latest" }, @@ -104,11 +132,12 @@ function getConfig() { case "prod": { return { ...base, - appId: "ai.opencode.desktop", + appId, productName: "OpenCode", protocols: { name: "OpenCode", schemes: ["opencode"] }, publish: { provider: "github", owner: "anomalyco", repo: "opencode", channel: "latest" }, - rpm: { packageName: "opencode" }, + deb: { fpm: [legacyDesktopEntryFpm] }, + rpm: { packageName: "opencode", fpm: [legacyDesktopEntryFpm] }, } } } diff --git a/packages/desktop/resources/linux/opencode-desktop.desktop b/packages/desktop/resources/linux/opencode-desktop.desktop new file mode 100644 index 0000000000..a5f677412c --- /dev/null +++ b/packages/desktop/resources/linux/opencode-desktop.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=OpenCode +Exec=/opt/OpenCode/ai.opencode.desktop %U +Terminal=false +Type=Application +Icon=ai.opencode.desktop +StartupWMClass=ai.opencode.desktop +NoDisplay=true +Comment=Open source AI coding agent +Categories=Development; From 2c652732048abdaddee5f5f8e75fbb1aff1c7824 Mon Sep 17 00:00:00 2001 From: opencode Date: Wed, 10 Jun 2026 16:42:54 +0000 Subject: [PATCH 023/157] sync release versions for v1.17.2 --- bun.lock | 52 ++++++++++----------- packages/app/package.json | 2 +- packages/cli/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/console/support/package.json | 2 +- packages/core/package.json | 2 +- packages/desktop/package.json | 2 +- packages/effect-drizzle-sqlite/package.json | 2 +- packages/effect-sqlite-node/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/function/package.json | 2 +- packages/http-recorder/package.json | 2 +- packages/llm/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/server/package.json | 2 +- packages/slack/package.json | 2 +- packages/stats/app/package.json | 2 +- packages/stats/core/package.json | 2 +- packages/stats/server/package.json | 2 +- packages/tui/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 28 files changed, 53 insertions(+), 53 deletions(-) diff --git a/bun.lock b/bun.lock index cbc4e7f7e7..0ebb18a515 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -85,7 +85,7 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.17.1", + "version": "1.17.2", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -110,7 +110,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -146,7 +146,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -173,7 +173,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -195,7 +195,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -219,7 +219,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -239,7 +239,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.17.1", + "version": "1.17.2", "bin": { "opencode": "./bin/opencode", }, @@ -330,7 +330,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@zip.js/zip.js": "2.7.62", "effect": "catalog:", @@ -384,7 +384,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -398,7 +398,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "effect": "catalog:", }, @@ -410,7 +410,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -441,7 +441,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -457,7 +457,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@effect/platform-node": "4.0.0-beta.74", "@effect/platform-node-shared": "4.0.0-beta.74", @@ -476,7 +476,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -494,7 +494,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.17.1", + "version": "1.17.2", "bin": { "opencode": "./bin/opencode", }, @@ -622,7 +622,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -660,7 +660,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "cross-spawn": "catalog:", }, @@ -675,7 +675,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@opencode-ai/core": "workspace:*", "drizzle-orm": "catalog:", @@ -689,7 +689,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -702,7 +702,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@ibm/plex": "6.4.1", "@opencode-ai/stats-core": "workspace:*", @@ -735,7 +735,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -754,7 +754,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -794,7 +794,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", @@ -822,7 +822,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -871,7 +871,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 46ef54c8fa..d70d021b29 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.17.1", + "version": "1.17.2", "description": "", "type": "module", "exports": { diff --git a/packages/cli/package.json b/packages/cli/package.json index db71b46ea5..e6532b880f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/cli", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "bin": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 90a33f3739..96f80cdb70 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 8f2af5f79b..f5d20ac10d 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index daaf063d56..d863daf251 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.17.1", + "version": "1.17.2", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index fe7991eec1..2bfa1a60dc 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.17.1", + "version": "1.17.2", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/support/package.json b/packages/console/support/package.json index e2d6296407..436ef4e32b 100644 --- a/packages/console/support/package.json +++ b/packages/console/support/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-support", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 723e7ee8fe..0de9fbdb3c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.1", + "version": "1.17.2", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index ef1ba07738..97593d8848 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 8ae0b1037e..cd8ad23a9a 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.1", + "version": "1.17.2", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index c1c49df152..5ffe2a0430 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.1", + "version": "1.17.2", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 9c85f5aea0..9d792e16cc 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/function/package.json b/packages/function/package.json index 30ce5920e6..7c5d66c3be 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.17.1", + "version": "1.17.2", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index db40e0a229..00d549396c 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.1", + "version": "1.17.2", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/llm/package.json b/packages/llm/package.json index 8118c79d8a..afa820eb3b 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.1", + "version": "1.17.2", "name": "@opencode-ai/llm", "type": "module", "license": "MIT", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 229739e312..d0a3d283ab 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.1", + "version": "1.17.2", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index ae6ce12cf6..c62b30c24f 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 5970822f84..c902d8d346 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 639c853c8d..5acd2d1875 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/server", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 621680eac3..6313f5e55c 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/stats/app/package.json b/packages/stats/app/package.json index c2f25df0e3..3faa1f42c2 100644 --- a/packages/stats/app/package.json +++ b/packages/stats/app/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-app", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/core/package.json b/packages/stats/core/package.json index d26d6bbb81..42bc47fbb5 100644 --- a/packages/stats/core/package.json +++ b/packages/stats/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-core", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/server/package.json b/packages/stats/server/package.json index 33438363c7..60dc92effe 100644 --- a/packages/stats/server/package.json +++ b/packages/stats/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-server", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/tui/package.json b/packages/tui/package.json index 266aa4a6c0..868474e627 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/tui", - "version": "1.17.1", + "version": "1.17.2", "private": true, "type": "module", "license": "MIT", diff --git a/packages/ui/package.json b/packages/ui/package.json index 06609b9f6d..1ebd8b68cd 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.17.1", + "version": "1.17.2", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 0b5a2355d4..b3d3277cc4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.17.1", + "version": "1.17.2", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 172b4cfd9a..7c129c4011 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.17.1", + "version": "1.17.2", "publisher": "sst-dev", "repository": { "type": "git", From 14ec7ed5b311413f3a93c081e45da9d8336f5d93 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 10 Jun 2026 13:28:48 -0400 Subject: [PATCH 024/157] fix fff disabling logic --- packages/core/src/filesystem/search.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index ce5f50cc6e..0f123f5b9c 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -232,4 +232,6 @@ export const fffLayer = Layer.effect( }), ) -export const defaultLayer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_DISABLE_FFF ? ripgrepLayer : fffLayer))) +export const defaultLayer = Layer.unwrap( + Effect.sync(() => (Flag.OPENCODE_DISABLE_FFF || !Fff.available() ? ripgrepLayer : fffLayer)), +) From bed780fac90cdc6a626dd1f723f0ef3c919cf089 Mon Sep 17 00:00:00 2001 From: Vladimir Glafirov Date: Wed, 10 Jun 2026 19:30:43 +0200 Subject: [PATCH 025/157] chore: bump gitlab-ai-provider to 6.9.1 (#31728) --- bun.lock | 6 +++--- packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bun.lock b/bun.lock index 0ebb18a515..b85e481f99 100644 --- a/bun.lock +++ b/bun.lock @@ -288,7 +288,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.8.0", + "gitlab-ai-provider": "6.9.1", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -564,7 +564,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.8.0", + "gitlab-ai-provider": "6.9.1", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -3626,7 +3626,7 @@ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], - "gitlab-ai-provider": ["gitlab-ai-provider@6.8.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-KwHASXkHtDcgrzTXZVp9Dyx6t8m9nK0R2fCm47MWcxxQ1kOBt3f2LZugtu1kOby8i4Sbd+kvBSYM66PGkDclng=="], + "gitlab-ai-provider": ["gitlab-ai-provider@6.9.1", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-2qd8UCwS3qTGk0uVq+tMT+06cpMjTw8ShECCAnI3+pXynnxG3rEo4tRojl9ES281pA+WwijsO6gCHuiuleYBjg=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], diff --git a/packages/core/package.json b/packages/core/package.json index 0de9fbdb3c..b06dd53a82 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -104,7 +104,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.8.0", + "gitlab-ai-provider": "6.9.1", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d0a3d283ab..9cb6fd9fef 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -117,7 +117,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.8.0", + "gitlab-ai-provider": "6.9.1", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", From 5b54203168658adf4f50adb6d81cb22528b6e43b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 10 Jun 2026 17:44:15 +0000 Subject: [PATCH 026/157] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 1bbea78c1b..3c65d0dc04 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-nix3Ogrt4nFv+HSZpYZ3VqIQc+g1SdP+DSgu72Yjoqw=", - "aarch64-linux": "sha256-L634j49jyNoKhC1MDyF+Grxy++yhWgcbXsxKDgYhKAk=", - "aarch64-darwin": "sha256-ha692TeekwiV0irhIxdwE8/1x1bLKtzSwcjDvetiPqM=", - "x86_64-darwin": "sha256-JRwKof0cTFwOcOyLb6l+kF/HlCiFmMWzG1y3U1esyyM=" + "x86_64-linux": "sha256-bQFK3yO9Y5m2zIlZ16wmJ05WOLNuvYvHMDbV+lEHzkg=", + "aarch64-linux": "sha256-sP/DzyfDnyeGOAo0cx50Jas3X4RGuLxfAdoTrv355c0=", + "aarch64-darwin": "sha256-88zVvcQyKok6qoi70FtLdYkUhyNyD1hrcDTUDEuOtiM=", + "x86_64-darwin": "sha256-CUXFpNm274+ekBTZzD/hkocNFAexOBTMzIZFyGBh8MA=" } } From 8688ed7e3ea40d5a4e9c863f7041fae48a3becdc Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:55:37 -0500 Subject: [PATCH 027/157] feat(web): data link --- packages/console/app/src/component/header.tsx | 23 ++++++++----------- packages/console/app/src/i18n/en.ts | 1 + 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/console/app/src/component/header.tsx b/packages/console/app/src/component/header.tsx index cc45ed534f..a0e5ffc0ca 100644 --- a/packages/console/app/src/component/header.tsx +++ b/packages/console/app/src/component/header.tsx @@ -13,10 +13,9 @@ import copyLogoSvgLight from "../asset/lander/opencode-logo-light.svg" import copyLogoSvgDark from "../asset/lander/opencode-logo-dark.svg" import copyWordmarkSvgLight from "../asset/lander/opencode-wordmark-light.svg" import copyWordmarkSvgDark from "../asset/lander/opencode-wordmark-dark.svg" -import { A, createAsync, useNavigate } from "@solidjs/router" +import { A, useNavigate } from "@solidjs/router" import { createMemo, Match, Show, Switch } from "solid-js" import { createStore } from "solid-js/store" -import { github } from "~/lib/github" import { createEffect, onCleanup } from "solid-js" import { config } from "~/config" import { useI18n } from "~/context/i18n" @@ -40,16 +39,6 @@ export function Header(props: { zen?: boolean; go?: boolean; hideGetStarted?: bo const navigate = useNavigate() const i18n = useI18n() const language = useLanguage() - const githubData = createAsync(() => github()) - const starCount = createMemo(() => - githubData()?.stars - ? new Intl.NumberFormat("en-US", { - notation: "compact", - compactDisplay: "short", - maximumFractionDigits: 0, - }).format(githubData()?.stars) - : config.github.starsFormatted.compact, - ) const [store, setStore] = createStore({ mobileMenuOpen: false, @@ -155,12 +144,15 @@ export function Header(props: { zen?: boolean; go?: boolean; hideGetStarted?: bo