Merge remote-tracking branch 'origin/dev' into fix/google-vertex-anthropic-thinking

pull/10442/head
Aiden Cline 2026-01-24 23:23:57 -05:00
commit 87f0b68f5a
65 changed files with 717 additions and 346 deletions

View File

@ -23,7 +23,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -73,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@ -107,7 +107,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@ -134,7 +134,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@ -158,7 +158,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@ -182,7 +182,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@ -211,7 +211,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@ -240,7 +240,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@ -256,7 +256,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.1.34",
"version": "1.1.35",
"bin": {
"opencode": "./bin/opencode",
},
@ -360,7 +360,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@ -380,9 +380,9 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.1.34",
"version": "1.1.35",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.4",
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
@ -391,7 +391,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@ -404,7 +404,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -423,6 +423,7 @@
"marked": "catalog:",
"marked-katex-extension": "5.1.6",
"marked-shiki": "catalog:",
"morphdom": "2.7.8",
"remeda": "catalog:",
"shiki": "catalog:",
"solid-js": "catalog:",
@ -445,7 +446,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"zod": "catalog:",
},
@ -456,7 +457,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@ -928,11 +929,13 @@
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.2", "", { "dependencies": { "ansi-colors": "4.1.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-88cqrrB2cLXN8nMOHidQTcVOnZsJ5kebEbBefjMCifaUCwTA30ouSSWvTZqrOX4O104zjJyu7M8Gcv/NNYQuaA=="],
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.5", "", { "dependencies": { "@hey-api/types": "0.1.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-f2ZHucnA2wBGAY8ipB4wn/mrEYW+WUxU2huJmUvfDO6AE2vfILSHeF3wCO39Pz4wUYPoAWZByaauftLrOfC12Q=="],
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.4", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.2", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-9l++kjcb0ui4JqPlueZ6OZ9zKn6eK/8//Z2jHcIXb5MRwDRgubOOSpTU5llEv3uvWfT10VzcMp99dySWq0AASw=="],
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.10", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.5", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "0.1.2", "ansi-colors": "4.1.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-o0wlFxuLt1bcyIV/ZH8DQ1wrgODTnUYj/VfCHOOYgXUQlLp9Dm2PjihOz+WYrZLowhqUhSKeJRArOGzvLuOTsg=="],
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
"@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
@ -3102,6 +3105,8 @@
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
"morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],

View File

@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-spng4G0hikHyNhplZiXL/K4XzSBsQNwFCrtzatuY+e4=",
"aarch64-linux": "sha256-j6kTzWlSPWwoyvOR6nJAx8VpikMsy/U3ZAJGI2PxPE0=",
"aarch64-darwin": "sha256-H4XA72cYxaBBpT6wYAAAOhW4gHduUTxiqT/cNCjW5zc=",
"x86_64-darwin": "sha256-4pljrneppSsxS5EYoXX0914L9z9jLHcKatm5T8L6/54="
"x86_64-linux": "sha256-olTZ+tKugAY3LxizsJMlbK3TW78HZUoM03PigvQLP4A=",
"aarch64-linux": "sha256-xdKDeqMEnYM2+vGySfb8pbcYyo/xMmgxG/ZhPCKaZEg=",
"aarch64-darwin": "sha256-fihCTrHIiUG+py4vuqdr+YshqSKm2/B5onY50b97sPM=",
"x86_64-darwin": "sha256-inlQQPNAOdkmKK6HQAMI2bG/ZFlfwmUQu9a6vm6Q0jQ="
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.1.34",
"version": "1.1.35",
"description": "",
"type": "module",
"exports": {

View File

@ -1,5 +1,6 @@
import { Popover as Kobalte } from "@kobalte/core/popover"
import { Component, ComponentProps, createMemo, createSignal, JSX, Show, ValidComponent } from "solid-js"
import { Component, ComponentProps, createEffect, createMemo, JSX, onCleanup, Show, ValidComponent } from "solid-js"
import { createStore } from "solid-js/store"
import { useLocal } from "@/context/local"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { popularProviders } from "@/hooks/use-providers"
@ -92,26 +93,118 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
triggerAs?: T
triggerProps?: ComponentProps<T>
}) {
const [open, setOpen] = createSignal(false)
const [store, setStore] = createStore<{
open: boolean
dismiss: "escape" | "outside" | null
trigger?: HTMLElement
content?: HTMLElement
}>({
open: false,
dismiss: null,
trigger: undefined,
content: undefined,
})
const dialog = useDialog()
const handleManage = () => {
setOpen(false)
setStore("open", false)
dialog.show(() => <DialogManageModels />)
}
const language = useLanguage()
createEffect(() => {
if (!store.open) return
const inside = (node: Node | null | undefined) => {
if (!node) return false
const el = store.content
if (el && el.contains(node)) return true
const anchor = store.trigger
if (anchor && anchor.contains(node)) return true
return false
}
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== "Escape") return
setStore("dismiss", "escape")
setStore("open", false)
event.preventDefault()
event.stopPropagation()
}
const onPointerDown = (event: PointerEvent) => {
const target = event.target
if (!(target instanceof Node)) return
if (inside(target)) return
setStore("dismiss", "outside")
setStore("open", false)
}
const onFocusIn = (event: FocusEvent) => {
if (!store.content) return
const target = event.target
if (!(target instanceof Node)) return
if (inside(target)) return
setStore("dismiss", "outside")
setStore("open", false)
}
window.addEventListener("keydown", onKeyDown, true)
window.addEventListener("pointerdown", onPointerDown, true)
window.addEventListener("focusin", onFocusIn, true)
onCleanup(() => {
window.removeEventListener("keydown", onKeyDown, true)
window.removeEventListener("pointerdown", onPointerDown, true)
window.removeEventListener("focusin", onFocusIn, true)
})
})
return (
<Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
<Kobalte.Trigger as={props.triggerAs ?? "div"} {...(props.triggerProps as any)}>
<Kobalte
open={store.open}
onOpenChange={(next) => {
if (next) setStore("dismiss", null)
setStore("open", next)
}}
modal={false}
placement="top-start"
gutter={8}
>
<Kobalte.Trigger
ref={(el) => setStore("trigger", el)}
as={props.triggerAs ?? "div"}
{...(props.triggerProps as any)}
>
{props.children}
</Kobalte.Trigger>
<Kobalte.Portal>
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
<Kobalte.Content
ref={(el) => setStore("content", el)}
class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden"
onEscapeKeyDown={(event) => {
setStore("dismiss", "escape")
setStore("open", false)
event.preventDefault()
event.stopPropagation()
}}
onPointerDownOutside={() => {
setStore("dismiss", "outside")
setStore("open", false)
}}
onFocusOutside={() => {
setStore("dismiss", "outside")
setStore("open", false)
}}
onCloseAutoFocus={(event) => {
if (store.dismiss === "outside") event.preventDefault()
setStore("dismiss", null)
}}
>
<Kobalte.Title class="sr-only">{language.t("dialog.model.select.title")}</Kobalte.Title>
<ModelList
provider={props.provider}
onSelect={() => setOpen(false)}
onSelect={() => setStore("open", false)}
class="p-1"
action={
<IconButton

View File

@ -11,7 +11,8 @@ import { usePlatform } from "@/context/platform"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { useNavigate } from "@solidjs/router"
import { useLanguage } from "@/context/language"
import { Popover } from "@opencode-ai/ui/popover"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { useGlobalSDK } from "@/context/global-sdk"
type ServerStatus = { healthy: boolean; version?: string }
@ -52,16 +53,27 @@ async function checkHealth(url: string, platform: ReturnType<typeof usePlatform>
function AddRow(props: AddRowProps) {
return (
<div class="flex items-center gap-3 px-4 min-w-0 flex-1">
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": props.status === true,
"bg-icon-critical-base": props.status === false,
"bg-border-weak-base": props.status === undefined,
}}
/>
<div class="flex-1 min-w-0">
<div class="flex items-center px-4 min-h-14 py-3 min-w-0 flex-1">
<div class="flex-1 min-w-0 [&_[data-slot=input-wrapper]]:relative">
<div
classList={{
"size-1.5 rounded-full absolute left-3 z-10 pointer-events-none": true,
"bg-icon-success-base": props.status === true,
"bg-icon-critical-base": props.status === false,
"bg-border-weak-base": props.status === undefined,
}}
style={{ top: "50%", transform: "translateY(-50%)" }}
ref={(el) => {
// Position relative to input-wrapper
requestAnimationFrame(() => {
const wrapper = el.parentElement?.querySelector('[data-slot="input-wrapper"]')
if (wrapper instanceof HTMLElement) {
wrapper.style.position = "relative"
wrapper.appendChild(el)
}
})
}}
/>
<TextField
type="text"
hideLabel
@ -74,6 +86,7 @@ function AddRow(props: AddRowProps) {
onChange={props.onChange}
onKeyDown={props.onKeyDown}
onBlur={props.onBlur}
class="pl-7"
/>
</div>
</div>
@ -134,7 +147,14 @@ export function DialogSelectServer() {
status: undefined as boolean | undefined,
},
})
const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.())
const [defaultUrl, defaultUrlActions] = createResource(
async () => {
const url = await platform.getDefaultServerUrl?.()
if (!url) return null
return normalizeServerUrl(url) ?? null
},
{ initialValue: null },
)
const isDesktop = platform.platform === "desktop"
const looksComplete = (value: string) => {
@ -344,17 +364,23 @@ export function DialogSelectServer() {
return (
<Dialog title={language.t("dialog.server.title")}>
<div class="flex flex-col gap-2 pb-4">
<div class="flex flex-col gap-2">
<List
search={{ placeholder: language.t("dialog.server.search.placeholder"), autofocus: true }}
search={{ placeholder: language.t("dialog.server.search.placeholder"), autofocus: false }}
noInitialSelection
emptyMessage={language.t("dialog.server.empty")}
items={sortedItems}
key={(x) => x}
onSelect={(x) => {
if (x) select(x)
}}
onFilter={(value) => {
if (value && store.addServer.showForm && !store.addServer.adding) {
resetAdd()
}
}}
divider={true}
class="[&_[data-slot=list-scroll]]:max-h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-raised-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:py-3"
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]:max-h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-raised-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent [&_[data-slot=list-item-add]]:px-0"
add={
store.addServer.showForm
? {
@ -375,7 +401,35 @@ export function DialogSelectServer() {
}
>
{(i) => {
const [popoverOpen, setPopoverOpen] = createSignal(false)
const [truncated, setTruncated] = createSignal(false)
let nameRef: HTMLSpanElement | undefined
let versionRef: HTMLSpanElement | undefined
const check = () => {
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
const versionTruncated = versionRef ? versionRef.scrollWidth > versionRef.clientWidth : false
setTruncated(nameTruncated || versionTruncated)
}
createEffect(() => {
check()
window.addEventListener("resize", check)
onCleanup(() => window.removeEventListener("resize", check))
})
const tooltipValue = () => {
const name = serverDisplayName(i)
const version = store.status[i]?.version
return (
<span class="flex items-center gap-2">
<span>{name}</span>
<Show when={version}>
<span class="text-text-invert-base">{version}</span>
</Show>
</span>
)
}
return (
<div class="flex items-center gap-3 min-w-0 flex-1 group/item">
<Show
@ -393,58 +447,54 @@ export function DialogSelectServer() {
/>
}
>
<div
class="flex items-center gap-3 px-4 min-w-0 flex-1"
classList={{ "opacity-50": store.status[i]?.healthy === false }}
>
<Tooltip value={tooltipValue()} placement="top" inactive={!truncated()}>
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": store.status[i]?.healthy === true,
"bg-icon-critical-base": store.status[i]?.healthy === false,
"bg-border-weak-base": store.status[i] === undefined,
}}
/>
<span class="truncate">{serverDisplayName(i)}</span>
<Show when={store.status[i]?.version}>
<span class="text-text-weak text-14-regular">{store.status[i]?.version}</span>
</Show>
<Show when={defaultUrl() === i}>
<span class="text-text-weak bg-surface-base text-14-regular px-1.5 rounded-xs">
{language.t("dialog.server.status.default")}
class="flex items-center gap-3 px-4 min-w-0 flex-1"
classList={{ "opacity-50": store.status[i]?.healthy === false }}
>
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": store.status[i]?.healthy === true,
"bg-icon-critical-base": store.status[i]?.healthy === false,
"bg-border-weak-base": store.status[i] === undefined,
}}
/>
<span ref={nameRef} class="truncate">
{serverDisplayName(i)}
</span>
</Show>
</div>
<Show when={store.status[i]?.version}>
<span ref={versionRef} class="text-text-weak text-14-regular truncate">
{store.status[i]?.version}
</span>
</Show>
<Show when={defaultUrl() === i}>
<span class="text-text-weak bg-surface-base text-14-regular px-1.5 rounded-xs">
{language.t("dialog.server.status.default")}
</span>
</Show>
</div>
</Tooltip>
</Show>
<Show when={store.editServer.id !== i}>
<div class="flex items-center justify-center gap-5 px-4">
<div class="flex items-center justify-center gap-5 pl-4">
<Show when={current() === i}>
<p class="text-text-weak text-12-regular">{language.t("dialog.server.current")}</p>
</Show>
<div onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}>
<Popover
open={popoverOpen()}
onOpenChange={setPopoverOpen}
placement="bottom-start"
trigger={
<IconButton
icon="dot-grid"
variant="ghost"
class="bg-transparent transition-opacity shrink-0 hover:scale-110 size-8"
onPointerDown={(event: PointerEvent) => event.stopPropagation()}
/>
}
class="w-max !min-w-fit !max-w-none"
>
<div class="flex flex-col gap-1">
<Button
variant="ghost"
size="normal"
class="justify-start text-md"
onClick={(e: MouseEvent) => {
e.stopPropagation()
setPopoverOpen(false)
<DropdownMenu>
<DropdownMenu.Trigger
as={IconButton}
icon="dot-grid"
variant="ghost"
class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
onClick={(e: MouseEvent) => e.stopPropagation()}
onPointerDown={(e: PointerEvent) => e.stopPropagation()}
/>
<DropdownMenu.Portal>
<DropdownMenu.Content class="mt-1">
<DropdownMenu.Item
onSelect={() => {
setStore("editServer", {
id: i,
value: i,
@ -453,54 +503,42 @@ export function DialogSelectServer() {
})
}}
>
{language.t("dialog.server.menu.edit")}
</Button>
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<Show when={isDesktop && defaultUrl() !== i}>
<Button
variant="ghost"
size="normal"
class="justify-start text-md"
onClick={async (e: MouseEvent) => {
e.stopPropagation()
setPopoverOpen(false)
<DropdownMenu.Item
onSelect={async () => {
await platform.setDefaultServerUrl?.(i)
defaultUrlActions.refetch(i)
defaultUrlActions.mutate(i)
}}
>
{language.t("dialog.server.menu.default")}
</Button>
<DropdownMenu.ItemLabel>
{language.t("dialog.server.menu.default")}
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</Show>
<Show when={isDesktop && defaultUrl() === i}>
<Button
variant="ghost"
size="normal"
class="justify-start text-md"
onClick={async (e: MouseEvent) => {
e.stopPropagation()
setPopoverOpen(false)
<DropdownMenu.Item
onSelect={async () => {
await platform.setDefaultServerUrl?.(null)
defaultUrlActions.refetch(null)
defaultUrlActions.mutate(null)
}}
>
{language.t("dialog.server.menu.defaultRemove")}
</Button>
<DropdownMenu.ItemLabel>
{language.t("dialog.server.menu.defaultRemove")}
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</Show>
<div class="h-px bg-border-weak-base my-1" />
<Button
variant="ghost"
size="normal"
class="justify-start text-md text-text-on-critical-base hover:bg-surface-critical-weak"
onClick={(e: MouseEvent) => {
e.stopPropagation()
setPopoverOpen(false)
handleRemove(i)
}}
<DropdownMenu.Separator />
<DropdownMenu.Item
onSelect={() => handleRemove(i)}
class="text-text-on-critical-base hover:bg-surface-critical-weak"
>
{language.t("dialog.server.menu.delete")}
</Button>
</div>
</Popover>
</div>
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div>
</Show>
</div>
@ -508,7 +546,7 @@ export function DialogSelectServer() {
}}
</List>
<div class="px-6">
<div class="px-5 pb-5">
<Button
variant="secondary"
icon="plus-small"
@ -517,7 +555,7 @@ export function DialogSelectServer() {
setStore("addServer", { showForm: true, url: "", error: "" })
scrollListToBottom()
}}
class="px-3 py-4"
class="py-1.5 pl-1.5 pr-3 flex items-center gap-1.5"
>
{store.addServer.adding ? language.t("dialog.server.add.checking") : language.t("dialog.server.add.button")}
</Button>

View File

@ -184,6 +184,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!item.commentID) return
comments.setFocus({ file: item.path, id: item.commentID })
comments.setActive({ file: item.path, id: item.commentID })
view().reviewPanel.open()
if (item.commentOrigin === "review") {
@ -1711,15 +1712,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="flex flex-nowrap items-start gap-2 p-2 overflow-x-auto no-scrollbar">
<For each={prompt.context.items()}>
{(item) => {
const active = () => {
const a = comments.active()
return !!item.commentID && item.commentID === a?.id && item.path === a?.file
}
return (
<Tooltip
value={
<span class="flex max-w-[300px]">
<span
class="text-text-invert-base truncate min-w-0"
style={{ direction: "rtl", "text-align": "left" }}
style={{ direction: "rtl", "text-align": "left", "unicode-bidi": "plaintext" }}
>
<bdi>{getDirectory(item.path)}</bdi>
{getDirectory(item.path)}
</span>
<span class="shrink-0">{getFilename(item.path)}</span>
</span>
@ -1729,8 +1734,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
>
<div
classList={{
"group shrink-0 flex flex-col rounded-[6px] bg-background-stronger pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all shadow-xs-border hover:shadow-xs-border-hover": true,
"cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID,
"group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
"cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID && !active(),
"cursor-pointer bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover":
active(),
"bg-background-stronger": !active(),
}}
onClick={() => {
openComment(item)

View File

@ -163,7 +163,10 @@ export function SessionHeader() {
? language.t("session.share.popover.description.shared")
: language.t("session.share.popover.description.unshared")
}
gutter={8}
gutter={6}
placement="bottom-end"
shift={-64}
class="rounded-xl [&_[data-slot=popover-close-button]]:hidden"
triggerAs={Button}
triggerProps={{
variant: "secondary",
@ -192,8 +195,8 @@ export function SessionHeader() {
</div>
}
>
<div class="flex flex-col gap-2 w-72">
<TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
<div class="flex flex-col gap-2">
<TextField value={shareUrl() ?? ""} readOnly copyable tabIndex={-1} class="w-full" />
<div class="grid grid-cols-2 gap-2">
<Button
size="large"
@ -231,7 +234,7 @@ export function SessionHeader() {
gutter={8}
>
<IconButton
icon={state.copied ? "check" : "copy"}
icon={state.copied ? "check" : "link"}
variant="secondary"
class="rounded-l-none"
onClick={copyLink}
@ -246,7 +249,7 @@ export function SessionHeader() {
</Show>
</div>
</Show>
<div class="hidden md:block shrink-0">
<div class="hidden md:flex items-center gap-3 ml-2 shrink-0">
<TooltipKeybind
title={language.t("command.terminal.toggle")}
keybind={command.keybind("terminal.toggle")}

View File

@ -4,7 +4,6 @@ import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
import { Icon } from "@opencode-ai/ui/icon"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { Select } from "@opencode-ai/ui/select"
const MAIN_WORKTREE = "main"
const CREATE_WORKTREE = "create"
@ -60,18 +59,7 @@ export function NewSessionView(props: NewSessionViewProps) {
</div>
<div class="flex justify-center items-center gap-1">
<Icon name="branch" size="small" />
<Select
options={options()}
current={current()}
value={(x) => x}
label={label}
onSelect={(value) => {
props.onWorktreeChange(value ?? MAIN_WORKTREE)
}}
size="normal"
variant="ghost"
class="text-12-medium"
/>
<div class="text-12-medium text-text-weak select-text ml-2">{label(current())}</div>
</div>
<Show when={sync.project}>
{(project) => (

View File

@ -1,4 +1,4 @@
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
import { useNavigate } from "@solidjs/router"
import { useDialog } from "@opencode-ai/ui/context/dialog"
@ -7,6 +7,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
import { Button } from "@opencode-ai/ui/button"
import { Switch } from "@opencode-ai/ui/switch"
import { Icon } from "@opencode-ai/ui/icon"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
@ -138,7 +139,8 @@ export function StatusPopover() {
triggerAs={Button}
triggerProps={{
variant: "ghost",
class: "rounded-sm w-[75px] h-[24px] py-1.5 pr-3 pl-2 gap-2 border-none shadow-none",
class:
"rounded-sm w-[75px] h-[24px] py-1.5 pr-3 pl-2 gap-2 border-none shadow-none data-[expanded]:bg-surface-raised-base-active",
style: { scale: 1 },
}}
trigger={
@ -154,10 +156,15 @@ export function StatusPopover() {
<span class="text-12-regular text-text-strong">Status</span>
</div>
}
class="[&_[data-slot=popover-body]]:p-0 w-[360px] max-w-[calc(100vw-40px)] mx-5 bg-transparent border-0 shadow-none rounded-xl"
gutter={8}
class="[&_[data-slot=popover-body]]:p-0 w-[360px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
gutter={6}
placement="bottom-end"
shift={-136}
>
<div class="flex items-center gap-1 w-[360px] border border-border-weak-base rounded-xl">
<div
class="flex items-center gap-1 w-[360px] rounded-xl"
style={{ "box-shadow": "var(--shadow-lg-border-base)" }}
>
<Tabs
aria-label="Server Configurations"
class="tabs"
@ -197,58 +204,92 @@ export function StatusPopover() {
<Tabs.Content value="servers">
<div class="flex flex-col px-2 pb-2">
<div class="flex flex-col p-2 bg-background-base">
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
<For each={sortedServers()}>
{(url) => {
const isActive = () => url === server.url
const isDefault = () => url === defaultServerUrl()
const status = () => store.status[url]
const isBlocked = () => status()?.healthy === false
const [truncated, setTruncated] = createSignal(false)
let nameRef: HTMLSpanElement | undefined
let versionRef: HTMLSpanElement | undefined
onMount(() => {
const check = () => {
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
const versionTruncated = versionRef ? versionRef.scrollWidth > versionRef.clientWidth : false
setTruncated(nameTruncated || versionTruncated)
}
check()
window.addEventListener("resize", check)
onCleanup(() => window.removeEventListener("resize", check))
})
const tooltipValue = () => {
const name = serverDisplayName(url)
const version = status()?.version
return (
<span class="flex items-center gap-2">
<span>{name}</span>
<Show when={version}>
<span class="text-text-invert-base">{version}</span>
</Show>
</span>
)
}
return (
<button
type="button"
class="flex items-center gap-2 w-full px-2 py-1 rounded-md transition-colors text-left"
classList={{
"opacity-50": isBlocked(),
"hover:bg-surface-raised-base-hover": !isBlocked(),
"cursor-not-allowed": isBlocked(),
}}
aria-disabled={isBlocked()}
onClick={() => {
if (isBlocked()) return
server.setActive(url)
navigate("/")
}}
>
<div
<Tooltip value={tooltipValue()} placement="top" inactive={!truncated()}>
<button
type="button"
class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": status()?.healthy === true,
"bg-icon-critical-base": status()?.healthy === false,
"bg-border-weak-base": status() === undefined,
"opacity-50": isBlocked(),
"hover:bg-surface-raised-base-hover": !isBlocked(),
"cursor-not-allowed": isBlocked(),
}}
/>
<span class="text-14-regular text-text-base truncate">{serverDisplayName(url)}</span>
<Show when={status()?.version}>
<span class="text-12-regular text-text-weak">{status()?.version}</span>
</Show>
<Show when={isDefault()}>
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
Default
aria-disabled={isBlocked()}
onClick={() => {
if (isBlocked()) return
server.setActive(url)
navigate("/")
}}
>
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": status()?.healthy === true,
"bg-icon-critical-base": status()?.healthy === false,
"bg-border-weak-base": status() === undefined,
}}
/>
<span ref={nameRef} class="text-14-regular text-text-base truncate">
{serverDisplayName(url)}
</span>
</Show>
<div class="flex-1" />
<Show when={isActive()}>
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
</Show>
</button>
<Show when={status()?.version}>
<span ref={versionRef} class="text-12-regular text-text-weak truncate">
{status()?.version}
</span>
</Show>
<Show when={isDefault()}>
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
Default
</span>
</Show>
<div class="flex-1" />
<Show when={isActive()}>
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
</Show>
</button>
</Tooltip>
)
}}
</For>
<Button
variant="secondary"
class="mt-2 self-start"
class="mt-3 self-start h-8 px-3 py-1.5"
onClick={() => dialog.show(() => <DialogSelectServer />)}
>
Manage servers
@ -259,11 +300,11 @@ export function StatusPopover() {
<Tabs.Content value="mcp">
<div class="flex flex-col px-2 pb-2">
<div class="flex flex-col p-2 bg-background-base">
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
<Show
when={mcpItems().length > 0}
fallback={
<div class="text-14-regular text-text-weak text-center py-4">No MCP servers configured</div>
<div class="text-14-regular text-text-base text-center my-auto">No MCP servers configured</div>
}
>
<For each={mcpItems()}>
@ -272,7 +313,7 @@ export function StatusPopover() {
return (
<button
type="button"
class="flex items-center gap-2 w-full px-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
onClick={() => toggleMcp(item.name)}
disabled={loading() === item.name}
>
@ -305,11 +346,11 @@ export function StatusPopover() {
<Tabs.Content value="lsp">
<div class="flex flex-col px-2 pb-2">
<div class="flex flex-col p-2 bg-background-base">
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
<Show
when={lspItems().length > 0}
fallback={
<div class="text-14-regular text-text-weak text-center py-4">
<div class="text-14-regular text-text-base text-center my-auto">
LSPs auto-detected from file types
</div>
}
@ -335,13 +376,13 @@ export function StatusPopover() {
<Tabs.Content value="plugins">
<div class="flex flex-col px-2 pb-2">
<div class="flex flex-col p-2 bg-background-base">
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
<Show
when={plugins().length > 0}
fallback={
<div class="text-14-regular text-text-weak text-center py-4">
<div class="text-14-regular text-text-base text-center my-auto">
Plugins configured in{" "}
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm">opencode.json</code>
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm text-text-base">opencode.json</code>
</div>
}
>

View File

@ -111,6 +111,8 @@ export const Terminal = (props: TerminalProps) => {
const mod = await import("ghostty-web")
ghostty = await mod.Ghostty.load()
const once = { value: false }
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
if (window.__OPENCODE__?.serverPassword) {
url.username = "opencode"
@ -258,6 +260,8 @@ export const Terminal = (props: TerminalProps) => {
})
socket.addEventListener("error", (error) => {
if (disposed) return
if (once.value) return
once.value = true
console.error("WebSocket error:", error)
local.onConnectError?.(error)
})
@ -266,6 +270,8 @@ export const Terminal = (props: TerminalProps) => {
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
// For other codes (network issues, server restart), trigger error handler
if (event.code !== 1000) {
if (once.value) return
once.value = true
local.onConnectError?.(new Error(`WebSocket closed abnormally: ${event.code}`))
}
})

View File

@ -81,7 +81,7 @@ export function Titlebar() {
classList={{
"flex items-center w-full min-w-0": true,
"pl-2": !mac(),
"pr-2": !windows(),
"pr-6": !windows(),
}}
onMouseDown={drag}
data-tauri-drag-region
@ -145,6 +145,7 @@ export function Titlebar() {
data-tauri-drag-region
/>
<Show when={windows()}>
<div class="w-6 shrink-0" />
<div data-tauri-decorum-tb class="flex flex-row" />
</Show>
</div>

View File

@ -38,6 +38,7 @@ function createCommentSession(dir: string, id: string | undefined) {
)
const [focus, setFocus] = createSignal<CommentFocus | null>(null)
const [active, setActive] = createSignal<CommentFocus | null>(null)
const list = (file: string) => store.comments[file] ?? []
@ -76,6 +77,9 @@ function createCommentSession(dir: string, id: string | undefined) {
focus: createMemo(() => focus()),
setFocus,
clearFocus: () => setFocus(null),
active: createMemo(() => active()),
setActive,
clearActive: () => setActive(null),
}
}
@ -135,6 +139,9 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
focus: () => session().focus(),
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
clearFocus: () => session().clearFocus(),
active: () => session().active(),
setActive: (active: CommentFocus | null) => session().setActive(active),
clearActive: () => session().clearActive(),
}
},
})

View File

@ -13,7 +13,6 @@ export type LocalPTY = {
cols?: number
buffer?: string
scrollY?: number
error?: boolean
}
const WORKSPACE_KEY = "__workspace__"
@ -151,13 +150,18 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
return undefined
})
if (!clone?.data) return
setStore("all", index, {
...pty,
...clone.data,
const active = store.active === pty.id
batch(() => {
setStore("all", index, {
...pty,
...clone.data,
})
if (active) {
setStore("active", clone.data.id)
}
})
if (store.active === pty.id) {
setStore("active", clone.data.id)
}
},
open(id: string) {
setStore("active", id)

View File

@ -1,16 +1,4 @@
import {
For,
Index,
onCleanup,
onMount,
Show,
Match,
Switch,
createMemo,
createEffect,
on,
createSignal,
} from "solid-js"
import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on, createSignal } from "solid-js"
import { createMediaQuery } from "@solid-primitives/media"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { Dynamic } from "solid-js/web"
@ -41,7 +29,6 @@ import { useLayout } from "@/context/layout"
import { Terminal } from "@/components/terminal"
import { checksum, base64Encode, base64Decode } from "@opencode-ai/util/encode"
import { findLast } from "@opencode-ai/util/array"
import { getFilename } from "@opencode-ai/util/path"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectFile } from "@/components/dialog-select-file"
import { DialogSelectModel } from "@/components/dialog-select-model"
@ -67,7 +54,6 @@ import {
SortableTerminalTab,
NewSessionView,
} from "@/components/session"
import { usePlatform } from "@/context/platform"
import { navMark, navParams } from "@/utils/perf"
import { same } from "@/utils/same"
@ -103,7 +89,7 @@ function SessionReviewTab(props: SessionReviewTabProps) {
const sdk = useSDK()
const readFile = (path: string) => {
const readFile = async (path: string) => {
return sdk.client.file
.read({ path })
.then((x) => x.data)
@ -192,7 +178,6 @@ export default function Page() {
const codeComponent = useCodeComponent()
const command = useCommand()
const language = useLanguage()
const platform = usePlatform()
const params = useParams()
const navigate = useNavigate()
const sdk = useSDK()
@ -1537,9 +1522,9 @@ export default function Page() {
}}
onClick={autoScroll.handleInteraction}
class="relative min-w-0 w-full h-full overflow-y-auto session-scroller"
style={{ "--session-title-height": info()?.title ? "40px" : "0px" }}
style={{ "--session-title-height": info()?.title || info()?.parentID ? "40px" : "0px" }}
>
<Show when={info()?.title}>
<Show when={info()?.title || info()?.parentID}>
<div
classList={{
"sticky top-0 z-30 bg-background-stronger": true,
@ -1548,8 +1533,21 @@ export default function Page() {
"md:max-w-200 md:mx-auto": !showTabs(),
}}
>
<div class="h-10 flex items-center">
<h1 class="text-16-medium text-text-strong truncate">{info()?.title}</h1>
<div class="h-10 flex items-center gap-1">
<Show when={info()?.parentID}>
<IconButton
tabIndex={-1}
icon="arrow-left"
variant="ghost"
onClick={() => {
navigate(`/${params.dir}/session/${info()?.parentID}`)
}}
aria-label={language.t("common.goBack")}
/>
</Show>
<Show when={info()?.title}>
<h1 class="text-16-medium text-text-strong truncate">{info()?.title}</h1>
</Show>
</div>
</div>
</Show>
@ -2453,66 +2451,23 @@ export default function Page() {
</Tabs>
<div class="flex-1 min-h-0 relative">
<For each={terminal.all()}>
{(pty) => {
const [dismissed, setDismissed] = createSignal(false)
return (
<div
id={`terminal-wrapper-${pty.id}`}
class="absolute inset-0"
style={{
display: terminal.active() === pty.id ? "block" : "none",
}}
>
{(pty) => (
<div
id={`terminal-wrapper-${pty.id}`}
class="absolute inset-0"
style={{
display: terminal.active() === pty.id ? "block" : "none",
}}
>
<Show when={pty.id} keyed>
<Terminal
pty={pty}
onCleanup={(data) => terminal.update({ ...data, id: pty.id })}
onConnect={() => {
terminal.update({ id: pty.id, error: false })
setDismissed(false)
}}
onConnectError={() => {
setDismissed(false)
terminal.update({ id: pty.id, error: true })
}}
onCleanup={terminal.update}
onConnectError={() => terminal.clone(pty.id)}
/>
<Show when={pty.error && !dismissed()}>
<div
class="absolute inset-0 flex flex-col items-center justify-center gap-3"
style={{ "background-color": "rgba(0, 0, 0, 0.6)" }}
>
<Icon
name="circle-ban-sign"
class="w-8 h-8"
style={{ color: "rgba(239, 68, 68, 0.8)" }}
/>
<div class="text-center" style={{ color: "rgba(255, 255, 255, 0.7)" }}>
<div class="text-14-semibold mb-1">{language.t("terminal.connectionLost.title")}</div>
<div class="text-12-regular" style={{ color: "rgba(255, 255, 255, 0.5)" }}>
{language.t("terminal.connectionLost.description")}
</div>
</div>
<button
class="mt-2 px-3 py-1.5 text-12-medium rounded-lg transition-colors"
style={{
"background-color": "rgba(255, 255, 255, 0.1)",
color: "rgba(255, 255, 255, 0.7)",
border: "1px solid rgba(255, 255, 255, 0.2)",
}}
onMouseEnter={(e) =>
(e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.15)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.1)")
}
onClick={() => setDismissed(true)}
>
{language.t("common.dismiss")}
</button>
</div>
</Show>
</div>
)
}}
</Show>
</div>
)}
</For>
</div>
</div>

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.1.34",
"version": "1.1.35",
"type": "module",
"license": "MIT",
"scripts": {

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.1.34",
"version": "1.1.35",
"private": true,
"type": "module",
"license": "MIT",

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.1.34",
"version": "1.1.35",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.1.34",
"version": "1.1.35",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",

View File

@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.1.34",
"version": "1.1.35",
"type": "module",
"license": "MIT",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.1.34",
"version": "1.1.35",
"private": true,
"type": "module",
"license": "MIT",

View File

@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.1.34"
version = "1.1.35"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-darwin-arm64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-darwin-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-linux-arm64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-linux-x64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-windows-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.1.34",
"version": "1.1.35",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View File

@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.1.34",
"version": "1.1.35",
"name": "opencode",
"type": "module",
"license": "MIT",

View File

@ -10,7 +10,7 @@ export namespace Question {
export const Option = z
.object({
label: z.string().max(30).describe("Display text (1-5 words, concise)"),
label: z.string().describe("Display text (1-5 words, concise)"),
description: z.string().describe("Explanation of choice"),
})
.meta({
@ -21,7 +21,7 @@ export namespace Question {
export const Info = z
.object({
question: z.string().describe("Complete question"),
header: z.string().max(30).describe("Very short label (max 30 chars)"),
header: z.string().describe("Very short label (max 30 chars)"),
options: z.array(Option).describe("Available choices"),
multiple: z.boolean().optional().describe("Allow selecting multiple choices"),
custom: z.boolean().optional().describe("Allow typing a custom answer (default: true)"),

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.1.34",
"version": "1.1.35",
"type": "module",
"license": "MIT",
"scripts": {

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.1.34",
"version": "1.1.35",
"type": "module",
"license": "MIT",
"scripts": {
@ -20,7 +20,7 @@
"dist"
],
"devDependencies": {
"@hey-api/openapi-ts": "0.90.4",
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"typescript": "catalog:",

View File

@ -2518,7 +2518,17 @@ export class Control extends HeyApiClient {
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }, { in: "body" }] }])
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ key: "body", map: "body" },
],
},
],
)
return (options?.client ?? this.client).post<TuiControlResponseResponses, unknown, ThrowOnError>({
url: "/tui/control/response",
...options,
@ -2770,7 +2780,17 @@ export class Tui extends HeyApiClient {
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }, { in: "body" }] }])
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ key: "body", map: "body" },
],
},
],
)
return (options?.client ?? this.client).post<TuiPublishResponses, TuiPublishErrors, ThrowOnError>({
url: "/tui/publish",
...options,

View File

@ -7319,8 +7319,7 @@
"properties": {
"label": {
"description": "Display text (1-5 words, concise)",
"type": "string",
"maxLength": 30
"type": "string"
},
"description": {
"description": "Explanation of choice",
@ -7338,8 +7337,7 @@
},
"header": {
"description": "Very short label (max 30 chars)",
"type": "string",
"maxLength": 30
"type": "string"
},
"options": {
"description": "Available choices",

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.1.34",
"version": "1.1.35",
"type": "module",
"license": "MIT",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.1.34",
"version": "1.1.35",
"type": "module",
"license": "MIT",
"exports": {
@ -56,6 +56,7 @@
"marked": "catalog:",
"marked-katex-extension": "5.1.6",
"marked-shiki": "catalog:",
"morphdom": "2.7.8",
"remeda": "catalog:",
"shiki": "catalog:",
"solid-js": "catalog:",

View File

@ -66,7 +66,7 @@
[data-slot="dialog-header"] {
display: flex;
padding: 16px 16px 16px 24px;
padding: 20px;
justify-content: space-between;
align-items: center;
flex-shrink: 0;

View File

@ -72,6 +72,7 @@ const icons = {
keyboard: `<path d="M5.125 7.375V4.375H14.875V2.875M8.3125 13.9375H11.6875M8.125 13.9375H11.875M2.125 7.375H17.875V17.125H2.125V7.375ZM5.5 10.375H5.125V10.75H5.5V10.375ZM8.5 10.375H8.125V10.75H8.5V10.375ZM11.875 10.375H11.5V10.75H11.875V10.375ZM14.875 10.375H14.5V10.75H14.875V10.375ZM14.875 13.75H14.5V14.125H14.875V13.75ZM5.5 13.75H5.125V14.125H5.5V13.75Z" stroke="currentColor" stroke-linecap="square"/>`,
selector: `<path d="M6.66626 12.5033L9.99959 15.8366L13.3329 12.5033M6.66626 7.50326L9.99959 4.16992L13.3329 7.50326" stroke="currentColor" stroke-linecap="square"/>`,
"arrow-down-to-line": `<path d="M15.2083 11.6667L10 16.875L4.79167 11.6667M10 16.25V3.125" stroke="currentColor" stroke-width="1.25" stroke-linecap="square"/>`,
link: `<path d="M2.08334 12.0833L1.72979 11.7298L1.37624 12.0833L1.72979 12.4369L2.08334 12.0833ZM7.91668 17.9167L7.56312 18.2702L7.91668 18.6238L8.27023 18.2702L7.91668 17.9167ZM17.9167 7.91666L18.2702 8.27022L18.6238 7.91666L18.2702 7.56311L17.9167 7.91666ZM12.0833 2.08333L12.4369 1.72977L12.0833 1.37622L11.7298 1.72977L12.0833 2.08333ZM8.39646 5.06311L8.0429 5.41666L8.75001 6.12377L9.10356 5.77021L8.75001 5.41666L8.39646 5.06311ZM5.77023 9.10355L6.12378 8.74999L5.41668 8.04289L5.06312 8.39644L5.41668 8.74999L5.77023 9.10355ZM14.2298 10.8964L13.8762 11.25L14.5833 11.9571L14.9369 11.6035L14.5833 11.25L14.2298 10.8964ZM11.6036 14.9369L11.9571 14.5833L11.25 13.8762L10.8965 14.2298L11.25 14.5833L11.6036 14.9369ZM7.14646 12.1464L6.7929 12.5L7.50001 13.2071L7.85356 12.8535L7.50001 12.5L7.14646 12.1464ZM12.8536 7.85355L13.2071 7.49999L12.5 6.79289L12.1465 7.14644L12.5 7.49999L12.8536 7.85355ZM2.08334 12.0833L1.72979 12.4369L7.56312 18.2702L7.91668 17.9167L8.27023 17.5631L2.4369 11.7298L2.08334 12.0833ZM17.9167 7.91666L18.2702 7.56311L12.4369 1.72977L12.0833 2.08333L11.7298 2.43688L17.5631 8.27022L17.9167 7.91666ZM12.0833 2.08333L11.7298 1.72977L8.39646 5.06311L8.75001 5.41666L9.10356 5.77021L12.4369 2.43688L12.0833 2.08333ZM5.41668 8.74999L5.06312 8.39644L1.72979 11.7298L2.08334 12.0833L2.4369 12.4369L5.77023 9.10355L5.41668 8.74999ZM14.5833 11.25L14.9369 11.6035L18.2702 8.27022L17.9167 7.91666L17.5631 7.56311L14.2298 10.8964L14.5833 11.25ZM7.91668 17.9167L8.27023 18.2702L11.6036 14.9369L11.25 14.5833L10.8965 14.2298L7.56312 17.5631L7.91668 17.9167ZM7.50001 12.5L7.85356 12.8535L12.8536 7.85355L12.5 7.49999L12.1465 7.14644L7.14646 12.1464L7.50001 12.5Z" fill="currentColor"/>`,
}
export interface IconProps extends ComponentProps<"svg"> {

View File

@ -4,6 +4,10 @@
z-index: var(--line-comment-z, 30);
}
[data-component="line-comment"][data-open] {
z-index: var(--line-comment-open-z, 100);
}
[data-component="line-comment"] [data-slot="line-comment-button"] {
width: 20px;
height: 20px;

View File

@ -25,6 +25,7 @@ export const LineCommentAnchor = (props: LineCommentAnchorProps) => {
data-component="line-comment"
data-variant={variant()}
data-comment-id={props.id}
data-open={props.open ? "" : undefined}
classList={{
[props.class ?? ""]: !!props.class,
}}

View File

@ -30,7 +30,6 @@
gap: 8px;
align-self: stretch;
margin-bottom: 4px;
padding-right: 4px;
> [data-component="icon-button"] {
width: 24px;

View File

@ -38,6 +38,7 @@ export interface ListProps<T> extends FilteredListProps<T> {
loadingMessage?: string
onKeyEvent?: (event: KeyboardEvent, item: T | undefined) => void
onMove?: (item: T | undefined) => void
onFilter?: (value: string) => void
activeIcon?: IconProps["name"]
filter?: string
search?: ListSearchProps | boolean
@ -98,6 +99,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
const current = internalFilter()
if (prev !== current) {
onInput(current)
props.onFilter?.(current)
}
return current
}, "")

View File

@ -1,6 +1,7 @@
import { useMarked } from "../context/marked"
import { useI18n } from "../context/i18n"
import DOMPurify from "dompurify"
import morphdom from "morphdom"
import { checksum } from "@opencode-ai/util/encode"
import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js"
import { isServer } from "solid-js/web"
@ -194,18 +195,61 @@ export function Markdown(
{ initialValue: "" },
)
let copySetupTimer: ReturnType<typeof setTimeout> | undefined
let copyCleanup: (() => void) | undefined
createEffect(() => {
const container = root()
const content = html()
if (!container) return
if (!content) return
if (isServer) return
const cleanup = setupCodeCopy(container, {
copy: i18n.t("ui.message.copy"),
copied: i18n.t("ui.message.copied"),
if (!content) {
container.innerHTML = ""
return
}
const temp = document.createElement("div")
temp.innerHTML = content
morphdom(container, temp, {
childrenOnly: true,
onBeforeElUpdated: (fromEl, toEl) => {
if (fromEl.isEqualNode(toEl)) return false
if (fromEl.getAttribute("data-component") === "markdown-code") {
const fromPre = fromEl.querySelector("pre")
const toPre = toEl.querySelector("pre")
if (fromPre && toPre && !fromPre.isEqualNode(toPre)) {
morphdom(fromPre, toPre)
}
return false
}
return true
},
onBeforeNodeDiscarded: (node) => {
if (node instanceof Element) {
if (node.getAttribute("data-slot") === "markdown-copy-button") return false
if (node.getAttribute("data-component") === "markdown-code") return false
}
return true
},
})
onCleanup(cleanup)
if (copySetupTimer) clearTimeout(copySetupTimer)
copySetupTimer = setTimeout(() => {
if (copyCleanup) copyCleanup()
copyCleanup = setupCodeCopy(container, {
copy: i18n.t("ui.message.copy"),
copied: i18n.t("ui.message.copied"),
})
}, 150)
})
onCleanup(() => {
if (copySetupTimer) clearTimeout(copySetupTimer)
if (copyCleanup) copyCleanup()
})
return (
<div
data-component="markdown"
@ -213,7 +257,6 @@ export function Markdown(
...(local.classList ?? {}),
[local.class ?? ""]: !!local.class,
}}
innerHTML={html.latest}
ref={setRoot}
{...others}
/>

View File

@ -1,5 +1,15 @@
import { Popover as Kobalte } from "@kobalte/core/popover"
import { ComponentProps, JSXElement, ParentProps, Show, splitProps, ValidComponent } from "solid-js"
import {
ComponentProps,
JSXElement,
ParentProps,
Show,
createEffect,
createSignal,
onCleanup,
splitProps,
ValidComponent,
} from "solid-js"
import { useI18n } from "../context/i18n"
import { IconButton } from "./icon-button"
@ -13,6 +23,7 @@ export interface PopoverProps<T extends ValidComponent = "div">
description?: JSXElement
class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"]
style?: ComponentProps<"div">["style"]
portal?: boolean
}
@ -26,17 +37,96 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
"description",
"class",
"classList",
"style",
"children",
"portal",
"open",
"defaultOpen",
"onOpenChange",
"modal",
])
const [contentRef, setContentRef] = createSignal<HTMLElement | undefined>(undefined)
const [triggerRef, setTriggerRef] = createSignal<HTMLElement | undefined>(undefined)
const [dismiss, setDismiss] = createSignal<"escape" | "outside" | null>(null)
const [uncontrolledOpen, setUncontrolledOpen] = createSignal<boolean>(local.defaultOpen ?? false)
const controlled = () => local.open !== undefined
const opened = () => {
if (controlled()) return local.open ?? false
return uncontrolledOpen()
}
const onOpenChange = (next: boolean) => {
if (next) setDismiss(null)
if (local.onOpenChange) local.onOpenChange(next)
if (controlled()) return
setUncontrolledOpen(next)
}
createEffect(() => {
if (!opened()) return
const inside = (node: Node | null | undefined) => {
if (!node) return false
const content = contentRef()
if (content && content.contains(node)) return true
const trigger = triggerRef()
if (trigger && trigger.contains(node)) return true
return false
}
const close = (reason: "escape" | "outside") => {
setDismiss(reason)
onOpenChange(false)
}
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== "Escape") return
close("escape")
event.preventDefault()
event.stopPropagation()
}
const onPointerDown = (event: PointerEvent) => {
const target = event.target
if (!(target instanceof Node)) return
if (inside(target)) return
close("outside")
}
const onFocusIn = (event: FocusEvent) => {
const target = event.target
if (!(target instanceof Node)) return
if (inside(target)) return
close("outside")
}
window.addEventListener("keydown", onKeyDown, true)
window.addEventListener("pointerdown", onPointerDown, true)
window.addEventListener("focusin", onFocusIn, true)
onCleanup(() => {
window.removeEventListener("keydown", onKeyDown, true)
window.removeEventListener("pointerdown", onPointerDown, true)
window.removeEventListener("focusin", onFocusIn, true)
})
})
const content = () => (
<Kobalte.Content
ref={(el: HTMLElement | undefined) => setContentRef(el)}
data-component="popover-content"
classList={{
...(local.classList ?? {}),
[local.class ?? ""]: !!local.class,
}}
style={local.style}
onCloseAutoFocus={(event: Event) => {
if (dismiss() === "outside") event.preventDefault()
setDismiss(null)
}}
>
{/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
<Show when={local.title}>
@ -59,8 +149,13 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
)
return (
<Kobalte gutter={4} {...rest}>
<Kobalte.Trigger as={local.triggerAs ?? "div"} data-slot="popover-trigger" {...(local.triggerProps as any)}>
<Kobalte gutter={4} {...rest} open={opened()} onOpenChange={onOpenChange} modal={local.modal ?? false}>
<Kobalte.Trigger
ref={(el: HTMLElement) => setTriggerRef(el)}
as={local.triggerAs ?? "div"}
data-slot="popover-trigger"
{...(local.triggerProps as any)}
>
{local.trigger}
</Kobalte.Trigger>
<Show when={local.portal ?? true} fallback={content()}>

View File

@ -267,6 +267,6 @@
position: relative;
overflow: hidden;
--line-comment-z: 5;
--line-comment-popover-z: 6;
--line-comment-popover-z: 30;
}
}

View File

@ -63,6 +63,12 @@
align-items: center;
justify-content: center;
padding: 14px 24px;
outline: none;
&:focus-visible {
outline: none;
box-shadow: none;
}
}
[data-slot="tabs-trigger-close-button"] {
@ -81,7 +87,7 @@
}
&:focus-visible {
outline: none;
box-shadow: 0 0 0 2px var(--border-focus);
box-shadow: none;
}
&:has([data-hidden]) {
[data-slot="tabs-trigger-close-button"] {

View File

@ -51,7 +51,7 @@
border: 1px solid var(--border-weak-base);
background: var(--input-base);
&:focus-within {
&:focus-within:not(:has([data-readonly])) {
border-color: transparent;
/* border/shadow-xs/select */
box-shadow:

View File

@ -93,17 +93,20 @@ export function TextField(props: TextFieldProps) {
</Show>
<Show when={local.copyable}>
<Tooltip
value={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyToClipboard")}
value={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyLink")}
placement="top"
gutter={8}
gutter={4}
forceOpen={copied()}
skipDelayDuration={0}
>
<IconButton
type="button"
icon={copied() ? "check" : "copy"}
icon={copied() ? "check" : "link"}
variant="ghost"
onClick={handleCopy}
tabIndex={-1}
data-slot="input-copy-button"
aria-label={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyToClipboard")}
aria-label={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyLink")}
/>
</Tooltip>
</Show>

View File

@ -44,7 +44,7 @@
/* transform: translate3d(0, 0, 0); */
}
&[data-closed] {
&[data-closed]:not([data-force-open="true"]) {
opacity: 0;
}

View File

@ -8,6 +8,7 @@ export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
contentClass?: string
contentStyle?: JSX.CSSProperties
inactive?: boolean
forceOpen?: boolean
}
export interface TooltipKeybindProps extends Omit<TooltipProps, "value"> {
@ -32,7 +33,14 @@ export function TooltipKeybind(props: TooltipKeybindProps) {
export function Tooltip(props: TooltipProps) {
const [open, setOpen] = createSignal(false)
const [local, others] = splitProps(props, ["children", "class", "contentClass", "contentStyle", "inactive"])
const [local, others] = splitProps(props, [
"children",
"class",
"contentClass",
"contentStyle",
"inactive",
"forceOpen",
])
const c = children(() => local.children)
@ -55,7 +63,7 @@ export function Tooltip(props: TooltipProps) {
<Switch>
<Match when={local.inactive}>{local.children}</Match>
<Match when={true}>
<KobalteTooltip forceMount gutter={4} {...others} open={open()} onOpenChange={setOpen}>
<KobalteTooltip forceMount gutter={4} {...others} open={local.forceOpen || open()} onOpenChange={setOpen}>
<KobalteTooltip.Trigger as={"div"} data-component="tooltip-trigger" class={local.class}>
{c()}
</KobalteTooltip.Trigger>
@ -63,6 +71,7 @@ export function Tooltip(props: TooltipProps) {
<KobalteTooltip.Content
data-component="tooltip"
data-placement={props.placement}
data-force-open={local.forceOpen}
class={local.contentClass}
style={local.contentStyle}
>

View File

@ -30,6 +30,10 @@ export function createAutoScroll(options: AutoScrollOptions) {
return el.scrollHeight - el.clientHeight - el.scrollTop
}
const canScroll = (el: HTMLElement) => {
return el.scrollHeight - el.clientHeight > 1
}
// Browsers can dispatch scroll events asynchronously. If new content arrives
// between us calling `scrollTo()` and the subsequent `scroll` event firing,
// the handler can see a non-zero `distanceFromBottom` and incorrectly assume
@ -89,6 +93,12 @@ export function createAutoScroll(options: AutoScrollOptions) {
}
const stop = () => {
const el = scroll
if (!el) return
if (!canScroll(el)) {
if (store.userScrolled) setStore("userScrolled", false)
return
}
if (store.userScrolled) return
setStore("userScrolled", true)
@ -111,6 +121,11 @@ export function createAutoScroll(options: AutoScrollOptions) {
const el = scroll
if (!el) return
if (!canScroll(el)) {
if (store.userScrolled) setStore("userScrolled", false)
return
}
if (distanceFromBottom(el) < threshold()) {
if (store.userScrolled) setStore("userScrolled", false)
return
@ -149,6 +164,11 @@ export function createAutoScroll(options: AutoScrollOptions) {
createResizeObserver(
() => store.contentRef,
() => {
const el = scroll
if (el && !canScroll(el)) {
if (store.userScrolled) setStore("userScrolled", false)
return
}
if (!active()) return
if (store.userScrolled) return
// ResizeObserver fires after layout, before paint.
@ -159,7 +179,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
)
createEffect(
on(options.working, (working) => {
on(options.working, (working: boolean) => {
settling = false
if (settleTimer) clearTimeout(settleTimer)
settleTimer = undefined

View File

@ -13,6 +13,7 @@ export interface FilteredListProps<T> {
sortBy?: (a: T, b: T) => number
sortGroupsBy?: (a: { category: string; items: T[] }, b: { category: string; items: T[] }) => number
onSelect?: (value: T | undefined, index: number) => void
noInitialSelection?: boolean
}
export function useFilteredList<T>(props: FilteredListProps<T>) {
@ -57,6 +58,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
})
function initialActive() {
if (props.noInitialSelection) return ""
if (props.current) return props.key(props.current)
const items = flat()
@ -71,6 +73,10 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
})
const reset = () => {
if (props.noInitialSelection) {
list.setActive("")
return
}
const all = flat()
if (all.length === 0) return
list.setActive(props.key(all[0]))

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "رسالة جديدة",
"ui.textField.copyToClipboard": "نسخ إلى الحافظة",
"ui.textField.copyLink": "نسخ الرابط",
"ui.textField.copied": "تم النسخ",
"ui.imagePreview.alt": "معاينة الصورة",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "Nova mensagem",
"ui.textField.copyToClipboard": "Copiar para área de transferência",
"ui.textField.copyLink": "Copiar link",
"ui.textField.copied": "Copiado",
"ui.imagePreview.alt": "Visualização de imagem",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "Ny besked",
"ui.textField.copyToClipboard": "Kopier til udklipsholder",
"ui.textField.copyLink": "Kopier link",
"ui.textField.copied": "Kopieret",
"ui.imagePreview.alt": "Billedforhåndsvisning",

View File

@ -44,6 +44,7 @@ export const dict = {
"ui.messageNav.newMessage": "Neue Nachricht",
"ui.textField.copyToClipboard": "In die Zwischenablage kopieren",
"ui.textField.copyLink": "Link kopieren",
"ui.textField.copied": "Kopiert",
"ui.imagePreview.alt": "Bildvorschau",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "New message",
"ui.textField.copyToClipboard": "Copy to clipboard",
"ui.textField.copyLink": "Copy link",
"ui.textField.copied": "Copied",
"ui.imagePreview.alt": "Image preview",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "Nuevo mensaje",
"ui.textField.copyToClipboard": "Copiar al portapapeles",
"ui.textField.copyLink": "Copiar enlace",
"ui.textField.copied": "Copiado",
"ui.imagePreview.alt": "Vista previa de imagen",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "Nouveau message",
"ui.textField.copyToClipboard": "Copier dans le presse-papiers",
"ui.textField.copyLink": "Copier le lien",
"ui.textField.copied": "Copié",
"ui.imagePreview.alt": "Aperçu de l'image",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "新しいメッセージ",
"ui.textField.copyToClipboard": "クリップボードにコピー",
"ui.textField.copyLink": "リンクをコピー",
"ui.textField.copied": "コピーしました",
"ui.imagePreview.alt": "画像プレビュー",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "새 메시지",
"ui.textField.copyToClipboard": "클립보드에 복사",
"ui.textField.copyLink": "링크 복사",
"ui.textField.copied": "복사됨",
"ui.imagePreview.alt": "이미지 미리보기",

View File

@ -43,6 +43,7 @@ export const dict: Record<Keys, string> = {
"ui.messageNav.newMessage": "Ny melding",
"ui.textField.copyToClipboard": "Kopier til utklippstavlen",
"ui.textField.copyLink": "Kopier lenke",
"ui.textField.copied": "Kopiert",
"ui.imagePreview.alt": "Bildeforhåndsvisning",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "Nowa wiadomość",
"ui.textField.copyToClipboard": "Skopiuj do schowka",
"ui.textField.copyLink": "Skopiuj link",
"ui.textField.copied": "Skopiowano",
"ui.imagePreview.alt": "Podgląd obrazu",

View File

@ -40,6 +40,7 @@ export const dict = {
"ui.messageNav.newMessage": "Новое сообщение",
"ui.textField.copyToClipboard": "Копировать в буфер обмена",
"ui.textField.copyLink": "Копировать ссылку",
"ui.textField.copied": "Скопировано",
"ui.imagePreview.alt": "Предпросмотр изображения",

View File

@ -44,6 +44,7 @@ export const dict = {
"ui.messageNav.newMessage": "新消息",
"ui.textField.copyToClipboard": "复制到剪贴板",
"ui.textField.copyLink": "复制链接",
"ui.textField.copied": "已复制",
"ui.imagePreview.alt": "图片预览",

View File

@ -44,6 +44,7 @@ export const dict = {
"ui.messageNav.newMessage": "新訊息",
"ui.textField.copyToClipboard": "複製到剪貼簿",
"ui.textField.copyLink": "複製連結",
"ui.textField.copied": "已複製",
"ui.imagePreview.alt": "圖片預覽",

View File

@ -120,7 +120,7 @@
--surface-brand-base: var(--yuzu-light-9);
--surface-brand-hover: var(--yuzu-light-10);
--surface-interactive-base: var(--cobalt-light-3);
--surface-interactive-hover: var(--cobalt-light-4);
--surface-interactive-hover: #e5f0ff;
--surface-interactive-weak: var(--cobalt-light-2);
--surface-interactive-weak-hover: var(--cobalt-light-3);
--surface-success-base: var(--apple-light-3);
@ -376,9 +376,9 @@
--surface-raised-stronger-non-alpha: var(--smoke-dark-3);
--surface-brand-base: var(--yuzu-light-9);
--surface-brand-hover: var(--yuzu-light-10);
--surface-interactive-base: var(--cobalt-light-3);
--surface-interactive-hover: var(--cobalt-light-4);
--surface-interactive-weak: var(--cobalt-light-2);
--surface-interactive-base: var(--cobalt-dark-3);
--surface-interactive-hover: #0a1d4d;
--surface-interactive-weak: var(--cobalt-dark-2);
--surface-interactive-weak-hover: var(--cobalt-light-3);
--surface-success-base: var(--apple-light-3);
--surface-success-weak: var(--apple-light-2);

View File

@ -46,7 +46,7 @@
"surface-brand-base": "var(--yuzu-light-9)",
"surface-brand-hover": "var(--yuzu-light-10)",
"surface-interactive-base": "var(--cobalt-light-3)",
"surface-interactive-hover": "var(--cobalt-light-4)",
"surface-interactive-hover": "#E5F0FF",
"surface-interactive-weak": "var(--cobalt-light-2)",
"surface-interactive-weak-hover": "var(--cobalt-light-3)",
"surface-success-base": "var(--apple-light-3)",
@ -311,9 +311,9 @@
"surface-raised-stronger-non-alpha": "var(--smoke-dark-3)",
"surface-brand-base": "var(--yuzu-light-9)",
"surface-brand-hover": "var(--yuzu-light-10)",
"surface-interactive-base": "var(--cobalt-light-3)",
"surface-interactive-hover": "var(--cobalt-light-4)",
"surface-interactive-weak": "var(--cobalt-light-2)",
"surface-interactive-base": "var(--cobalt-dark-3)",
"surface-interactive-hover": "#0A1D4D",
"surface-interactive-weak": "var(--cobalt-dark-2)",
"surface-interactive-weak-hover": "var(--cobalt-light-3)",
"surface-success-base": "var(--apple-dark-3)",
"surface-success-weak": "var(--apple-dark-2)",

View File

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
"version": "1.1.34",
"version": "1.1.35",
"private": true,
"type": "module",
"license": "MIT",

View File

@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
"version": "1.1.34",
"version": "1.1.35",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

View File

@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.1.34",
"version": "1.1.35",
"publisher": "sst-dev",
"repository": {
"type": "git",