chore(observability): merge v2

This commit is contained in:
starptech 2026-07-09 06:36:36 +02:00
commit f4e661f874
276 changed files with 17001 additions and 3755 deletions

View file

@ -30,7 +30,7 @@
}, },
"packages/app": { "packages/app": {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@dnd-kit/abstract": "0.5.0", "@dnd-kit/abstract": "0.5.0",
"@dnd-kit/dom": "0.5.0", "@dnd-kit/dom": "0.5.0",
@ -94,7 +94,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@opencode-ai/cli", "name": "@opencode-ai/cli",
"version": "1.17.14", "version": "1.17.15",
"bin": { "bin": {
"opencode2": "./bin/opencode2.cjs", "opencode2": "./bin/opencode2.cjs",
}, },
@ -114,10 +114,12 @@
"effect": "catalog:", "effect": "catalog:",
"fuzzysort": "catalog:", "fuzzysort": "catalog:",
"jsonc-parser": "3.3.1", "jsonc-parser": "3.3.1",
"open": "10.1.2",
"opentui-spinner": "catalog:", "opentui-spinner": "catalog:",
"semver": "catalog:", "semver": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"strip-ansi": "7.1.2", "strip-ansi": "7.1.2",
"uqr": "0.1.3",
}, },
"devDependencies": { "devDependencies": {
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",
@ -151,7 +153,7 @@
}, },
"packages/codemode": { "packages/codemode": {
"name": "@opencode-ai/codemode", "name": "@opencode-ai/codemode",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"acorn": "8.15.0", "acorn": "8.15.0",
"effect": "catalog:", "effect": "catalog:",
@ -165,7 +167,7 @@
}, },
"packages/console/app": { "packages/console/app": {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
@ -201,7 +203,7 @@
}, },
"packages/console/core": { "packages/console/core": {
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@aws-sdk/client-sts": "3.782.0", "@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1", "@jsx-email/render": "1.1.1",
@ -228,7 +230,7 @@
}, },
"packages/console/function": { "packages/console/function": {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "3.0.82", "@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/openai": "3.0.48", "@ai-sdk/openai": "3.0.48",
@ -250,7 +252,7 @@
}, },
"packages/console/mail": { "packages/console/mail": {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
@ -274,7 +276,7 @@
}, },
"packages/console/support": { "packages/console/support": {
"name": "@opencode-ai/console-support", "name": "@opencode-ai/console-support",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-core": "workspace:*",
@ -294,7 +296,7 @@
}, },
"packages/core": { "packages/core": {
"name": "@opencode-ai/core", "name": "@opencode-ai/core",
"version": "1.17.14", "version": "1.17.15",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@ -390,7 +392,7 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@zip.js/zip.js": "2.7.62", "@zip.js/zip.js": "2.7.62",
"effect": "catalog:", "effect": "catalog:",
@ -450,7 +452,7 @@
}, },
"packages/effect-drizzle-sqlite": { "packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite", "name": "@opencode-ai/effect-drizzle-sqlite",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
@ -464,7 +466,7 @@
}, },
"packages/effect-sqlite-node": { "packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node", "name": "@opencode-ai/effect-sqlite-node",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"effect": "catalog:", "effect": "catalog:",
}, },
@ -476,7 +478,7 @@
}, },
"packages/enterprise": { "packages/enterprise": {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@hono/standard-validator": "catalog:", "@hono/standard-validator": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@ -508,7 +510,7 @@
}, },
"packages/function": { "packages/function": {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@octokit/auth-app": "8.0.1", "@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
@ -524,7 +526,7 @@
}, },
"packages/http-recorder": { "packages/http-recorder": {
"name": "@opencode-ai/http-recorder", "name": "@opencode-ai/http-recorder",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@effect/platform-node-shared": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83",
}, },
@ -556,7 +558,7 @@
}, },
"packages/llm": { "packages/llm": {
"name": "@opencode-ai/llm", "name": "@opencode-ai/llm",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14", "@smithy/eventstream-codec": "4.2.14",
@ -575,7 +577,7 @@
}, },
"packages/opencode": { "packages/opencode": {
"name": "opencode", "name": "opencode",
"version": "1.17.14", "version": "1.17.15",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@ -708,7 +710,7 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@ai-sdk/provider": "3.0.8", "@ai-sdk/provider": "3.0.8",
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
@ -795,7 +797,7 @@
}, },
"packages/sdk/js": { "packages/sdk/js": {
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"cross-spawn": "catalog:", "cross-spawn": "catalog:",
}, },
@ -810,7 +812,7 @@
}, },
"packages/server": { "packages/server": {
"name": "@opencode-ai/server", "name": "@opencode-ai/server",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@ -827,7 +829,7 @@
}, },
"packages/session-ui": { "packages/session-ui": {
"name": "@opencode-ai/session-ui", "name": "@opencode-ai/session-ui",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@ -888,7 +890,7 @@
}, },
"packages/slack": { "packages/slack": {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1", "@slack/bolt": "^3.17.1",
@ -901,7 +903,7 @@
}, },
"packages/stats/app": { "packages/stats/app": {
"name": "@opencode-ai/stats-app", "name": "@opencode-ai/stats-app",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
"@opencode-ai/stats-core": "workspace:*", "@opencode-ai/stats-core": "workspace:*",
@ -934,7 +936,7 @@
}, },
"packages/stats/core": { "packages/stats/core": {
"name": "@opencode-ai/stats-core", "name": "@opencode-ai/stats-core",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@aws-sdk/client-athena": "3.933.0", "@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0", "@planetscale/database": "1.19.0",
@ -953,7 +955,7 @@
}, },
"packages/stats/server": { "packages/stats/server": {
"name": "@opencode-ai/stats-server", "name": "@opencode-ai/stats-server",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@aws-sdk/client-firehose": "3.933.0", "@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
@ -994,7 +996,7 @@
}, },
"packages/tui": { "packages/tui": {
"name": "@opencode-ai/tui", "name": "@opencode-ai/tui",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@ -1015,6 +1017,7 @@
"remeda": "catalog:", "remeda": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"strip-ansi": "7.1.2", "strip-ansi": "7.1.2",
"uqr": "0.1.3",
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/bun": "catalog:", "@tsconfig/bun": "catalog:",
@ -1024,7 +1027,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:", "@pierre/diffs": "catalog:",
@ -1075,7 +1078,7 @@
}, },
"packages/web": { "packages/web": {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"version": "1.17.14", "version": "1.17.15",
"dependencies": { "dependencies": {
"@astrojs/cloudflare": "12.6.3", "@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1", "@astrojs/markdown-remark": "6.3.1",
@ -5949,6 +5952,8 @@
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"uqr": ["uqr@0.1.3", "", {}, "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="], "urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="],

View file

@ -24,6 +24,7 @@
"prepare": "husky", "prepare": "husky",
"random": "echo 'Random script'", "random": "echo 'Random script'",
"sso": "aws sso login --sso-session=opencode --no-browser", "sso": "aws sso login --sso-session=opencode --no-browser",
"translate:app": "bun run script/translate-app.ts",
"test": "echo 'do not run tests from root' && exit 1" "test": "echo 'do not run tests from root' && exit 1"
}, },
"workspaces": { "workspaces": {

View file

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

View file

@ -9,7 +9,7 @@ import { Font } from "@opencode-ai/ui/font"
import { Splash } from "@opencode-ai/ui/logo" import { Splash } from "@opencode-ai/ui/logo"
import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { ThemeProvider } from "@opencode-ai/ui/theme/context"
import { MetaProvider } from "@solidjs/meta" import { MetaProvider } from "@solidjs/meta"
import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" import { type BaseRouterProps, Navigate, Route, Router, useNavigate, useParams, useSearchParams } from "@solidjs/router"
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
import { Effect } from "effect" import { Effect } from "effect"
import { import {
@ -32,7 +32,7 @@ import { CommandProvider, useCommand, type CommandOption } from "@/context/comma
import { CommentsProvider } from "@/context/comments" import { CommentsProvider } from "@/context/comments"
import { FileProvider } from "@/context/file" import { FileProvider } from "@/context/file"
import { ServerSDKProvider } from "@/context/server-sdk" import { ServerSDKProvider } from "@/context/server-sdk"
import { ServerSyncProvider } from "@/context/server-sync" import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
import { GlobalProvider, useGlobal } from "@/context/global" import { GlobalProvider, useGlobal } from "@/context/global"
import { HighlightsProvider } from "@/context/highlights" import { HighlightsProvider } from "@/context/highlights"
import { LanguageProvider, type Locale, useLanguage } from "@/context/language" import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
@ -52,9 +52,10 @@ import LegacyLayout from "@/pages/layout"
import NewLayout from "@/pages/layout-new" import NewLayout from "@/pages/layout-new"
import { ErrorPage } from "./pages/error" import { ErrorPage } from "./pages/error"
import { useCheckServerHealth } from "./utils/server-health" import { useCheckServerHealth } from "./utils/server-health"
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route" import { legacySessionHref, legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
import { createSessionLineage } from "@/pages/session/session-lineage"
import { SessionPage, TargetSessionRouteContent } from "@/pages/session" import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session"
import { NewHome, LegacyHome } from "@/pages/home" import { NewHome, LegacyHome } from "@/pages/home"
const NewSession = lazy(() => import("@/pages/new-session")) const NewSession = lazy(() => import("@/pages/new-session"))
@ -88,10 +89,14 @@ const SessionRoute = () => {
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt) tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
}) })
return <SessionPage /> return (
<SessionRouteErrorBoundary sessionID={params.id}>
<SessionPage />
</SessionRouteErrorBoundary>
)
} }
const TargetSessionRoute = () => { function TargetServerRoute(props: ParentProps) {
const params = useParams<{ serverKey: string; id: string }>() const params = useParams<{ serverKey: string; id: string }>()
const global = useGlobal() const global = useGlobal()
const conn = createMemo(() => { const conn = createMemo(() => {
@ -105,14 +110,47 @@ const TargetSessionRoute = () => {
// re-resolves reactively instead); both rely on this key for server changes. // re-resolves reactively instead); both rely on this key for server changes.
<Show when={requireServerKey(params.serverKey)} keyed> <Show when={requireServerKey(params.serverKey)} keyed>
<ServerSDKProvider server={conn}> <ServerSDKProvider server={conn}>
<ServerSyncProvider server={conn}> <ServerSyncProvider server={conn}>{props.children}</ServerSyncProvider>
<TargetSessionRouteContent />
</ServerSyncProvider>
</ServerSDKProvider> </ServerSDKProvider>
</Show> </Show>
) )
} }
const TargetSessionRoute = () => (
<TargetServerRoute>
<TargetSessionRouteContent />
</TargetServerRoute>
)
function LegacyTargetSessionRoute() {
const params = useParams<{ serverKey: string; id: string }>()
return (
<TargetServerRoute>
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)}>
<LegacyTargetSessionRedirect />
</SessionRouteErrorBoundary>
</TargetServerRoute>
)
}
function LegacyTargetSessionRedirect() {
const params = useParams<{ id: string }>()
const navigate = useNavigate()
const sync = useServerSync()
const current = createSessionLineage(
() => params.id,
() => sync().session.lineage,
)
createEffect(() => {
const directory = current()?.session.directory
if (!directory) return
navigate(legacySessionHref(directory, params.id), { replace: true })
})
return null
}
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected // Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/ // server via ServerKey, then provide the server-scoped shell (Permission/Layout/
// Notification/Models + the visual Layout) for that server. // Notification/Models + the visual Layout) for that server.
@ -186,7 +224,7 @@ declare global {
deepLinks?: string[] deepLinks?: string[]
} }
api?: { api?: {
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void> setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise<void>
exportDebugLogs?: () => Promise<string> exportDebugLogs?: () => Promise<string>
} }
} }
@ -320,8 +358,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
<MetaProvider> <MetaProvider>
<Font /> <Font />
<ThemeProvider <ThemeProvider
onThemeApplied={(_, mode) => { onThemeApplied={(_, mode, scheme) => {
void window.api?.setTitlebar?.({ mode }) void window.api?.setTitlebar?.({ mode, scheme })
}} }}
> >
<LanguageProvider locale={props.locale}> <LanguageProvider locale={props.locale}>
@ -542,7 +580,14 @@ function Routes(props: { serverScoped?: JSX.Element }) {
<LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout> <LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
)} )}
> >
<Show when={!settings.general.newLayoutDesigns()}>{<Route path="/" component={LegacyHome} />}</Show> <Show when={!settings.general.newLayoutDesigns()}>
{
<>
<Route path="/" component={LegacyHome} />
<Route path="/server/:serverKey/session/:id" component={LegacyTargetSessionRoute} />
</>
}
</Show>
<Route path="/:dir" component={DirectoryLayout}> <Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} /> <Route path="/" component={() => <Navigate href="session" />} />
<Route path="/session/:id?" component={SessionRoute} /> <Route path="/session/:id?" component={SessionRoute} />
@ -550,15 +595,15 @@ function Routes(props: { serverScoped?: JSX.Element }) {
</Route> </Route>
<Show when={settings.general.newLayoutDesigns()}> <Show when={settings.general.newLayoutDesigns()}>
<Route path="/" component={NewHome} /> <Route path="/" component={NewHome} />
<Route path="/:dir/session/:id" component={LegacyTargetSessionRoute} /> <Route path="/:dir/session/:id" component={NewLayoutLegacySessionRedirect} />
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
</Show> </Show>
<Route path="/new-session" component={DraftRoute} /> <Route path="/new-session" component={DraftRoute} />
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
</> </>
) )
} }
function LegacyTargetSessionRoute() { function NewLayoutLegacySessionRedirect() {
const server = useServer() const server = useServer()
const tabs = useTabs() const tabs = useTabs()
const params = useParams<{ id: string }>() const params = useParams<{ id: string }>()

View file

@ -0,0 +1,325 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { getFilename } from "@opencode-ai/core/util/path"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useNavigate } from "@solidjs/router"
import { createMemo, onCleanup } from "solid-js"
import { useCommand, type CommandOption } from "@/context/command"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"
import { createSessionTabs } from "@/pages/session/helpers"
import { useSessionLayout } from "@/pages/session/session-layout"
import { decode64 } from "@/utils/base64"
export type CommandPaletteEntry = {
id: string
type: "command" | "file" | "session"
title: string
description?: string
keybind?: string
category: string
option?: CommandOption
path?: string
directory?: string
sessionID?: string
archived?: number
updated?: number
}
const ENTRY_LIMIT = 5
const COMMON_COMMAND_IDS = [
"session.new",
"workspace.new",
"session.previous",
"session.next",
"terminal.toggle",
"review.toggle",
] as const
export function uniqueCommandPaletteEntries(items: CommandPaletteEntry[]) {
const seen = new Set<string>()
return items.filter((item) => {
if (seen.has(item.id)) return false
seen.add(item.id)
return true
})
}
export function createCommandPaletteFileEntry(path: string, category: string): CommandPaletteEntry {
return {
id: "file:" + path,
type: "file",
title: path,
category,
path,
}
}
export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => void) {
const file = useFile()
const layout = useLayout()
const { tabs, view } = useSessionLayout()
return (path: string) => {
const value = file.tab(path)
void tabs().open(value)
void file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
onOpenFile?.(path)
tabs().setActive(value)
}
}
export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
const command = useCommand()
const language = useLanguage()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const navigate = useNavigate()
const serverSDK = useServerSDK()()
const serverSync = useServerSync()
const { params, tabs } = useSessionLayout()
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
const state = { cleanup: undefined as (() => void) | void, committed: false }
const filesOnly = () => props.filesOnly?.() ?? false
const allowedCommands = createMemo(() => {
if (filesOnly()) return []
return command.options.filter(
(option) =>
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
)
})
const commandEntries = createMemo(() => {
const category = language.t("palette.group.commands")
return allowedCommands().map((option) => createCommandEntry(option, category))
})
const preferredCommandEntries = createMemo(() => {
const all = allowedCommands()
const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
const picked = all.filter((option) => order.has(option.id))
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
const category = language.t("palette.group.commands")
return sorted.map((option) => createCommandEntry(option, category))
})
const tabState = createSessionTabs({
tabs,
pathFromTab: file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
})
const recentFileEntries = createMemo(() => {
const all = tabState.openedTabs()
const active = tabState.activeFileTab()
const order = active ? [active, ...all.filter((item) => item !== active)] : all
const seen = new Set<string>()
const category = language.t("palette.group.files")
return order
.map((item) => file.pathFromTab(item))
.filter((path): path is string => {
if (!path || seen.has(path)) return false
seen.add(path)
return true
})
.slice(0, ENTRY_LIMIT)
.map((path) => createCommandPaletteFileEntry(path, category))
})
const rootFileEntries = createMemo(() => {
const category = language.t("palette.group.files")
return file.tree
.children("")
.filter((node) => node.type === "file")
.map((node) => node.path)
.sort((a, b) => a.localeCompare(b))
.slice(0, ENTRY_LIMIT)
.map((path) => createCommandPaletteFileEntry(path, category))
})
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
const project = createMemo(() => {
const directory = projectDirectory()
if (!directory) return undefined
return layout.projects.list().find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
})
const workspaces = createMemo(() => {
const directory = projectDirectory()
const current = project()
if (!current) return directory ? [directory] : []
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
if (directory && !dirs.includes(directory)) return [...dirs, directory]
return dirs
})
const homedir = createMemo(() => serverSync().data.path.home)
const sessions = createSessionEntries({
workspaces,
label: (directory) => {
const current = project()
const kind =
current && directory === current.worktree
? language.t("workspace.type.local")
: language.t("workspace.type.sandbox")
const [store] = serverSync().child(directory, { bootstrap: false })
const home = homedir()
const path = home ? directory.replace(home, "~") : directory
const name = store.vcs?.branch ?? getFilename(directory)
return `${kind} : ${name || path}`
},
load: (directory) => serverSDK.client.session.list({ directory, roots: true }),
untitled: () => language.t("command.session.new"),
category: () => language.t("command.category.session"),
})
const highlight = (item: CommandPaletteEntry | undefined) => {
state.cleanup?.()
state.cleanup = undefined
if (item?.type !== "command") return
state.cleanup = item.option?.onHighlight?.()
}
const select = (item: CommandPaletteEntry | undefined) => {
if (!item) return
state.committed = true
state.cleanup = undefined
dialog.close()
if (item.type === "command") {
item.option?.onSelect?.("palette")
return
}
if (item.type === "session") {
if (!item.directory || !item.sessionID) return
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
return
}
if (!item.path) return
openFile(item.path)
}
onCleanup(() => {
if (state.committed) return
state.cleanup?.()
})
return {
language,
file,
commandEntries,
preferredCommandEntries,
recentFileEntries,
rootFileEntries,
sessions,
highlight,
select,
close: () => dialog.close(),
}
}
function createCommandEntry(option: CommandOption, category: string): CommandPaletteEntry {
return {
id: "command:" + option.id,
type: "command",
title: option.title,
description: option.description,
keybind: option.keybind,
category,
option,
}
}
function createSessionEntries(props: {
workspaces: () => string[]
label: (directory: string) => string
load: (directory: string) => ReturnType<ServerSDK["client"]["session"]["list"]>
untitled: () => string
category: () => string
}) {
const state: {
token: number
inflight: Promise<CommandPaletteEntry[]> | undefined
cached: CommandPaletteEntry[] | undefined
} = { token: 0, inflight: undefined, cached: undefined }
return (text: string) => {
if (!text.trim()) {
state.token += 1
state.inflight = undefined
state.cached = undefined
return [] as CommandPaletteEntry[]
}
if (state.cached) return state.cached
if (state.inflight) return state.inflight
const current = state.token
const dirs = props.workspaces()
if (dirs.length === 0) return [] as CommandPaletteEntry[]
state.inflight = Promise.all(
dirs.map((directory) => {
const description = props.label(directory)
return props
.load(directory)
.then((result) =>
(result.data ?? [])
.filter((session) => !!session?.id)
.map((session) => ({
id: session.id,
title: session.title ?? props.untitled(),
description,
directory,
archived: session.time?.archived,
updated: session.time?.updated,
})),
)
.catch(() => [] as SessionEntryInput[])
}),
)
.then((results) => {
if (state.token !== current) return [] as CommandPaletteEntry[]
const seen = new Set<string>()
const next = results
.flat()
.filter((item) => {
const key = `${item.directory}:${item.id}`
if (seen.has(key)) return false
seen.add(key)
return true
})
.map((item) => createSessionEntry(item, props.category()))
state.cached = next
return next
})
.catch(() => [] as CommandPaletteEntry[])
.finally(() => {
state.inflight = undefined
})
return state.inflight
}
}
type SessionEntryInput = {
directory: string
id: string
title: string
description: string
archived?: number
updated?: number
}
function createSessionEntry(input: SessionEntryInput, category: string): CommandPaletteEntry {
return {
id: `session:${input.directory}:${input.id}`,
type: "session",
title: input.title,
description: input.description,
category,
directory: input.directory,
sessionID: input.id,
archived: input.archived,
updated: input.updated,
}
}

View file

@ -0,0 +1,220 @@
.command-palette-v2 {
overflow: hidden;
}
/* Anchor to the top edge of where a centered 480px-tall dialog would sit, so the
top stays put while the content-driven height grows and shrinks. */
[data-component="dialog-v2"]:has(.command-palette-v2) {
align-items: flex-start;
}
[data-component="dialog-v2"]:has(.command-palette-v2) [data-slot="dialog-container"] {
width: min(calc(100vw - 24px), 640px);
height: auto;
min-height: 280px;
max-height: min(calc(100vh - 96px), 480px);
margin-top: max(48px, calc((100vh - 480px) / 2));
border-radius: 12px;
background: var(--v2-background-bg-base);
box-shadow: var(--v2-elevation-floating);
}
.command-palette-v2-body {
display: flex;
min-height: 0;
flex: 1;
flex-direction: column;
gap: 0;
padding: 0;
}
.command-palette-v2-search {
flex-shrink: 0;
padding: 6px;
}
.command-palette-v2-search [data-component="text-input-v2"] {
width: 100%;
height: 36px;
border-radius: 6px;
outline: 0;
background: color-mix(in srgb, var(--v2-background-bg-layer-02) 60%, transparent);
box-shadow: none;
transition:
background-color 120ms ease-in-out,
box-shadow 120ms ease-in-out;
}
.command-palette-v2-search [data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]),
.command-palette-v2-search [data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) {
background: var(--v2-background-bg-layer-02);
box-shadow: none;
}
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-value"] {
border-radius: 6px;
background: transparent;
gap: 8px;
}
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] {
padding-left: 12px;
}
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-input"] {
border-radius: 6px;
background: transparent;
}
.command-palette-v2-scroll {
min-height: 0;
flex: 1;
}
.command-palette-v2-results {
display: flex;
flex-direction: column;
gap: 16px;
padding: 6px 6px 8px;
}
.command-palette-v2-group {
display: flex;
flex-direction: column;
gap: 1px;
}
.command-palette-v2-group-title {
margin: 6px 0;
padding: 0 12px;
color: var(--v2-text-text-muted);
font-size: 13px;
font-weight: 440;
line-height: 16px;
letter-spacing: -0.04px;
user-select: none;
}
.command-palette-v2-row {
display: flex;
width: 100%;
height: 36px;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 12px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--v2-text-text-base);
text-align: left;
cursor: default;
scroll-margin: 6px 0;
}
.command-palette-v2-row[data-active] {
background: var(--v2-overlay-simple-overlay-hover);
}
.command-palette-v2-row:focus-visible {
background: var(--v2-overlay-simple-overlay-hover);
outline: none;
}
.command-palette-v2-row-main {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 8px;
}
.command-palette-v2-row-icon {
flex-shrink: 0;
color: var(--v2-icon-icon-muted);
}
.command-palette-v2-row-text {
display: flex;
min-width: 0;
align-items: center;
gap: 6px;
}
.command-palette-v2-title {
min-width: 0;
overflow: hidden;
color: var(--v2-text-text-base);
font-size: 13px;
font-weight: 530;
line-height: 16px;
letter-spacing: -0.04px;
text-overflow: ellipsis;
white-space: nowrap;
}
.command-palette-v2-description,
.command-palette-v2-meta {
min-width: 0;
overflow: hidden;
color: var(--v2-text-text-muted);
font-size: 13px;
font-weight: 440;
line-height: 16px;
letter-spacing: -0.04px;
text-overflow: ellipsis;
white-space: nowrap;
}
.command-palette-v2-meta {
flex-shrink: 0;
}
.command-palette-v2-file-path {
display: flex;
min-width: 0;
align-items: baseline;
font-size: 13px;
line-height: 16px;
letter-spacing: -0.04px;
}
.command-palette-v2-file-dir {
min-width: 0;
overflow: hidden;
color: var(--v2-text-text-muted);
font-weight: 440;
text-overflow: ellipsis;
white-space: nowrap;
}
.command-palette-v2-file-name {
flex-shrink: 0;
color: var(--v2-text-text-base);
font-weight: 530;
white-space: nowrap;
}
.command-palette-v2-state {
display: grid;
min-height: 120px;
place-items: center;
color: var(--v2-text-text-muted);
font-size: 13px;
font-weight: 440;
line-height: 16px;
letter-spacing: -0.04px;
}
@media (max-width: 640px) {
.command-palette-v2-row-text {
flex-direction: column;
align-items: flex-start;
gap: 1px;
}
.command-palette-v2-description {
max-width: 100%;
}
}

View file

@ -0,0 +1,221 @@
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2"
import { Icon } from "@opencode-ai/ui/v2/icon"
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { createEffect, createMemo, createResource, createSignal, For, Match, Show, Switch } from "solid-js"
import { formatKeybindParts } from "@/context/command"
import { useLanguage } from "@/context/language"
import { getRelativeTime } from "@/utils/time"
import {
createCommandPaletteFileEntry,
createCommandPaletteModel,
uniqueCommandPaletteEntries,
type CommandPaletteEntry,
} from "./command-palette"
import "./dialog-command-palette-v2.css"
function groups(entries: CommandPaletteEntry[]) {
const map = new Map<string, CommandPaletteEntry[]>()
for (const entry of entries) map.set(entry.category, [...(map.get(entry.category) ?? []), entry])
return Array.from(map.entries()).map(([category, entries]) => ({ category, entries }))
}
function matchesEntry(entry: CommandPaletteEntry, query: string) {
const value = query.toLowerCase()
return [entry.title, entry.description, entry.category].some((text) => text?.toLowerCase().includes(value))
}
export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) {
const palette = createCommandPaletteModel(props)
const [query, setQuery] = createSignal("")
const [active, setActive] = createSignal(0)
const loadItems = async (text: string) => {
const q = text.trim()
if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
const [files, nextSessions] = await Promise.all([palette.file.searchFiles(q), Promise.resolve(palette.sessions(q))])
const category = palette.language.t("palette.group.files")
return [
...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
...nextSessions.filter((entry) => matchesEntry(entry, q)),
...files.map((path) => createCommandPaletteFileEntry(path, category)),
]
}
const [entries] = createResource(query, loadItems, { initialValue: [] as CommandPaletteEntry[] })
// Render stale results while a new query loads to avoid flashing "Loading" per keystroke.
const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? []))
const groupedEntries = createMemo(() => groups(visibleEntries()))
const activeEntry = createMemo(() => visibleEntries()[active()])
createEffect(() => {
query()
visibleEntries()
setActive(0)
})
createEffect(() => {
palette.highlight(activeEntry())
})
let resultsRef: HTMLDivElement | undefined
const move = (delta: -1 | 1) => {
const count = visibleEntries().length
if (count === 0) return
setActive((index) => (index + delta + count) % count)
requestAnimationFrame(() => {
resultsRef?.querySelector("[data-active]")?.scrollIntoView({ block: "nearest" })
})
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "ArrowDown") {
event.preventDefault()
move(1)
return
}
if (event.key === "ArrowUp") {
event.preventDefault()
move(-1)
return
}
if (event.key === "Enter") {
event.preventDefault()
palette.select(activeEntry())
return
}
if (event.key === "Escape") {
event.preventDefault()
palette.close()
}
}
return (
<Dialog class="command-palette-v2" size="large">
<DialogBody class="command-palette-v2-body">
<div class="command-palette-v2-search">
<TextInputV2
value={query()}
autofocus
autocomplete="off"
spellcheck={false}
appearance="large"
placeholder={palette.language.t("palette.search.placeholder")}
leadingIcon={<Icon name="magnifying-glass" />}
onInput={(event) => setQuery(event.currentTarget.value)}
onKeyDown={handleKeyDown}
/>
</div>
<ScrollView class="command-palette-v2-scroll" viewportRef={(el) => (resultsRef = el)}>
<div class="command-palette-v2-results" role="listbox">
<Show
when={visibleEntries().length > 0}
fallback={
<div class="command-palette-v2-state">
{entries.loading ? palette.language.t("common.loading") : palette.language.t("palette.empty")}
</div>
}
>
<For each={groupedEntries()}>
{(group) => (
<div class="command-palette-v2-group">
<Show when={group.category}>
<div class="command-palette-v2-group-title">{group.category}</div>
</Show>
<For each={group.entries}>
{(item) => (
<PaletteRow
item={item}
active={activeEntry()?.id === item.id}
language={palette.language}
onActive={() => setActive(visibleEntries().findIndex((entry) => entry.id === item.id))}
onSelect={() => palette.select(item)}
/>
)}
</For>
</div>
)}
</For>
</Show>
</div>
</ScrollView>
</DialogBody>
</Dialog>
)
}
function PaletteRow(props: {
item: CommandPaletteEntry
active: boolean
language: ReturnType<typeof useLanguage>
onActive: () => void
onSelect: () => void
}) {
return (
<button
type="button"
class="command-palette-v2-row"
role="option"
aria-selected={props.active}
data-active={props.active ? "" : undefined}
onMouseMove={(event) => {
// Ignore hover from a static cursor when keyboard scrolling moves rows underneath it.
if (event.movementX === 0 && event.movementY === 0) return
props.onActive()
}}
onMouseDown={(event) => event.preventDefault()}
onClick={props.onSelect}
>
<Switch
fallback={
<div class="command-palette-v2-row-main">
<FileIcon node={{ path: props.item.path ?? "", type: "file" }} class="command-palette-v2-row-icon size-4" />
<div class="command-palette-v2-file-path">
<span class="command-palette-v2-file-dir">{getDirectory(props.item.path ?? "")}</span>
<span class="command-palette-v2-file-name">{getFilename(props.item.path ?? "")}</span>
</div>
</div>
}
>
<Match when={props.item.type === "command"}>
<div class="command-palette-v2-row-main">
<div class="command-palette-v2-row-text">
<span class="command-palette-v2-title">{props.item.title}</span>
<Show when={props.item.description}>
<span class="command-palette-v2-description">{props.item.description}</span>
</Show>
</div>
</div>
<Show when={props.item.keybind}>
<KeybindV2 keys={formatKeybindParts(props.item.keybind ?? "", props.language.t)} variant="neutral" />
</Show>
</Match>
<Match when={props.item.type === "session"}>
<div class="command-palette-v2-row-main">
<Icon name="status" class="command-palette-v2-row-icon" />
<div class="command-palette-v2-row-text">
<span class="command-palette-v2-title" classList={{ "opacity-70": !!props.item.archived }}>
{props.item.title}
</span>
<Show when={props.item.description}>
<span class="command-palette-v2-description" classList={{ "opacity-70": !!props.item.archived }}>
{props.item.description}
</span>
</Show>
</div>
</div>
<Show when={props.item.updated}>
<span class="command-palette-v2-meta">
{getRelativeTime(new Date(props.item.updated!).toISOString(), props.language.t)}
</span>
</Show>
</Match>
</Switch>
</button>
)
}

View file

@ -1,329 +1,84 @@
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog" import { Dialog } from "@opencode-ai/ui/dialog"
import { FileIcon } from "@opencode-ai/ui/file-icon" import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon" import { Icon } from "@opencode-ai/ui/icon"
import { Keybind } from "@opencode-ai/ui/keybind" import { Keybind } from "@opencode-ai/ui/keybind"
import { List } from "@opencode-ai/ui/list" import { List } from "@opencode-ai/ui/list"
import { base64Encode } from "@opencode-ai/core/util/encode"
import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
import { useNavigate } from "@solidjs/router" import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js"
import { createMemo, createSignal, lazy, Match, onCleanup, Show, Switch } from "solid-js" import { formatKeybind } from "@/context/command"
import { formatKeybind, useCommand, type CommandOption } from "@/context/command" import { useServerSDK } from "@/context/server-sdk"
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"
import { useLayout } from "@/context/layout"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
import { decode64 } from "@/utils/base64" import { decode64 } from "@/utils/base64"
import { getRelativeTime } from "@/utils/time" import { getRelativeTime } from "@/utils/time"
import {
createCommandPaletteFileEntry,
createCommandPaletteFileOpener,
createCommandPaletteModel,
uniqueCommandPaletteEntries,
type CommandPaletteEntry,
} from "./command-palette"
const DialogSelectFileV2 = lazy(() => const DialogSelectFileV2 = lazy(() =>
import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })), import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
) )
const DialogCommandPaletteV2 = lazy(() =>
type EntryType = "command" | "file" | "session" import("./dialog-command-palette-v2").then((module) => ({ default: module.DialogCommandPaletteV2 })),
)
type Entry = {
id: string
type: EntryType
title: string
description?: string
keybind?: string
category: string
option?: CommandOption
path?: string
directory?: string
sessionID?: string
archived?: number
updated?: number
}
type DialogSelectFileMode = "all" | "files" type DialogSelectFileMode = "all" | "files"
const ENTRY_LIMIT = 5
const COMMON_COMMAND_IDS = [
"session.new",
"workspace.new",
"session.previous",
"session.next",
"terminal.toggle",
"review.toggle",
] as const
const uniqueEntries = (items: Entry[]) => {
const seen = new Set<string>()
const out: Entry[] = []
for (const item of items) {
if (seen.has(item.id)) continue
seen.add(item.id)
out.push(item)
}
return out
}
const createCommandEntry = (option: CommandOption, category: string): Entry => ({
id: "command:" + option.id,
type: "command",
title: option.title,
description: option.description,
keybind: option.keybind,
category,
option,
})
const createFileEntry = (path: string, category: string): Entry => ({
id: "file:" + path,
type: "file",
title: path,
category,
path,
})
const createSessionEntry = (
input: {
directory: string
id: string
title: string
description: string
archived?: number
updated?: number
},
category: string,
): Entry => ({
id: `session:${input.directory}:${input.id}`,
type: "session",
title: input.title,
description: input.description,
category,
directory: input.directory,
sessionID: input.id,
archived: input.archived,
updated: input.updated,
})
function createCommandEntries(props: {
filesOnly: () => boolean
command: ReturnType<typeof useCommand>
language: ReturnType<typeof useLanguage>
}) {
const allowed = createMemo(() => {
if (props.filesOnly()) return []
return props.command.options.filter(
(option) =>
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
)
})
const list = createMemo(() => {
const category = props.language.t("palette.group.commands")
return allowed().map((option) => createCommandEntry(option, category))
})
const picks = createMemo(() => {
const all = allowed()
const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
const picked = all.filter((option) => order.has(option.id))
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
const category = props.language.t("palette.group.commands")
return sorted.map((option) => createCommandEntry(option, category))
})
return { allowed, list, picks }
}
function createFileEntries(props: {
file: ReturnType<typeof useFile>
tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
language: ReturnType<typeof useLanguage>
}) {
const tabState = createSessionTabs({
tabs: props.tabs,
pathFromTab: props.file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? props.file.tab(tab) : tab),
})
const recent = createMemo(() => {
const all = tabState.openedTabs()
const active = tabState.activeFileTab()
const order = active ? [active, ...all.filter((item) => item !== active)] : all
const seen = new Set<string>()
const category = props.language.t("palette.group.files")
const items: Entry[] = []
for (const item of order) {
const path = props.file.pathFromTab(item)
if (!path) continue
if (seen.has(path)) continue
seen.add(path)
items.push(createFileEntry(path, category))
}
return items.slice(0, ENTRY_LIMIT)
})
const root = createMemo(() => {
const category = props.language.t("palette.group.files")
const nodes = props.file.tree.children("")
const paths = nodes
.filter((node) => node.type === "file")
.map((node) => node.path)
.sort((a, b) => a.localeCompare(b))
return paths.slice(0, ENTRY_LIMIT).map((path) => createFileEntry(path, category))
})
return { recent, root }
}
function createSessionEntries(props: {
workspaces: () => string[]
label: (directory: string) => string
serverSDK: ServerSDK
language: ReturnType<typeof useLanguage>
}) {
const state: {
token: number
inflight: Promise<Entry[]> | undefined
cached: Entry[] | undefined
} = {
token: 0,
inflight: undefined,
cached: undefined,
}
const sessions = (text: string) => {
const query = text.trim()
if (!query) {
state.token += 1
state.inflight = undefined
state.cached = undefined
return [] as Entry[]
}
if (state.cached) return state.cached
if (state.inflight) return state.inflight
const current = state.token
const dirs = props.workspaces()
if (dirs.length === 0) return [] as Entry[]
state.inflight = Promise.all(
dirs.map((directory) => {
const description = props.label(directory)
return props.serverSDK.client.session
.list({ directory, roots: true })
.then((x) =>
(x.data ?? [])
.filter((s) => !!s?.id)
.map((s) => ({
id: s.id,
title: s.title ?? props.language.t("command.session.new"),
description,
directory,
archived: s.time?.archived,
updated: s.time?.updated,
})),
)
.catch(
() =>
[] as {
id: string
title: string
description: string
directory: string
archived?: number
updated?: number
}[],
)
}),
)
.then((results) => {
if (state.token !== current) return [] as Entry[]
const seen = new Set<string>()
const category = props.language.t("command.category.session")
const next = results
.flat()
.filter((item) => {
const key = `${item.directory}:${item.id}`
if (seen.has(key)) return false
seen.add(key)
return true
})
.map((item) => createSessionEntry(item, category))
state.cached = next
return next
})
.catch(() => [] as Entry[])
.finally(() => {
state.inflight = undefined
})
return state.inflight
}
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 platform = usePlatform() const platform = usePlatform()
const settings = useSettings() const settings = useSettings()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const navigate = useNavigate()
const serverSDK = useServerSDK()
const serverSync = useServerSync()
const { params, tabs, view } = useSessionLayout()
const filesOnly = () => props.mode === "files" const filesOnly = () => props.mode === "files"
const state = { cleanup: undefined as (() => void) | void, committed: false }
const [grouped, setGrouped] = createSignal(false)
const commandEntries = createCommandEntries({ filesOnly, command, language })
const fileEntries = createFileEntries({ file, tabs, language })
const projectDirectory = createMemo(() => decode64(params.dir) ?? "") if (!filesOnly() && settings.general.newLayoutDesigns()) {
const project = createMemo(() => { return <DialogCommandPaletteV2 onOpenFile={props.onOpenFile} />
const directory = projectDirectory()
if (!directory) return
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
})
const workspaces = createMemo(() => {
const directory = projectDirectory()
const current = project()
if (!current) return directory ? [directory] : []
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
if (directory && !dirs.includes(directory)) return [...dirs, directory]
return dirs
})
const homedir = createMemo(() => serverSync().data.path.home)
const label = (directory: string) => {
const current = project()
const kind =
current && directory === current.worktree
? language.t("workspace.type.local")
: language.t("workspace.type.sandbox")
const [store] = serverSync().child(directory, { bootstrap: false })
const home = homedir()
const path = home ? directory.replace(home, "~") : directory
const name = store.vcs?.branch ?? getFilename(directory)
return `${kind} : ${name || path}`
} }
const { sessions } = createSessionEntries({ workspaces, label, serverSDK: serverSDK(), language }) if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
return <DialogSelectFileDesktopV2 onOpenFile={props.onOpenFile} />
}
return <DialogSelectFileLegacy filesOnly={filesOnly} onOpenFile={props.onOpenFile} />
}
function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) {
const language = useLanguage()
const serverSDK = useServerSDK()
const { params } = useSessionLayout()
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
return (
<DialogSelectFileV2
server={serverSDK().server}
mode="file"
start={projectDirectory()}
title={language.t("session.header.searchFiles")}
onSelect={(result) => {
if (typeof result !== "string") return
openFile(result)
}}
/>
)
}
function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) {
const palette = createCommandPaletteModel(props)
const [grouped, setGrouped] = createSignal(false)
const items = async (text: string) => { const items = async (text: string) => {
const query = text.trim() const query = text.trim()
setGrouped(query.length > 0) setGrouped(query.length > 0)
if (!query && filesOnly()) { if (!query && props.filesOnly()) {
const loaded = file.tree.state("")?.loaded const loaded = palette.file.tree.state("")?.loaded
const pending = loaded ? Promise.resolve() : file.tree.list("") const pending = loaded ? Promise.resolve() : palette.file.tree.list("")
const next = uniqueEntries([...fileEntries.recent(), ...fileEntries.root()]) const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
if (loaded || next.length > 0) { if (loaded || next.length > 0) {
void pending void pending
@ -331,79 +86,24 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
} }
await pending await pending
return uniqueEntries([...fileEntries.recent(), ...fileEntries.root()]) return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
} }
if (!query) return [...commandEntries.picks(), ...fileEntries.recent()] if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
if (filesOnly()) { if (props.filesOnly()) {
const files = await file.searchFiles(query) const files = await palette.file.searchFiles(query)
const category = language.t("palette.group.files") const category = palette.language.t("palette.group.files")
return files.map((path) => createFileEntry(path, category)) return files.map((path) => createCommandPaletteFileEntry(path, category))
} }
const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))]) const [files, nextSessions] = await Promise.all([
const category = language.t("palette.group.files") palette.file.searchFiles(query),
const entries = files.map((path) => createFileEntry(path, category)) Promise.resolve(palette.sessions(query)),
return [...commandEntries.list(), ...nextSessions, ...entries] ])
} const category = palette.language.t("palette.group.files")
const entries = files.map((path) => createCommandPaletteFileEntry(path, category))
const handleMove = (item: Entry | undefined) => { return [...palette.commandEntries(), ...nextSessions, ...entries]
state.cleanup?.()
if (!item) return
if (item.type !== "command") return
state.cleanup = item.option?.onHighlight?.()
}
const open = (path: string) => {
const value = file.tab(path)
void tabs().open(value)
void file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
props.onOpenFile?.(path)
tabs().setActive(value)
}
const handleSelect = (item: Entry | undefined) => {
if (!item) return
state.committed = true
state.cleanup = undefined
dialog.close()
if (item.type === "command") {
item.option?.onSelect?.("palette")
return
}
if (item.type === "session") {
if (!item.directory || !item.sessionID) return
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
return
}
if (!item.path) return
open(item.path)
}
onCleanup(() => {
if (state.committed) return
state.cleanup?.()
})
if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
return (
<DialogSelectFileV2
server={serverSDK().server}
mode="file"
start={projectDirectory()}
title={language.t("session.header.searchFiles")}
onSelect={(result) => {
if (typeof result !== "string") return
open(result)
}}
/>
)
} }
return ( return (
@ -411,21 +111,21 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
<List <List
class="px-3" class="px-3"
search={{ search={{
placeholder: filesOnly() placeholder: props.filesOnly()
? language.t("session.header.searchFiles") ? palette.language.t("session.header.searchFiles")
: language.t("palette.search.placeholder"), : palette.language.t("palette.search.placeholder"),
autofocus: true, autofocus: true,
hideIcon: true, hideIcon: true,
}} }}
emptyMessage={language.t("palette.empty")} emptyMessage={palette.language.t("palette.empty")}
loadingMessage={language.t("common.loading")} loadingMessage={palette.language.t("common.loading")}
items={items} items={items}
key={(item) => item.id} key={(item) => item.id}
filterKeys={["title", "description", "category"]} filterKeys={["title", "description", "category"]}
skipFilter={(item) => item.type === "file"} skipFilter={(item) => item.type === "file"}
groupBy={grouped() ? (item) => item.category : () => ""} groupBy={grouped() ? (item) => item.category : () => ""}
onMove={handleMove} onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)}
onSelect={handleSelect} onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)}
> >
{(item) => ( {(item) => (
<Switch <Switch
@ -452,7 +152,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
</Show> </Show>
</div> </div>
<Show when={item.keybind}> <Show when={item.keybind}>
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", language.t)}</Keybind> <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", palette.language.t)}</Keybind>
</Show> </Show>
</div> </div>
</Match> </Match>
@ -479,7 +179,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
</div> </div>
<Show when={item.updated}> <Show when={item.updated}>
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2"> <span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
{getRelativeTime(new Date(item.updated!).toISOString(), language.t)} {getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)}
</span> </span>
</Show> </Show>
</div> </div>

View file

@ -24,6 +24,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Icon } from "@opencode-ai/ui/v2/icon" import { Icon } from "@opencode-ai/ui/v2/icon"
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2" import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { ModelTooltip } from "./model-tooltip" import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { decode64 } from "@/utils/base64" import { decode64 } from "@/utils/base64"
@ -92,6 +93,7 @@ const ModelList: Component<{
class="w-full" class="w-full"
placement="right-start" placement="right-start"
gutter={12} gutter={12}
openDelay={0}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />} value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
> >
{node} {node}
@ -446,26 +448,41 @@ export function ModelSelectorPopoverV2(props: {
<MenuV2.RadioGroup value={current()}> <MenuV2.RadioGroup value={current()}>
<For each={group.items}> <For each={group.items}>
{(item) => ( {(item) => (
<MenuV2.RadioItem <TooltipV2
value={modelKey(item)} class="w-full"
data-option-key={modelKey(item)} placement="right-start"
data-selected-model={current() === modelKey(item) ? true : undefined} gutter={6}
class="scroll-my-6" openDelay={0}
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }} value={
onMouseEnter={() => { <ModelTooltip
setStore("active", modelKey(item)) model={item}
setTimeout(() => searchRef?.focus()) latest={item.latest}
}} free={isFree(item.provider.id, item.cost)}
onSelect={() => selectModel(item)} v2
/>
}
> >
<span class="min-w-0 truncate">{item.name}</span> <MenuV2.RadioItem
<Show when={isFree(item.provider.id, item.cost)}> value={modelKey(item)}
<TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2> data-option-key={modelKey(item)}
</Show> data-selected-model={current() === modelKey(item) ? true : undefined}
<Show when={item.latest}> class="scroll-my-6 w-full"
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2> classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
</Show> onMouseEnter={() => {
</MenuV2.RadioItem> setStore("active", modelKey(item))
setTimeout(() => searchRef?.focus())
}}
onSelect={() => selectModel(item)}
>
<span class="min-w-0 truncate">{item.name}</span>
<Show when={isFree(item.provider.id, item.cost)}>
<TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
</Show>
<Show when={item.latest}>
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
</Show>
</MenuV2.RadioItem>
</TooltipV2>
)} )}
</For> </For>
</MenuV2.RadioGroup> </MenuV2.RadioGroup>

View file

@ -1,4 +1,4 @@
import { Show, type Component } from "solid-js" import { Show, type Component, type JSX } from "solid-js"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
type InputKey = "text" | "image" | "audio" | "video" | "pdf" type InputKey = "text" | "image" | "audio" | "video" | "pdf"
@ -23,7 +23,18 @@ type ModelInfo = {
} }
} }
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => { function ModelTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
return (
<div class="flex min-w-0 items-center gap-4">
<span class="shrink-0 text-v2-text-text-muted">{props.name}</span>
<span class="ml-auto min-w-0 truncate text-right text-v2-text-text-base">{props.value}</span>
</div>
)
}
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean; v2?: boolean }> = (
props,
) => {
const language = useLanguage() const language = useLanguage()
const sourceName = (model: ModelInfo) => { const sourceName = (model: ModelInfo) => {
const value = `${model.id} ${model.name}`.toLowerCase() const value = `${model.id} ${model.name}`.toLowerCase()
@ -51,6 +62,13 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
const suffix = tags.length ? ` (${tags.join(", ")})` : "" const suffix = tags.length ? ` (${tags.join(", ")})` : ""
return `${sourceName(props.model)} ${props.model.name}${suffix}` return `${sourceName(props.model)} ${props.model.name}${suffix}`
} }
const name = () => {
const tags: Array<string> = []
if (props.latest) tags.push(language.t("model.tag.latest"))
if (props.free) tags.push(language.t("model.tag.free"))
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
return `${props.model.name}${suffix}`
}
const inputs = () => { const inputs = () => {
if (props.model.capabilities) { if (props.model.capabilities) {
const input = props.model.capabilities.input const input = props.model.capabilities.input
@ -73,6 +91,21 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
: language.t("model.tooltip.reasoning.none") : language.t("model.tooltip.reasoning.none")
} }
const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() }) const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
const contextLimit = () => props.model.limit.context.toLocaleString(language.intl())
if (props.v2) {
return (
<div class="flex w-[180px] flex-col gap-2">
<ModelTooltipRow name={language.t("model.tooltip.model")} value={name()} />
<ModelTooltipRow name={language.t("model.tooltip.provider")} value={props.model.provider.name} />
<Show when={inputs()}>
{(value) => <ModelTooltipRow name={language.t("model.tooltip.inputs")} value={value()} />}
</Show>
<ModelTooltipRow name={language.t("model.tooltip.reasoning")} value={reasoning()} />
<ModelTooltipRow name={language.t("model.tooltip.context.label")} value={contextLimit()} />
</div>
)
}
return ( return (
<div class="flex flex-col gap-1 py-1"> <div class="flex flex-col gap-1 py-1">

View file

@ -440,11 +440,6 @@ export const SettingsGeneral: Component = () => {
value={(o) => o.value} value={(o) => o.value}
label={(o) => o.label} label={(o) => o.label}
onSelect={(option) => option && theme.setColorScheme(option.value)} onSelect={(option) => option && theme.setColorScheme(option.value)}
onHighlight={(option) => {
if (!option) return
theme.previewColorScheme(option.value)
return () => theme.cancelPreview()
}}
variant="secondary" variant="secondary"
size="small" size="small"
triggerVariant="settings" triggerVariant="settings"
@ -471,11 +466,6 @@ export const SettingsGeneral: Component = () => {
if (!option) return if (!option) return
theme.setTheme(option.id) theme.setTheme(option.id)
}} }}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
variant="secondary" variant="secondary"
size="small" size="small"
triggerVariant="settings" triggerVariant="settings"

View file

@ -422,11 +422,6 @@ export const SettingsGeneralV2: Component<{
value={(o) => o.value} value={(o) => o.value}
label={(o) => o.label} label={(o) => o.label}
onSelect={(option) => option && theme.setColorScheme(option.value)} onSelect={(option) => option && theme.setColorScheme(option.value)}
onHighlight={(option) => {
if (!option) return
theme.previewColorScheme(option.value)
return () => theme.cancelPreview()
}}
/> />
</SettingsRowV2> </SettingsRowV2>
@ -454,11 +449,6 @@ export const SettingsGeneralV2: Component<{
if (!option) return if (!option) return
theme.setTheme(option.id) theme.setTheme(option.id)
}} }}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
/> />
</SettingsRowV2> </SettingsRowV2>

View file

@ -246,18 +246,23 @@ export function TabNavItem(props: {
}} }}
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]" class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
> >
<Show when={props.session()}> <span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
{(session) => ( <Show
<span data-slot="project-avatar-slot"> when={props.session()}
fallback={
<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
}
>
{(session) => (
<SessionTabAvatar <SessionTabAvatar
project={project()} project={project()}
directory={session().directory} directory={session().directory}
sessionId={session().id} sessionId={session().id}
server={props.server} server={props.server}
/> />
</span> )}
)} </Show>
</Show> </span>
<span <span
ref={(el) => { ref={(el) => {
titleEl = el titleEl = el

View file

@ -40,6 +40,7 @@ function SessionTabSlot(props: {
let ref!: HTMLDivElement let ref!: HTMLDivElement
const sdk = createMemo(() => props.serverCtx()?.sdk ?? null) const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId)) const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
const persisted = createMemo(() => tabs.info[props.id])
const [loadedSession] = createResource( const [loadedSession] = createResource(
() => { () => {
const ctx = props.serverCtx() const ctx = props.serverCtx()
@ -71,8 +72,10 @@ function SessionTabSlot(props: {
createEffect(() => { createEffect(() => {
const value = session() const value = session()
if (!value) return
tabs.rememberSessionInfo(props.tab, value)
const current = sdk() const current = sdk()
if (!value || !current) return if (!current) return
createTabPromptState(tabs, props.tab, current.scope, { createTabPromptState(tabs, props.tab, current.scope, {
dir: base64Encode(value.directory), dir: base64Encode(value.directory),
id: value.id, id: value.id,
@ -86,7 +89,7 @@ function SessionTabSlot(props: {
data-tab-key={props.id} data-tab-key={props.id}
data-active={props.active()} data-active={props.active()}
class="relative flex w-56 min-w-7 max-w-56 flex-shrink" class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
classList={{ hidden: !session() && !missingSession() }} classList={{ hidden: !session() && !missingSession() && !persisted()?.title }}
> >
<TabNavItem <TabNavItem
ref={(el) => { ref={(el) => {
@ -95,7 +98,7 @@ function SessionTabSlot(props: {
href={tabHref(props.tab)} href={tabHref(props.tab)}
server={props.tab.server} server={props.tab.server}
session={session} session={session}
fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined} fallbackTitle={persisted()?.title ?? (missingSession() ? language.t("session.tab.unknown") : undefined)}
onTitleChange={(title) => { onTitleChange={(title) => {
const value = session() const value = session()
const ctx = props.serverCtx() const ctx = props.serverCtx()

View file

@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { upsertCommandRegistration } from "./command" import { resolveKeybindOption, upsertCommandRegistration } from "./command"
describe("upsertCommandRegistration", () => { describe("upsertCommandRegistration", () => {
test("replaces keyed registrations", () => { test("replaces keyed registrations", () => {
@ -23,3 +23,19 @@ describe("upsertCommandRegistration", () => {
expect(next[1]?.options).toBe(one) expect(next[1]?.options).toBe(one)
}) })
}) })
describe("resolveKeybindOption", () => {
test("prefers a matching contextual command over the global fallback", () => {
const fallback = { id: "tab.close", title: "Close tab" }
const contextual = { id: "terminal.close", title: "Close terminal", when: () => true }
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(contextual)
})
test("uses the global fallback outside the command context", () => {
const fallback = { id: "tab.close", title: "Close tab" }
const contextual = { id: "terminal.close", title: "Close terminal", when: () => false }
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(fallback)
})
})

View file

@ -82,10 +82,15 @@ export interface CommandOption {
suggested?: boolean suggested?: boolean
disabled?: boolean disabled?: boolean
hidden?: boolean hidden?: boolean
when?: (event: KeyboardEvent) => boolean
onSelect?: (source?: "palette" | "keybind" | "slash") => void onSelect?: (source?: "palette" | "keybind" | "slash") => void
onHighlight?: () => (() => void) | void onHighlight?: () => (() => void) | void
} }
export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
}
type CommandSource = "palette" | "keybind" | "slash" type CommandSource = "palette" | "keybind" | "slash"
export type CommandCatalogItem = { export type CommandCatalogItem = {
@ -334,7 +339,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
}) })
const keymap = createMemo(() => { const keymap = createMemo(() => {
const map = new Map<string, CommandOption>() const map = new Map<string, CommandOption[]>()
for (const option of options()) { for (const option of options()) {
if (option.id.startsWith(SUGGESTED_PREFIX)) continue if (option.id.startsWith(SUGGESTED_PREFIX)) continue
if (option.disabled) continue if (option.disabled) continue
@ -344,8 +349,12 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
for (const kb of keybinds) { for (const kb of keybinds) {
if (!kb.key) continue if (!kb.key) continue
const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt) const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
if (map.has(sig)) continue const existing = map.get(sig)
map.set(sig, option) if (existing) {
existing.push(option)
continue
}
map.set(sig, [option])
} }
} }
return map return map
@ -374,7 +383,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const sig = signatureFromEvent(event) const sig = signatureFromEvent(event)
const isPalette = palette().has(sig) const isPalette = palette().has(sig)
const option = keymap().get(sig) const option = resolveKeybindOption(keymap().get(sig), event)
const modified = event.ctrlKey || event.metaKey || event.altKey const modified = event.ctrlKey || event.metaKey || event.altKey
const isTab = event.key === "Tab" const isTab = event.key === "Tab"
@ -383,17 +392,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
if (isPalette) { if (isPalette) {
event.preventDefault() event.preventDefault()
event.stopPropagation()
showPalette() showPalette()
return return
} }
if (!option) return if (!option) return
event.preventDefault() event.preventDefault()
event.stopPropagation()
option.onSelect?.("keybind") option.onSelect?.("keybind")
} }
onMount(() => { onMount(() => {
makeEventListener(document, "keydown", handleKeyDown) makeEventListener(document, "keydown", handleKeyDown, { capture: true })
}) })
function register(cb: () => CommandOption[]): void function register(cb: () => CommandOption[]): void

View file

@ -28,6 +28,11 @@ export type DraftTab = {
export type Tab = SessionTab | DraftTab export type Tab = SessionTab | DraftTab
export type TabInfo = {
title?: string
directory?: string
}
type RecentTab = { type RecentTab = {
key?: string key?: string
} }
@ -64,6 +69,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
createStore<Tab[]>([]), createStore<Tab[]>([]),
) )
const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({})) const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
const [info, setInfo] = persisted(Persist.window("tabs.info"), createStore<Record<string, TabInfo>>({}))
const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([])) const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
const params = useParams() const params = useParams()
@ -102,6 +108,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform) for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
} }
const removeInfo = (key: string) => {
if (!info[key]) return
setInfo(
produce((draft) => {
delete draft[key]
}),
)
}
onCleanup(memory.dispose) onCleanup(memory.dispose)
createEffect(() => { createEffect(() => {
@ -110,11 +125,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const next = store.filter((tab) => servers.has(tab.server)) const next = store.filter((tab) => servers.has(tab.server))
if (next.length !== store.length) { if (next.length !== store.length) {
for (const tab of store) { for (const tab of store) {
if (!servers.has(tab.server)) memory.remove(tabKey(tab)) if (!servers.has(tab.server)) {
const key = tabKey(tab)
memory.remove(key)
removeInfo(key)
}
} }
setStore(() => next) setStore(() => next)
} }
if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined) if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
const keys = new Set(next.map(tabKey))
for (const key of Object.keys(info)) {
if (!keys.has(key)) removeInfo(key)
}
}) })
createEffect(() => { createEffect(() => {
@ -150,6 +173,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
if (nextTab) navigateTab(nextTab) if (nextTab) navigateTab(nextTab)
}).finally(() => closing.delete(key)) }).finally(() => closing.delete(key))
memory.remove(key) memory.remove(key)
removeInfo(key)
if (draftID) removeDraftPersisted(draftID) if (draftID) removeDraftPersisted(draftID)
} }
@ -264,6 +288,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const removed = store.filter((tab) => tab.server === key).map(tabKey) const removed = store.filter((tab) => tab.server === key).map(tabKey)
setStore((tabs) => tabs.filter((tab) => tab.server !== key)) setStore((tabs) => tabs.filter((tab) => tab.server !== key))
for (const key of removed) memory.remove(key) for (const key of removed) memory.remove(key)
for (const key of removed) removeInfo(key)
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined) if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
for (const draftID of drafts) removeDraftPersisted(draftID) for (const draftID of drafts) removeDraftPersisted(draftID)
if (server.key === key) navigate("/") if (server.key === key) navigate("/")
@ -318,6 +343,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined) if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
}) })
for (const key of removed) memory.remove(key) for (const key of removed) memory.remove(key)
for (const key of removed) removeInfo(key)
},
rememberSessionInfo(tab: SessionTab, session: Session) {
const key = tabKey(tab)
const next = { title: session.title, directory: session.directory }
const current = info[key]
if (current?.title === next.title && current.directory === next.directory) return
setInfo(key, next)
}, },
select: navigateTab, select: navigateTab,
remember(tab: Tab) { remember(tab: Tab) {
@ -342,6 +375,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
}, },
} }
return { ...actions, store, ready, recentReady } return { ...actions, store, info, ready, recentReady }
}, },
}) })

View file

@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "داكن", "theme.scheme.dark": "داكن",
"command.sidebar.toggle": "تبديل الشريط الجانبي", "command.sidebar.toggle": "تبديل الشريط الجانبي",
"command.project.open": "فتح مشروع", "command.project.open": "فتح مشروع",
"command.project.previous": "المشروع السابق",
"command.project.next": "المشروع التالي",
"command.project.index": "التبديل إلى المشروع {{index}}",
"command.provider.connect": "اتصال بموفر", "command.provider.connect": "اتصال بموفر",
"command.server.switch": "تبديل الخادم", "command.server.switch": "تبديل الخادم",
"command.settings.open": "فتح الإعدادات", "command.settings.open": "فتح الإعدادات",
@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "جلسة جديدة", "command.session.new": "جلسة جديدة",
"command.file.open": "فتح ملف", "command.file.open": "فتح ملف",
"command.tab.close": "إغلاق علامة التبويب", "command.tab.close": "إغلاق علامة التبويب",
"command.tab.reopenClosed": "إعادة فتح علامة التبويب المغلقة",
"command.context.addSelection": "إضافة التحديد إلى السياق", "command.context.addSelection": "إضافة التحديد إلى السياق",
"command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي", "command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي",
"command.input.focus": "التركيز على حقل الإدخال", "command.input.focus": "التركيز على حقل الإدخال",
@ -193,6 +197,11 @@ export const dict = {
"model.input.video": "فيديو", "model.input.video": "فيديو",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "يسمح: {{inputs}}", "model.tooltip.allows": "يسمح: {{inputs}}",
"model.tooltip.context.label": "السياق",
"model.tooltip.inputs": "المدخلات",
"model.tooltip.model": "النموذج",
"model.tooltip.provider": "الموفر",
"model.tooltip.reasoning": "الاستنتاج",
"model.tooltip.reasoning.allowed": "يسمح بالاستنتاج", "model.tooltip.reasoning.allowed": "يسمح بالاستنتاج",
"model.tooltip.reasoning.none": "بدون استنتاج", "model.tooltip.reasoning.none": "بدون استنتاج",
"model.tooltip.context": "حد السياق {{limit}}", "model.tooltip.context": "حد السياق {{limit}}",
@ -202,6 +211,7 @@ export const dict = {
"common.loading": "جارٍ التحميل", "common.loading": "جارٍ التحميل",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "إلغاء", "common.cancel": "إلغاء",
"common.clear": "مسح",
"common.connect": "اتصال", "common.connect": "اتصال",
"common.disconnect": "قطع الاتصال", "common.disconnect": "قطع الاتصال",
"common.continue": "إرسال", "common.continue": "إرسال",
@ -218,6 +228,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc للخروج", "prompt.mode.shell.exit": "esc للخروج",
"session.child.promptDisabled": "لا يمكن إرسال موجّهات إلى جلسات الوكلاء الفرعيين.",
"session.child.backToParent": "العودة إلى الجلسة الرئيسية.",
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية", "prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟", "prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
"prompt.example.3": "إصلاح الاختبارات المعطلة", "prompt.example.3": "إصلاح الاختبارات المعطلة",
@ -314,6 +326,74 @@ export const dict = {
"dialog.server.menu.delete": "حذف", "dialog.server.menu.delete": "حذف",
"dialog.server.current": "الخادم الحالي", "dialog.server.current": "الخادم الحالي",
"dialog.server.status.default": "افتراضي", "dialog.server.status.default": "افتراضي",
"wsl.server.add": "إضافة خادم WSL",
"wsl.server.addShort": "إضافة WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "خادم WSL",
"wsl.server.retryStart": "إعادة محاولة التشغيل",
"wsl.server.updating": "جارٍ التحديث...",
"wsl.onboarding.step.distro": "اختر توزيعة",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "جارٍ التحقق من WSL...",
"wsl.onboarding.restartRequired": "يحتاج Windows إلى إعادة التشغيل لإكمال تثبيت WSL.",
"wsl.onboarding.ready": "WSL جاهز.",
"wsl.onboarding.required": "يلزم WSL للمتابعة.",
"wsl.onboarding.checkingDistros": "جارٍ التحقق من التوزيعات...",
"wsl.onboarding.installingDistro": "جارٍ تثبيت {{distro}}...",
"wsl.onboarding.checkingDistro": "جارٍ التحقق من {{distro}}...",
"wsl.onboarding.listingDistros": "جارٍ سرد التوزيعات...",
"wsl.onboarding.distroReady": "{{distro}} جاهزة.",
"wsl.onboarding.distroNotInstalled": "لم يتم تثبيت {{distro}} بعد.",
"wsl.onboarding.openDistroOnce": "افتح {{distro}} مرة واحدة لإكمال الإعداد.",
"wsl.onboarding.finishingDistro": "جارٍ إكمال إعداد {{distro}}.",
"wsl.onboarding.pickDistro": "اختر توزيعة أو ثبّت واحدة أدناه.",
"wsl.onboarding.checkingOpencode": "جارٍ التحقق من OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "جارٍ التحقق من OpenCode في {{distro}}...",
"wsl.onboarding.updatingOpencode": "جارٍ تحديث OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "جارٍ تحديث OpenCode في {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "حدّث OpenCode في {{distro}}.",
"wsl.onboarding.updateOpencode": "تحديث OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode جاهز في {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode جاهز.",
"wsl.onboarding.installOpencodeIn": "ثبّت OpenCode في {{distro}}.",
"wsl.onboarding.installOpencode": "تثبيت OpenCode",
"wsl.onboarding.chooseDistroFirst": "اختر توزيعة أولاً.",
"wsl.onboarding.loadFailed": "فشل تحميل حالة WSL.",
"wsl.onboarding.loading": "جارٍ التحميل...",
"wsl.onboarding.installedDistros": "التوزيعات المثبتة",
"wsl.onboarding.checkAgain": "تحقق مجددًا",
"wsl.onboarding.distroStatus.ready": "جاهزة",
"wsl.onboarding.distroStatus.checking": "جارٍ التحقق...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode غير مثبت",
"wsl.onboarding.distroStatus.missingTools": "bash و curl مفقودان",
"wsl.onboarding.distroStatus.unsupported": "غير مدعومة · استخدم WSL 2",
"wsl.onboarding.needAnotherDistro": "هل تحتاج إلى توزيعة أخرى؟",
"wsl.onboarding.needAnotherDistroHint": "ثبّت توزيعة Linux من كتالوج WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL غير مثبت",
"wsl.onboarding.wslNotInstalled.description":
"يلزم WSL (نظام Windows الفرعي لـ Linux) قبل أن يتمكن OpenCode من إضافة خادم WSL",
"wsl.onboarding.wslUnavailable.title": "WSL غير متاح",
"wsl.onboarding.wslUnavailable.description": "تعذر على OpenCode التحقق من WSL على هذا الجهاز.",
"wsl.onboarding.installWsl": "تثبيت WSL",
"wsl.onboarding.windowsRestartRequired": "أعد تشغيل Windows لإكمال تثبيت WSL، ثم أعد فتح OpenCode.",
"wsl.onboarding.next": "التالي",
"wsl.onboarding.refresh": "تحديث",
"wsl.onboarding.allDistrosAdded": "تمت إضافة جميع التوزيعات المثبتة بالفعل.",
"wsl.onboarding.noDistros": "لم يتم اكتشاف أي توزيعات بعد.",
"wsl.onboarding.install": "تثبيت",
"wsl.onboarding.installing": "جارٍ التثبيت...",
"wsl.onboarding.installDistro": "تثبيت توزيعة",
"wsl.onboarding.searchDistros": "البحث عن توزيعات",
"wsl.onboarding.wsl2Required": "يلزم WSL 2.",
"wsl.onboarding.toolsRequired": "تحتاج هذه التوزيعة إلى bash و curl.",
"wsl.onboarding.openTerminal": "فتح المحطة الطرفية",
"wsl.onboarding.path": "المسار: {{path}}",
"wsl.onboarding.notFound": "غير موجود",
"wsl.onboarding.version": "الإصدار: {{version}}",
"wsl.onboarding.unknown": "غير معروف",
"wsl.onboarding.desktopVersion": "سطح المكتب {{version}}",
"wsl.onboarding.versionMismatch": "لا يتطابق الإصدار المثبت مع إصدار تطبيق سطح المكتب.",
"wsl.onboarding.adding": "جارٍ الإضافة...",
"dialog.project.edit.title": "تحرير المشروع", "dialog.project.edit.title": "تحرير المشروع",
"dialog.project.edit.name": "الاسم", "dialog.project.edit.name": "الاسم",
"dialog.project.edit.icon": "أيقونة", "dialog.project.edit.icon": "أيقونة",
@ -370,6 +450,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "لغة", "toast.language.title": "لغة",
@ -406,10 +487,12 @@ export const dict = {
"toast.update.action.notYet": "ليس الآن", "toast.update.action.notYet": "ليس الآن",
"error.page.title": "حدث خطأ ما", "error.page.title": "حدث خطأ ما",
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.", "error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
"error.page.description.localServerStartup": "حدث خطأ أثناء بدء تشغيل الخادم المحلي.",
"error.page.details.label": "تفاصيل الخطأ", "error.page.details.label": "تفاصيل الخطأ",
"error.page.action.restart": "إعادة تشغيل", "error.page.action.restart": "إعادة تشغيل",
"error.page.action.report": "الإبلاغ عن الخطأ", "error.page.action.report": "الإبلاغ عن الخطأ",
"error.page.action.reported": "تم الإبلاغ عن الخطأ", "error.page.action.reported": "تم الإبلاغ عن الخطأ",
"error.page.action.exportLogs": "تصدير السجلات",
"error.page.action.checking": "جارٍ التحقق...", "error.page.action.checking": "جارٍ التحقق...",
"error.page.action.checkUpdates": "التحقق من وجود تحديثات", "error.page.action.checkUpdates": "التحقق من وجود تحديثات",
"error.page.action.updateTo": "تحديث إلى {{version}}", "error.page.action.updateTo": "تحديث إلى {{version}}",
@ -450,16 +533,38 @@ export const dict = {
"home.recentProjects": "المشاريع الحديثة", "home.recentProjects": "المشاريع الحديثة",
"home.empty.title": "لا توجد مشاريع حديثة", "home.empty.title": "لا توجد مشاريع حديثة",
"home.empty.description": "ابدأ بفتح مشروع محلي", "home.empty.description": "ابدأ بفتح مشروع محلي",
"home.title": "الرئيسية",
"home.projects": "المشاريع",
"home.project.add": "إضافة مشروع",
"home.recentlyClosed": "المغلقة مؤخرًا",
"home.server.collapse": "طي مشاريع الخادم",
"home.server.expand": "توسيع مشاريع الخادم",
"home.sessions.search.placeholder": "البحث في الجلسات",
"home.sessions.search.placeholder.scoped": "البحث في جلسات {{scope}}",
"home.sessions.search.sessions": "الجلسات",
"home.sessions.search.noResults": "لم يتم العثور على جلسات لـ {{query}}",
"home.sessions.empty": "لا يوجد شيء هنا بعد",
"home.sessions.empty.description": "أنشئ جلسة للبدء",
"home.sessions.group.today": "اليوم",
"home.sessions.group.yesterday": "أمس",
"home.sessions.group.older": "الأقدم",
"session.tab.session": "جلسة", "session.tab.session": "جلسة",
"session.tab.review": "مراجعة", "session.tab.review": "مراجعة",
"session.tab.context": "سياق", "session.tab.context": "سياق",
"session.tab.unknown": "جلسة غير معروفة",
"session.panel.reviewAndFiles": "المراجعة والملفات", "session.panel.reviewAndFiles": "المراجعة والملفات",
"session.error.notFound": "لا يمكن العثور على هذه الجلسة",
"session.error.notFound.description": "يشير هذا التبويب إلى جلسة لم تعد موجودة على هذا الخادم.",
"session.error.notFound.closeTab": "إغلاق علامة التبويب",
"session.error.serverConnection": "تعذر الاتصال بهذا الخادم",
"session.review.filesChanged": "تم تغيير {{count}} ملفات", "session.review.filesChanged": "تم تغيير {{count}} ملفات",
"session.review.change.one": "تغيير", "session.review.change.one": "تغيير",
"session.review.change.other": "تغييرات", "session.review.change.other": "تغييرات",
"session.review.loadingChanges": "جارٍ تحميل التغييرات...", "session.review.loadingChanges": "جارٍ تحميل التغييرات...",
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد", "session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
"session.review.noChanges": "لا توجد تغييرات", "session.review.noChanges": "لا توجد تغييرات",
"session.review.noUncommittedChanges": "لا توجد تغييرات غير مُودَعة بعد",
"session.review.noBranchChanges": "لا توجد تغييرات في الفرع بعد",
"session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات", "session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات",
"session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة", "session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة",
"session.files.selectToOpen": "اختر ملفًا لفتحه", "session.files.selectToOpen": "اختر ملفًا لفتحه",
@ -475,6 +580,10 @@ export const dict = {
"session.todo.title": "المهام", "session.todo.title": "المهام",
"session.todo.collapse": "طي", "session.todo.collapse": "طي",
"session.todo.expand": "توسيع", "session.todo.expand": "توسيع",
"session.question.minimize": "تصغير السؤال",
"session.question.restore": "استعادة السؤال",
"session.question.pending.one": "{{count}} سؤال معلق",
"session.question.pending.other": "الأسئلة المعلقة: {{count}}",
"session.followupDock.summary.one": "{{count}} رسالة في الانتظار", "session.followupDock.summary.one": "{{count}} رسالة في الانتظار",
"session.followupDock.summary.other": "{{count}} رسائل في الانتظار", "session.followupDock.summary.other": "{{count}} رسائل في الانتظار",
"session.followupDock.sendNow": "إرسال الآن", "session.followupDock.sendNow": "إرسال الآن",
@ -487,9 +596,16 @@ export const dict = {
"session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها", "session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها",
"session.revertDock.restore": "استعادة الرسالة", "session.revertDock.restore": "استعادة الرسالة",
"session.new.title": "ابنِ أي شيء", "session.new.title": "ابنِ أي شيء",
"session.new.project.new": "مشروع جديد",
"session.new.project.search": "البحث عن المشاريع",
"session.new.project.add": "إضافة مشروع",
"session.new.worktree.main": "الفرع الرئيسي", "session.new.worktree.main": "الفرع الرئيسي",
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})", "session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
"session.new.worktree.create": "إنشاء شجرة عمل جديدة", "session.new.worktree.create": "إنشاء شجرة عمل جديدة",
"session.new.workspace.runIn": "تشغيل الجلسة في",
"session.new.workspace.triggerLocal": "محلي",
"session.new.workspace.local": "المستودع المحلي",
"session.new.workspace.existing": "مساحة عمل…",
"session.new.lastModified": "آخر تعديل", "session.new.lastModified": "آخر تعديل",
"session.header.search.placeholder": "بحث {{project}}", "session.header.search.placeholder": "بحث {{project}}",
"session.header.searchFiles": "بحث عن الملفات", "session.header.searchFiles": "بحث عن الملفات",
@ -550,6 +666,8 @@ export const dict = {
"sidebar.project.recentSessions": "الجلسات الحديثة", "sidebar.project.recentSessions": "الجلسات الحديثة",
"sidebar.project.viewAllSessions": "عرض جميع الجلسات", "sidebar.project.viewAllSessions": "عرض جميع الجلسات",
"sidebar.project.clearNotifications": "مسح الإشعارات", "sidebar.project.clearNotifications": "مسح الإشعارات",
"sidebar.empty.title": "لا توجد مشاريع مفتوحة",
"sidebar.empty.description": "افتح مشروعًا للبدء",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "سطح المكتب", "settings.section.desktop": "سطح المكتب",
"settings.section.server": "الخادم", "settings.section.server": "الخادم",
@ -559,6 +677,7 @@ export const dict = {
"settings.desktop.wsl.title": "تكامل WSL", "settings.desktop.wsl.title": "تكامل WSL",
"settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.", "settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.",
"settings.general.section.appearance": "المظهر", "settings.general.section.appearance": "المظهر",
"settings.general.section.advanced": "متقدم",
"settings.general.section.notifications": "إشعارات النظام", "settings.general.section.notifications": "إشعارات النظام",
"settings.general.section.updates": "التحديثات", "settings.general.section.updates": "التحديثات",
"settings.general.section.sounds": "المؤثرات الصوتية", "settings.general.section.sounds": "المؤثرات الصوتية",
@ -566,6 +685,11 @@ export const dict = {
"settings.general.section.display": "شاشة العرض", "settings.general.section.display": "شاشة العرض",
"settings.general.row.language.title": "اللغة", "settings.general.row.language.title": "اللغة",
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
"settings.general.row.shell.title": "Shell المحطة الطرفية",
"settings.general.row.shell.description":
"اختر shell المستخدم في المحطة الطرفية. تُستخدم واجهات shell المتوافقة أيضًا في استدعاءات أدوات الوكيل.",
"settings.general.row.shell.autoDefault": "تلقائي (افتراضي)",
"settings.general.row.shell.terminalOnly": "المحطة الطرفية فقط",
"settings.general.row.appearance.title": "المظهر", "settings.general.row.appearance.title": "المظهر",
"settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك", "settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك",
"settings.general.row.colorScheme.title": "مخطط الألوان", "settings.general.row.colorScheme.title": "مخطط الألوان",
@ -574,14 +698,29 @@ export const dict = {
"settings.general.row.theme.description": "تخصيص سمة OpenCode.", "settings.general.row.theme.description": "تخصيص سمة OpenCode.",
"settings.general.row.font.title": "خط الكود", "settings.general.row.font.title": "خط الكود",
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية", "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "خط الطرفية",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "خصّص الخط المستخدم في الطرفية",
"settings.general.row.uiFont.title": "خط الواجهة", "settings.general.row.uiFont.title": "خط الواجهة",
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها", "settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
"settings.general.row.followup.title": "سلوك المتابعة", "settings.general.row.followup.title": "سلوك المتابعة",
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار", "settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
"settings.general.row.followup.option.queue": "قائمة انتظار", "settings.general.row.followup.option.queue": "قائمة انتظار",
"settings.general.row.followup.option.steer": "توجيه", "settings.general.row.followup.option.steer": "توجيه",
"settings.general.row.showFileTree.title": "شجرة الملفات",
"settings.general.row.showFileTree.description": "إظهار لوحة شجرة الملفات في الجلسات",
"settings.general.row.showNavigation.title": "عناصر التحكم في التنقل",
"settings.general.row.showNavigation.description": "إظهار زري الرجوع والتقدم في شريط عنوان سطح المكتب",
"settings.general.row.showSearch.title": "لوحة الأوامر",
"settings.general.row.showSearch.description": "إظهار زر البحث ولوحة الأوامر في شريط العنوان",
"settings.general.row.showTerminal.title": "المحطة الطرفية",
"settings.general.row.showTerminal.description": "إظهار زر المحطة الطرفية في شريط عنوان سطح المكتب",
"settings.general.row.showStatus.title": "حالة الخادم",
"settings.general.row.showStatus.description": "إظهار زر حالة الخادم في شريط العنوان",
"settings.general.row.mobileTitlebarBottom.title": "التنقل السفلي",
"settings.general.row.mobileTitlebarBottom.description":
"وضع شريط العنوان وعلامات تبويب الجلسات أسفل الشاشة على الأجهزة المحمولة",
"settings.general.row.showCustomAgents.title": "الوكلاء المخصصون",
"settings.general.row.showCustomAgents.description": "إظهار محدد الوكلاء في محرر الرسائل",
"settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج", "settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج",
"settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني", "settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني",
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell", "settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
@ -590,6 +729,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit", "settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني", "إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
"settings.general.row.newLayoutDesigns.title": "التخطيط والتصاميم الجديدة",
"settings.general.row.newLayoutDesigns.description":
"تمكين التخطيط والصفحة الرئيسية ومحرر الرسائل وواجهة الجلسة المعاد تصميمها",
"settings.general.row.pinchZoom.title": "التكبير بإيماءة القرص",
"settings.general.row.pinchZoom.description": "السماح بإيماءة القرص على لوحة اللمس وإيماءة Ctrl-scroll للتكبير",
"settings.general.row.wayland.title": "استخدام Wayland الأصلي", "settings.general.row.wayland.title": "استخدام Wayland الأصلي",
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.", "settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -602,6 +746,8 @@ export const dict = {
"settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة", "settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة",
"settings.updates.action.checkNow": "تحقق الآن", "settings.updates.action.checkNow": "تحقق الآن",
"settings.updates.action.checking": "جارٍ التحقق...", "settings.updates.action.checking": "جارٍ التحقق...",
"settings.updates.action.downloading": "جارٍ التنزيل...",
"settings.updates.action.installing": "جارٍ التثبيت...",
"settings.updates.toast.latest.title": "أنت على آخر إصدار", "settings.updates.toast.latest.title": "أنت على آخر إصدار",
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.", "settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
"sound.option.none": "بلا", "sound.option.none": "بلا",

View file

@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "Escuro", "theme.scheme.dark": "Escuro",
"command.sidebar.toggle": "Alternar barra lateral", "command.sidebar.toggle": "Alternar barra lateral",
"command.project.open": "Abrir projeto", "command.project.open": "Abrir projeto",
"command.project.previous": "Projeto anterior",
"command.project.next": "Próximo projeto",
"command.project.index": "Alternar para o projeto {{index}}",
"command.provider.connect": "Conectar provedor", "command.provider.connect": "Conectar provedor",
"command.server.switch": "Trocar servidor", "command.server.switch": "Trocar servidor",
"command.settings.open": "Abrir configurações", "command.settings.open": "Abrir configurações",
@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "Nova sessão", "command.session.new": "Nova sessão",
"command.file.open": "Abrir arquivo", "command.file.open": "Abrir arquivo",
"command.tab.close": "Fechar aba", "command.tab.close": "Fechar aba",
"command.tab.reopenClosed": "Reabrir aba fechada",
"command.context.addSelection": "Adicionar seleção ao contexto", "command.context.addSelection": "Adicionar seleção ao contexto",
"command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual", "command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual",
"command.input.focus": "Focar entrada", "command.input.focus": "Focar entrada",
@ -193,6 +197,11 @@ export const dict = {
"model.input.video": "vídeo", "model.input.video": "vídeo",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Permite: {{inputs}}", "model.tooltip.allows": "Permite: {{inputs}}",
"model.tooltip.context.label": "Contexto",
"model.tooltip.inputs": "Entradas",
"model.tooltip.model": "Modelo",
"model.tooltip.provider": "Provedor",
"model.tooltip.reasoning": "Raciocínio",
"model.tooltip.reasoning.allowed": "Permite raciocínio", "model.tooltip.reasoning.allowed": "Permite raciocínio",
"model.tooltip.reasoning.none": "Sem raciocínio", "model.tooltip.reasoning.none": "Sem raciocínio",
"model.tooltip.context": "Limite de contexto {{limit}}", "model.tooltip.context": "Limite de contexto {{limit}}",
@ -202,6 +211,7 @@ export const dict = {
"common.loading": "Carregando", "common.loading": "Carregando",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Cancelar", "common.cancel": "Cancelar",
"common.clear": "Limpar",
"common.connect": "Conectar", "common.connect": "Conectar",
"common.disconnect": "Desconectar", "common.disconnect": "Desconectar",
"common.continue": "Enviar", "common.continue": "Enviar",
@ -218,6 +228,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc para sair", "prompt.mode.shell.exit": "esc para sair",
"session.child.promptDisabled": "Não é possível enviar prompts em sessões de subagentes.",
"session.child.backToParent": "Voltar à sessão principal.",
"prompt.example.1": "Corrigir um TODO no código", "prompt.example.1": "Corrigir um TODO no código",
"prompt.example.2": "Qual é a stack tecnológica deste projeto?", "prompt.example.2": "Qual é a stack tecnológica deste projeto?",
"prompt.example.3": "Corrigir testes quebrados", "prompt.example.3": "Corrigir testes quebrados",
@ -314,6 +326,75 @@ export const dict = {
"dialog.server.menu.delete": "Excluir", "dialog.server.menu.delete": "Excluir",
"dialog.server.current": "Servidor atual", "dialog.server.current": "Servidor atual",
"dialog.server.status.default": "Padrão", "dialog.server.status.default": "Padrão",
"wsl.server.add": "Adicionar servidor WSL",
"wsl.server.addShort": "Adicionar WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Servidor WSL",
"wsl.server.retryStart": "Tentar iniciar novamente",
"wsl.server.updating": "Atualizando...",
"wsl.onboarding.step.distro": "Escolher distribuição",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Verificando o WSL...",
"wsl.onboarding.restartRequired": "O Windows precisa ser reiniciado para concluir a instalação do WSL.",
"wsl.onboarding.ready": "O WSL está pronto.",
"wsl.onboarding.required": "O WSL é necessário para continuar.",
"wsl.onboarding.checkingDistros": "Verificando distribuições...",
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
"wsl.onboarding.checkingDistro": "Verificando {{distro}}...",
"wsl.onboarding.listingDistros": "Listando distribuições...",
"wsl.onboarding.distroReady": "{{distro}} está pronta.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ainda não está instalada.",
"wsl.onboarding.openDistroOnce": "Abra {{distro}} uma vez para concluir a configuração.",
"wsl.onboarding.finishingDistro": "Concluindo a configuração de {{distro}}.",
"wsl.onboarding.pickDistro": "Escolha uma distribuição ou instale uma abaixo.",
"wsl.onboarding.checkingOpencode": "Verificando o OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Verificando o OpenCode em {{distro}}...",
"wsl.onboarding.updatingOpencode": "Atualizando o OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Atualizando o OpenCode em {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Atualize o OpenCode em {{distro}}.",
"wsl.onboarding.updateOpencode": "Atualizar o OpenCode",
"wsl.onboarding.opencodeReadyIn": "O OpenCode está pronto em {{distro}}.",
"wsl.onboarding.opencodeReady": "O OpenCode está pronto.",
"wsl.onboarding.installOpencodeIn": "Instale o OpenCode em {{distro}}.",
"wsl.onboarding.installOpencode": "Instalar o OpenCode",
"wsl.onboarding.chooseDistroFirst": "Escolha primeiro uma distribuição.",
"wsl.onboarding.loadFailed": "Falha ao carregar o estado do WSL.",
"wsl.onboarding.loading": "Carregando...",
"wsl.onboarding.installedDistros": "Distribuições instaladas",
"wsl.onboarding.checkAgain": "Verificar novamente",
"wsl.onboarding.distroStatus.ready": "Pronta",
"wsl.onboarding.distroStatus.checking": "Verificando...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode não instalado",
"wsl.onboarding.distroStatus.missingTools": "bash e curl ausentes",
"wsl.onboarding.distroStatus.unsupported": "Sem suporte · Use o WSL 2",
"wsl.onboarding.needAnotherDistro": "Precisa de outra distribuição?",
"wsl.onboarding.needAnotherDistroHint": "Instale uma distribuição Linux pelo catálogo do WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL não instalado",
"wsl.onboarding.wslNotInstalled.description":
"O WSL (Windows Subsystem for Linux) é necessário para que o OpenCode possa adicionar um servidor WSL",
"wsl.onboarding.wslUnavailable.title": "WSL indisponível",
"wsl.onboarding.wslUnavailable.description": "O OpenCode não conseguiu verificar o WSL neste computador.",
"wsl.onboarding.installWsl": "Instalar o WSL",
"wsl.onboarding.windowsRestartRequired":
"Reinicie o Windows para concluir a instalação do WSL e abra o OpenCode novamente.",
"wsl.onboarding.next": "Próximo",
"wsl.onboarding.refresh": "Atualizar",
"wsl.onboarding.allDistrosAdded": "Todas as distribuições instaladas já foram adicionadas.",
"wsl.onboarding.noDistros": "Nenhuma distribuição detectada ainda.",
"wsl.onboarding.install": "Instalar",
"wsl.onboarding.installing": "Instalando...",
"wsl.onboarding.installDistro": "Instalar distribuição",
"wsl.onboarding.searchDistros": "Buscar distribuições",
"wsl.onboarding.wsl2Required": "O WSL 2 é necessário.",
"wsl.onboarding.toolsRequired": "Esta distribuição precisa do bash e do curl.",
"wsl.onboarding.openTerminal": "Abrir terminal",
"wsl.onboarding.path": "Caminho: {{path}}",
"wsl.onboarding.notFound": "não encontrado",
"wsl.onboarding.version": "Versão: {{version}}",
"wsl.onboarding.unknown": "desconhecido",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "A versão instalada não corresponde à versão do aplicativo para desktop.",
"wsl.onboarding.adding": "Adicionando...",
"dialog.project.edit.title": "Editar projeto", "dialog.project.edit.title": "Editar projeto",
"dialog.project.edit.name": "Nome", "dialog.project.edit.name": "Nome",
"dialog.project.edit.icon": "Ícone", "dialog.project.edit.icon": "Ícone",
@ -371,6 +452,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Idioma", "toast.language.title": "Idioma",
@ -407,10 +489,12 @@ export const dict = {
"toast.update.action.notYet": "Agora não", "toast.update.action.notYet": "Agora não",
"error.page.title": "Algo deu errado", "error.page.title": "Algo deu errado",
"error.page.description": "Ocorreu um erro ao carregar a aplicação.", "error.page.description": "Ocorreu um erro ao carregar a aplicação.",
"error.page.description.localServerStartup": "Ocorreu um erro ao iniciar o servidor local.",
"error.page.details.label": "Detalhes do Erro", "error.page.details.label": "Detalhes do Erro",
"error.page.action.restart": "Reiniciar", "error.page.action.restart": "Reiniciar",
"error.page.action.report": "Reportar erro", "error.page.action.report": "Reportar erro",
"error.page.action.reported": "Erro reportado", "error.page.action.reported": "Erro reportado",
"error.page.action.exportLogs": "Exportar logs",
"error.page.action.checking": "Verificando...", "error.page.action.checking": "Verificando...",
"error.page.action.checkUpdates": "Verificar atualizações", "error.page.action.checkUpdates": "Verificar atualizações",
"error.page.action.updateTo": "Atualizar para {{version}}", "error.page.action.updateTo": "Atualizar para {{version}}",
@ -453,10 +537,30 @@ export const dict = {
"home.recentProjects": "Projetos recentes", "home.recentProjects": "Projetos recentes",
"home.empty.title": "Nenhum projeto recente", "home.empty.title": "Nenhum projeto recente",
"home.empty.description": "Comece abrindo um projeto local", "home.empty.description": "Comece abrindo um projeto local",
"home.title": "Início",
"home.projects": "Projetos",
"home.project.add": "Adicionar projeto",
"home.recentlyClosed": "Projetos fechados recentemente",
"home.server.collapse": "Recolher projetos do servidor",
"home.server.expand": "Expandir projetos do servidor",
"home.sessions.search.placeholder": "Buscar sessões",
"home.sessions.search.placeholder.scoped": "Buscar sessões em {{scope}}",
"home.sessions.search.sessions": "Sessões",
"home.sessions.search.noResults": "Nenhuma sessão encontrada para {{query}}",
"home.sessions.empty": "Nada por aqui ainda",
"home.sessions.empty.description": "Crie uma sessão para começar",
"home.sessions.group.today": "Hoje",
"home.sessions.group.yesterday": "Ontem",
"home.sessions.group.older": "Mais antigas",
"session.tab.session": "Sessão", "session.tab.session": "Sessão",
"session.tab.review": "Revisão", "session.tab.review": "Revisão",
"session.tab.context": "Contexto", "session.tab.context": "Contexto",
"session.tab.unknown": "Sessão desconhecida",
"session.panel.reviewAndFiles": "Revisão e arquivos", "session.panel.reviewAndFiles": "Revisão e arquivos",
"session.error.notFound": "Esta sessão não foi encontrada",
"session.error.notFound.description": "Esta aba aponta para uma sessão que não existe mais neste servidor.",
"session.error.notFound.closeTab": "Fechar aba",
"session.error.serverConnection": "Não é possível conectar a este servidor",
"session.review.filesChanged": "{{count}} Arquivos Alterados", "session.review.filesChanged": "{{count}} Arquivos Alterados",
"session.review.change.one": "Alteração", "session.review.change.one": "Alteração",
"session.review.change.other": "Alterações", "session.review.change.other": "Alterações",
@ -466,6 +570,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis", "O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis",
"session.review.noChanges": "Sem alterações", "session.review.noChanges": "Sem alterações",
"session.review.noUncommittedChanges": "Ainda não há alterações sem commit",
"session.review.noBranchChanges": "Ainda não há alterações na branch",
"session.files.selectToOpen": "Selecione um arquivo para abrir", "session.files.selectToOpen": "Selecione um arquivo para abrir",
"session.files.all": "Todos os arquivos", "session.files.all": "Todos os arquivos",
"session.files.empty": "Nenhum arquivo", "session.files.empty": "Nenhum arquivo",
@ -479,6 +585,10 @@ export const dict = {
"session.todo.title": "Tarefas", "session.todo.title": "Tarefas",
"session.todo.collapse": "Recolher", "session.todo.collapse": "Recolher",
"session.todo.expand": "Expandir", "session.todo.expand": "Expandir",
"session.question.minimize": "Minimizar pergunta",
"session.question.restore": "Restaurar pergunta",
"session.question.pending.one": "{{count}} pergunta pendente",
"session.question.pending.other": "{{count}} perguntas pendentes",
"session.followupDock.summary.one": "{{count}} mensagem na fila", "session.followupDock.summary.one": "{{count}} mensagem na fila",
"session.followupDock.summary.other": "{{count}} mensagens na fila", "session.followupDock.summary.other": "{{count}} mensagens na fila",
"session.followupDock.sendNow": "Enviar agora", "session.followupDock.sendNow": "Enviar agora",
@ -491,9 +601,16 @@ export const dict = {
"session.revertDock.expand": "Expandir mensagens revertidas", "session.revertDock.expand": "Expandir mensagens revertidas",
"session.revertDock.restore": "Restaurar mensagem", "session.revertDock.restore": "Restaurar mensagem",
"session.new.title": "Crie qualquer coisa", "session.new.title": "Crie qualquer coisa",
"session.new.project.new": "Novo projeto",
"session.new.project.search": "Buscar projetos",
"session.new.project.add": "Adicionar projeto",
"session.new.worktree.main": "Branch principal", "session.new.worktree.main": "Branch principal",
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})", "session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
"session.new.worktree.create": "Criar novo worktree", "session.new.worktree.create": "Criar novo worktree",
"session.new.workspace.runIn": "Executar sessão em",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Repositório local",
"session.new.workspace.existing": "Espaço de trabalho…",
"session.new.lastModified": "Última modificação", "session.new.lastModified": "Última modificação",
"session.header.search.placeholder": "Buscar {{project}}", "session.header.search.placeholder": "Buscar {{project}}",
"session.header.searchFiles": "Buscar arquivos", "session.header.searchFiles": "Buscar arquivos",
@ -557,15 +674,18 @@ export const dict = {
"sidebar.project.recentSessions": "Sessões recentes", "sidebar.project.recentSessions": "Sessões recentes",
"sidebar.project.viewAllSessions": "Ver todas as sessões", "sidebar.project.viewAllSessions": "Ver todas as sessões",
"sidebar.project.clearNotifications": "Limpar notificações", "sidebar.project.clearNotifications": "Limpar notificações",
"sidebar.empty.title": "Nenhum projeto aberto",
"sidebar.empty.description": "Abra um projeto para começar",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Servidor", "settings.section.server": "Servidor",
"settings.tab.general": "Geral", "settings.tab.general": "Geral",
"settings.tab.shortcuts": "Atalhos", "settings.tab.shortcuts": "Atalhos",
"settings.desktop.section.wsl": "WSL", "settings.desktop.section.wsl": "WSL",
"settings.desktop.wsl.title": "WSL integration", "settings.desktop.wsl.title": "Integração com WSL",
"settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.", "settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.",
"settings.general.section.appearance": "Aparência", "settings.general.section.appearance": "Aparência",
"settings.general.section.advanced": "Avançado",
"settings.general.section.notifications": "Notificações do sistema", "settings.general.section.notifications": "Notificações do sistema",
"settings.general.section.updates": "Atualizações", "settings.general.section.updates": "Atualizações",
"settings.general.section.sounds": "Efeitos sonoros", "settings.general.section.sounds": "Efeitos sonoros",
@ -573,6 +693,11 @@ export const dict = {
"settings.general.section.display": "Tela", "settings.general.section.display": "Tela",
"settings.general.row.language.title": "Idioma", "settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
"settings.general.row.shell.title": "Shell do terminal",
"settings.general.row.shell.description":
"Escolha o shell usado no terminal. Os shells compatíveis também são usados nas chamadas de ferramentas do agente.",
"settings.general.row.shell.autoDefault": "Automático (Padrão)",
"settings.general.row.shell.terminalOnly": "somente no terminal",
"settings.general.row.appearance.title": "Aparência", "settings.general.row.appearance.title": "Aparência",
"settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo", "settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo",
"settings.general.row.colorScheme.title": "Esquema de cores", "settings.general.row.colorScheme.title": "Esquema de cores",
@ -581,8 +706,8 @@ export const dict = {
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.", "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
"settings.general.row.font.title": "Fonte de código", "settings.general.row.font.title": "Fonte de código",
"settings.general.row.font.description": "Personalize a fonte usada em blocos de código", "settings.general.row.font.description": "Personalize a fonte usada em blocos de código",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Fonte do terminal",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Personalize a fonte usada no terminal",
"settings.general.row.uiFont.title": "Fonte da interface", "settings.general.row.uiFont.title": "Fonte da interface",
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface", "settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
"settings.general.row.followup.title": "Comportamento de acompanhamento", "settings.general.row.followup.title": "Comportamento de acompanhamento",
@ -590,6 +715,22 @@ export const dict = {
"Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila", "Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
"settings.general.row.followup.option.queue": "Fila", "settings.general.row.followup.option.queue": "Fila",
"settings.general.row.followup.option.steer": "Orientar", "settings.general.row.followup.option.steer": "Orientar",
"settings.general.row.showFileTree.title": "Árvore de arquivos",
"settings.general.row.showFileTree.description": "Mostrar o painel da árvore de arquivos nas sessões",
"settings.general.row.showNavigation.title": "Controles de navegação",
"settings.general.row.showNavigation.description":
"Mostrar os botões de voltar e avançar na barra de título do desktop",
"settings.general.row.showSearch.title": "Paleta de comandos",
"settings.general.row.showSearch.description": "Mostrar o botão de busca e paleta de comandos na barra de título",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Mostrar o botão do terminal na barra de título do desktop",
"settings.general.row.showStatus.title": "Status do servidor",
"settings.general.row.showStatus.description": "Mostrar o botão de status do servidor na barra de título",
"settings.general.row.mobileTitlebarBottom.title": "Navegação inferior",
"settings.general.row.mobileTitlebarBottom.description":
"Posicionar a barra de título e as abas da sessão na parte inferior da tela em dispositivos móveis",
"settings.general.row.showCustomAgents.title": "Agentes personalizados",
"settings.general.row.showCustomAgents.description": "Mostrar o seletor de agentes na área de composição",
"settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio", "settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio",
"settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo", "settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo",
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell", "settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
@ -598,6 +739,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição", "settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo", "Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
"settings.general.row.newLayoutDesigns.title": "Novo layout e design",
"settings.general.row.newLayoutDesigns.description":
"Ativar o layout, a página inicial, a área de composição e a interface de sessão reformulados",
"settings.general.row.pinchZoom.title": "Zoom com gesto de pinça",
"settings.general.row.pinchZoom.description":
"Permitir gestos de pinça no trackpad e de Ctrl+rolagem para aplicar zoom",
"settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.", "settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -610,6 +757,8 @@ export const dict = {
"settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver", "settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver",
"settings.updates.action.checkNow": "Verificar agora", "settings.updates.action.checkNow": "Verificar agora",
"settings.updates.action.checking": "Verificando...", "settings.updates.action.checking": "Verificando...",
"settings.updates.action.downloading": "Baixando...",
"settings.updates.action.installing": "Instalando...",
"settings.updates.toast.latest.title": "Você está atualizado", "settings.updates.toast.latest.title": "Você está atualizado",
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.", "settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
"sound.option.none": "Nenhum", "sound.option.none": "Nenhum",

View file

@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Prikaži/sakrij bočnu traku", "command.sidebar.toggle": "Prikaži/sakrij bočnu traku",
"command.project.open": "Otvori projekat", "command.project.open": "Otvori projekat",
"command.project.previous": "Prethodni projekat",
"command.project.next": "Sljedeći projekat",
"command.project.index": "Prebaci na projekat {{index}}",
"command.provider.connect": "Poveži provajdera", "command.provider.connect": "Poveži provajdera",
"command.server.switch": "Promijeni server", "command.server.switch": "Promijeni server",
"command.settings.open": "Otvori postavke", "command.settings.open": "Otvori postavke",
@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Nova sesija", "command.session.new": "Nova sesija",
"command.file.open": "Otvori datoteku", "command.file.open": "Otvori datoteku",
"command.tab.close": "Zatvori karticu", "command.tab.close": "Zatvori karticu",
"command.tab.reopenClosed": "Ponovo otvori zatvorenu karticu",
"command.context.addSelection": "Dodaj odabir u kontekst", "command.context.addSelection": "Dodaj odabir u kontekst",
"command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke", "command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke",
"command.input.focus": "Fokusiraj polje za unos", "command.input.focus": "Fokusiraj polje za unos",
@ -209,6 +213,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Podržava: {{inputs}}", "model.tooltip.allows": "Podržava: {{inputs}}",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Ulazi",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Provajder",
"model.tooltip.reasoning": "Rasuđivanje",
"model.tooltip.reasoning.allowed": "Podržava rasuđivanje", "model.tooltip.reasoning.allowed": "Podržava rasuđivanje",
"model.tooltip.reasoning.none": "Bez rasuđivanja", "model.tooltip.reasoning.none": "Bez rasuđivanja",
"model.tooltip.context": "Limit konteksta {{limit}}", "model.tooltip.context": "Limit konteksta {{limit}}",
@ -219,6 +228,7 @@ export const dict = {
"common.loading": "Učitavanje", "common.loading": "Učitavanje",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Otkaži", "common.cancel": "Otkaži",
"common.clear": "Očisti",
"common.connect": "Poveži", "common.connect": "Poveži",
"common.disconnect": "Prekini vezu", "common.disconnect": "Prekini vezu",
"common.continue": "Pošalji", "common.continue": "Pošalji",
@ -236,6 +246,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc za izlaz", "prompt.mode.shell.exit": "esc za izlaz",
"session.child.promptDisabled": "Sesijama podagenta nije moguće slati upite.",
"session.child.backToParent": "Nazad na glavnu sesiju.",
"prompt.example.1": "Popravi TODO u bazi koda", "prompt.example.1": "Popravi TODO u bazi koda",
"prompt.example.2": "Koji je tehnološki stack ovog projekta?", "prompt.example.2": "Koji je tehnološki stack ovog projekta?",
@ -342,6 +354,75 @@ export const dict = {
"dialog.server.menu.delete": "Izbriši", "dialog.server.menu.delete": "Izbriši",
"dialog.server.current": "Trenutni server", "dialog.server.current": "Trenutni server",
"dialog.server.status.default": "Podrazumijevano", "dialog.server.status.default": "Podrazumijevano",
"wsl.server.add": "Dodaj WSL server",
"wsl.server.addShort": "Dodaj WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Ponovi pokretanje",
"wsl.server.updating": "Ažuriranje...",
"wsl.onboarding.step.distro": "Odaberi distribuciju",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Provjera WSL-a...",
"wsl.onboarding.restartRequired": "Potrebno je ponovo pokrenuti Windows da bi se završila instalacija WSL-a.",
"wsl.onboarding.ready": "WSL je spreman.",
"wsl.onboarding.required": "WSL je potreban za nastavak.",
"wsl.onboarding.checkingDistros": "Provjera distribucija...",
"wsl.onboarding.installingDistro": "Instaliranje {{distro}}...",
"wsl.onboarding.checkingDistro": "Provjera {{distro}}...",
"wsl.onboarding.listingDistros": "Učitavanje liste distribucija...",
"wsl.onboarding.distroReady": "Distribucija {{distro}} je spremna.",
"wsl.onboarding.distroNotInstalled": "Distribucija {{distro}} još nije instalirana.",
"wsl.onboarding.openDistroOnce": "Otvori {{distro}} jednom da završiš postavljanje.",
"wsl.onboarding.finishingDistro": "Završavanje postavljanja za {{distro}}.",
"wsl.onboarding.pickDistro": "Odaberi distribuciju ili instaliraj jednu ispod.",
"wsl.onboarding.checkingOpencode": "Provjera OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Provjera OpenCode u distribuciji {{distro}}...",
"wsl.onboarding.updatingOpencode": "Ažuriranje OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Ažuriranje OpenCode u distribuciji {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Ažuriraj OpenCode u distribuciji {{distro}}.",
"wsl.onboarding.updateOpencode": "Ažuriraj OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode je spreman u distribuciji {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode je spreman.",
"wsl.onboarding.installOpencodeIn": "Instaliraj OpenCode u distribuciji {{distro}}.",
"wsl.onboarding.installOpencode": "Instaliraj OpenCode",
"wsl.onboarding.chooseDistroFirst": "Prvo odaberi distribuciju.",
"wsl.onboarding.loadFailed": "Nije uspjelo učitavanje WSL stanja.",
"wsl.onboarding.loading": "Učitavanje...",
"wsl.onboarding.installedDistros": "Instalirane distribucije",
"wsl.onboarding.checkAgain": "Provjeri ponovo",
"wsl.onboarding.distroStatus.ready": "Spremno",
"wsl.onboarding.distroStatus.checking": "Provjera...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nije instaliran",
"wsl.onboarding.distroStatus.missingTools": "Nedostaju bash i curl",
"wsl.onboarding.distroStatus.unsupported": "Nije podržano · Koristi WSL 2",
"wsl.onboarding.needAnotherDistro": "Treba ti druga distribucija?",
"wsl.onboarding.needAnotherDistroHint": "Instaliraj Linux distribuciju iz WSL kataloga",
"wsl.onboarding.wslNotInstalled.title": "WSL nije instaliran",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) je potreban da bi OpenCode mogao dodati WSL server",
"wsl.onboarding.wslUnavailable.title": "WSL nije dostupan",
"wsl.onboarding.wslUnavailable.description": "OpenCode nije mogao provjeriti WSL na ovom uređaju.",
"wsl.onboarding.installWsl": "Instaliraj WSL",
"wsl.onboarding.windowsRestartRequired":
"Ponovo pokreni Windows da završiš instalaciju WSL-a, zatim ponovo otvori OpenCode.",
"wsl.onboarding.next": "Dalje",
"wsl.onboarding.refresh": "Osvježi",
"wsl.onboarding.allDistrosAdded": "Sve instalirane distribucije su već dodane.",
"wsl.onboarding.noDistros": "Još nema otkrivenih distribucija.",
"wsl.onboarding.install": "Instaliraj",
"wsl.onboarding.installing": "Instaliranje...",
"wsl.onboarding.installDistro": "Instaliraj distribuciju",
"wsl.onboarding.searchDistros": "Pretraži distribucije",
"wsl.onboarding.wsl2Required": "Potreban je WSL 2.",
"wsl.onboarding.toolsRequired": "Ovoj distribuciji su potrebni bash i curl.",
"wsl.onboarding.openTerminal": "Otvori terminal",
"wsl.onboarding.path": "Putanja: {{path}}",
"wsl.onboarding.notFound": "nije pronađeno",
"wsl.onboarding.version": "Verzija: {{version}}",
"wsl.onboarding.unknown": "nepoznato",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Instalirana verzija ne odgovara verziji desktop aplikacije.",
"wsl.onboarding.adding": "Dodavanje...",
"dialog.project.edit.title": "Uredi projekat", "dialog.project.edit.title": "Uredi projekat",
"dialog.project.edit.name": "Naziv", "dialog.project.edit.name": "Naziv",
@ -405,6 +486,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -453,10 +535,12 @@ export const dict = {
"error.page.title": "Nešto je pošlo po zlu", "error.page.title": "Nešto je pošlo po zlu",
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.", "error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
"error.page.description.localServerStartup": "Došlo je do greške pri pokretanju lokalnog servera.",
"error.page.details.label": "Detalji greške", "error.page.details.label": "Detalji greške",
"error.page.action.restart": "Restartuj", "error.page.action.restart": "Restartuj",
"error.page.action.report": "Prijavi grešku", "error.page.action.report": "Prijavi grešku",
"error.page.action.reported": "Greška prijavljena", "error.page.action.reported": "Greška prijavljena",
"error.page.action.exportLogs": "Izvezi logove",
"error.page.action.checking": "Provjera...", "error.page.action.checking": "Provjera...",
"error.page.action.checkUpdates": "Provjeri ažuriranja", "error.page.action.checkUpdates": "Provjeri ažuriranja",
"error.page.action.updateTo": "Ažuriraj na {{version}}", "error.page.action.updateTo": "Ažuriraj na {{version}}",
@ -504,11 +588,31 @@ export const dict = {
"home.recentProjects": "Nedavni projekti", "home.recentProjects": "Nedavni projekti",
"home.empty.title": "Nema nedavnih projekata", "home.empty.title": "Nema nedavnih projekata",
"home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat", "home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat",
"home.title": "Početna",
"home.projects": "Projekti",
"home.project.add": "Dodaj projekat",
"home.recentlyClosed": "Nedavno zatvoreni projekti",
"home.server.collapse": "Sažmi projekte servera",
"home.server.expand": "Proširi projekte servera",
"home.sessions.search.placeholder": "Pretraži sesije",
"home.sessions.search.placeholder.scoped": "Pretraži sesije u {{scope}}",
"home.sessions.search.sessions": "Sesije",
"home.sessions.search.noResults": "Nema pronađenih sesija za {{query}}",
"home.sessions.empty": "Ovdje još nema ničega",
"home.sessions.empty.description": "Kreiraj sesiju za početak",
"home.sessions.group.today": "Danas",
"home.sessions.group.yesterday": "Jučer",
"home.sessions.group.older": "Starije",
"session.tab.session": "Sesija", "session.tab.session": "Sesija",
"session.tab.review": "Pregled", "session.tab.review": "Pregled",
"session.tab.context": "Kontekst", "session.tab.context": "Kontekst",
"session.tab.unknown": "Nepoznata sesija",
"session.panel.reviewAndFiles": "Pregled i datoteke", "session.panel.reviewAndFiles": "Pregled i datoteke",
"session.error.notFound": "Ova sesija nije pronađena",
"session.error.notFound.description": "Ova kartica upućuje na sesiju koja više ne postoji na ovom serveru.",
"session.error.notFound.closeTab": "Zatvori karticu",
"session.error.serverConnection": "Nije moguće povezati se s ovim serverom",
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka", "session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
"session.review.change.one": "Izmjena", "session.review.change.one": "Izmjena",
"session.review.change.other": "Izmjene", "session.review.change.other": "Izmjene",
@ -518,6 +622,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne", "Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne",
"session.review.noChanges": "Nema izmjena", "session.review.noChanges": "Nema izmjena",
"session.review.noUncommittedChanges": "Još nema nepotvrđenih promjena",
"session.review.noBranchChanges": "Još nema promjena na grani",
"session.files.selectToOpen": "Odaberi datoteku za otvaranje", "session.files.selectToOpen": "Odaberi datoteku za otvaranje",
"session.files.all": "Sve datoteke", "session.files.all": "Sve datoteke",
@ -534,6 +640,10 @@ export const dict = {
"session.todo.title": "Zadaci", "session.todo.title": "Zadaci",
"session.todo.collapse": "Sažmi", "session.todo.collapse": "Sažmi",
"session.todo.expand": "Proširi", "session.todo.expand": "Proširi",
"session.question.minimize": "Minimiziraj pitanje",
"session.question.restore": "Vrati pitanje",
"session.question.pending.one": "{{count}} pitanje na čekanju",
"session.question.pending.other": "{{count}} pitanja na čekanju",
"session.followupDock.summary.one": "{{count}} poruka na čekanju", "session.followupDock.summary.one": "{{count}} poruka na čekanju",
"session.followupDock.summary.other": "{{count}} poruka na čekanju", "session.followupDock.summary.other": "{{count}} poruka na čekanju",
"session.followupDock.sendNow": "Pošalji sada", "session.followupDock.sendNow": "Pošalji sada",
@ -547,9 +657,16 @@ export const dict = {
"session.revertDock.restore": "Vrati poruku", "session.revertDock.restore": "Vrati poruku",
"session.new.title": "Napravi bilo šta", "session.new.title": "Napravi bilo šta",
"session.new.project.new": "Novi projekat",
"session.new.project.search": "Pretraži projekte",
"session.new.project.add": "Dodaj projekat",
"session.new.worktree.main": "Glavna grana", "session.new.worktree.main": "Glavna grana",
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})", "session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
"session.new.worktree.create": "Kreiraj novi worktree", "session.new.worktree.create": "Kreiraj novi worktree",
"session.new.workspace.runIn": "Pokreni sesiju u",
"session.new.workspace.triggerLocal": "Lokalno",
"session.new.workspace.local": "Lokalni repozitorij",
"session.new.workspace.existing": "Radni prostor…",
"session.new.lastModified": "Posljednja izmjena", "session.new.lastModified": "Posljednja izmjena",
"session.header.search.placeholder": "Pretraži {{project}}", "session.header.search.placeholder": "Pretraži {{project}}",
@ -618,6 +735,8 @@ export const dict = {
"sidebar.project.recentSessions": "Nedavne sesije", "sidebar.project.recentSessions": "Nedavne sesije",
"sidebar.project.viewAllSessions": "Prikaži sve sesije", "sidebar.project.viewAllSessions": "Prikaži sve sesije",
"sidebar.project.clearNotifications": "Očisti obavijesti", "sidebar.project.clearNotifications": "Očisti obavijesti",
"sidebar.empty.title": "Nema otvorenih projekata",
"sidebar.empty.description": "Otvori projekat za početak",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@ -630,6 +749,7 @@ export const dict = {
"settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.", "settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.",
"settings.general.section.appearance": "Izgled", "settings.general.section.appearance": "Izgled",
"settings.general.section.advanced": "Napredno",
"settings.general.section.notifications": "Sistemske obavijesti", "settings.general.section.notifications": "Sistemske obavijesti",
"settings.general.section.updates": "Ažuriranja", "settings.general.section.updates": "Ažuriranja",
"settings.general.section.sounds": "Zvučni efekti", "settings.general.section.sounds": "Zvučni efekti",
@ -638,6 +758,11 @@ export const dict = {
"settings.general.row.language.title": "Jezik", "settings.general.row.language.title": "Jezik",
"settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u", "settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u",
"settings.general.row.shell.title": "Shell terminala",
"settings.general.row.shell.description":
"Odaberi shell koji se koristi za terminal. Kompatibilni shellovi koriste se i za pozive alata agenta.",
"settings.general.row.shell.autoDefault": "Automatski (podrazumijevano)",
"settings.general.row.shell.terminalOnly": "samo terminal",
"settings.general.row.appearance.title": "Izgled", "settings.general.row.appearance.title": "Izgled",
"settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju", "settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju",
"settings.general.row.colorScheme.title": "Šema boja", "settings.general.row.colorScheme.title": "Šema boja",
@ -646,14 +771,30 @@ export const dict = {
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.", "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
"settings.general.row.font.title": "Font za kod", "settings.general.row.font.title": "Font za kod",
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda", "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Font terminala",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Prilagodite font koji se koristi u terminalu",
"settings.general.row.uiFont.title": "UI font", "settings.general.row.uiFont.title": "UI font",
"settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu", "settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
"settings.general.row.followup.title": "Ponašanje nadovezivanja", "settings.general.row.followup.title": "Ponašanje nadovezivanja",
"settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu", "settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
"settings.general.row.followup.option.queue": "Red čekanja", "settings.general.row.followup.option.queue": "Red čekanja",
"settings.general.row.followup.option.steer": "Usmjeri", "settings.general.row.followup.option.steer": "Usmjeri",
"settings.general.row.showFileTree.title": "Stablo datoteka",
"settings.general.row.showFileTree.description": "Prikaži stablo datoteka u sesijama",
"settings.general.row.showNavigation.title": "Kontrole navigacije",
"settings.general.row.showNavigation.description":
"Prikaži dugmad za nazad i naprijed u naslovnoj traci desktop aplikacije",
"settings.general.row.showSearch.title": "Paleta komandi",
"settings.general.row.showSearch.description": "Prikaži dugme za pretragu i paletu komandi u naslovnoj traci",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Prikaži dugme terminala u naslovnoj traci desktop aplikacije",
"settings.general.row.showStatus.title": "Status servera",
"settings.general.row.showStatus.description": "Prikaži dugme statusa servera u naslovnoj traci",
"settings.general.row.mobileTitlebarBottom.title": "Donja navigacija",
"settings.general.row.mobileTitlebarBottom.description":
"Postavi naslovnu traku i kartice sesije na dno ekrana na mobilnim uređajima",
"settings.general.row.showCustomAgents.title": "Prilagođeni agenti",
"settings.general.row.showCustomAgents.description": "Prikaži izbor agenta u uređivaču poruke",
"settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja", "settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja",
"settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci", "settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci",
@ -663,6 +804,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje", "settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci", "Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
"settings.general.row.newLayoutDesigns.title": "Novi raspored i dizajn",
"settings.general.row.newLayoutDesigns.description":
"Omogući redizajnirani raspored, početnu stranicu, uređivač poruke i interfejs sesije",
"settings.general.row.pinchZoom.title": "Zumiranje štipanjem",
"settings.general.row.pinchZoom.description":
"Dozvoli zumiranje gestom štipanja na dodirnoj ploči i pomoću Ctrl-pomjeranja",
"settings.general.row.wayland.title": "Koristi nativni Wayland", "settings.general.row.wayland.title": "Koristi nativni Wayland",
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.", "settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -677,6 +824,8 @@ export const dict = {
"settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna", "settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna",
"settings.updates.action.checkNow": "Provjeri sada", "settings.updates.action.checkNow": "Provjeri sada",
"settings.updates.action.checking": "Provjera...", "settings.updates.action.checking": "Provjera...",
"settings.updates.action.downloading": "Preuzimanje...",
"settings.updates.action.installing": "Instaliranje...",
"settings.updates.toast.latest.title": "Sve je ažurno", "settings.updates.toast.latest.title": "Sve je ažurno",
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.", "settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
"sound.option.none": "Nijedan", "sound.option.none": "Nijedan",

View file

@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Skift sidebjælke", "command.sidebar.toggle": "Skift sidebjælke",
"command.project.open": "Åbn projekt", "command.project.open": "Åbn projekt",
"command.project.previous": "Forrige projekt",
"command.project.next": "Næste projekt",
"command.project.index": "Skift til projekt {{index}}",
"command.provider.connect": "Tilslut udbyder", "command.provider.connect": "Tilslut udbyder",
"command.server.switch": "Skift server", "command.server.switch": "Skift server",
"command.settings.open": "Åbn indstillinger", "command.settings.open": "Åbn indstillinger",
@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Ny session", "command.session.new": "Ny session",
"command.file.open": "Åbn fil", "command.file.open": "Åbn fil",
"command.tab.close": "Luk fane", "command.tab.close": "Luk fane",
"command.tab.reopenClosed": "Åbn lukket fane igen",
"command.context.addSelection": "Tilføj markering til kontekst", "command.context.addSelection": "Tilføj markering til kontekst",
"command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil", "command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil",
"command.input.focus": "Fokuser inputfelt", "command.input.focus": "Fokuser inputfelt",
@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Tillader: {{inputs}}", "model.tooltip.allows": "Tillader: {{inputs}}",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Input",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Udbyder",
"model.tooltip.reasoning": "Tænkning",
"model.tooltip.reasoning.allowed": "Tillader tænkning", "model.tooltip.reasoning.allowed": "Tillader tænkning",
"model.tooltip.reasoning.none": "Ingen tænkning", "model.tooltip.reasoning.none": "Ingen tænkning",
"model.tooltip.context": "Kontekstgrænse {{limit}}", "model.tooltip.context": "Kontekstgrænse {{limit}}",
@ -217,6 +226,7 @@ export const dict = {
"common.loading": "Indlæser", "common.loading": "Indlæser",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Annuller", "common.cancel": "Annuller",
"common.clear": "Ryd",
"common.connect": "Forbind", "common.connect": "Forbind",
"common.disconnect": "Frakobl", "common.disconnect": "Frakobl",
"common.continue": "Indsend", "common.continue": "Indsend",
@ -234,6 +244,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc for at afslutte", "prompt.mode.shell.exit": "esc for at afslutte",
"session.child.promptDisabled": "Der kan ikke sendes prompts til underagentsessioner.",
"session.child.backToParent": "Tilbage til hovedsessionen.",
"prompt.example.1": "Ret en TODO i koden", "prompt.example.1": "Ret en TODO i koden",
"prompt.example.2": "Hvad er teknologistakken for dette projekt?", "prompt.example.2": "Hvad er teknologistakken for dette projekt?",
@ -340,6 +352,75 @@ export const dict = {
"dialog.server.menu.delete": "Slet", "dialog.server.menu.delete": "Slet",
"dialog.server.current": "Nuværende server", "dialog.server.current": "Nuværende server",
"dialog.server.status.default": "Standard", "dialog.server.status.default": "Standard",
"wsl.server.add": "Tilføj WSL-server",
"wsl.server.addShort": "Tilføj WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-server",
"wsl.server.retryStart": "Prøv at starte igen",
"wsl.server.updating": "Opdaterer...",
"wsl.onboarding.step.distro": "Vælg distribution",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Tjekker WSL...",
"wsl.onboarding.restartRequired": "Windows skal genstartes for at fuldføre installationen af WSL.",
"wsl.onboarding.ready": "WSL er klar.",
"wsl.onboarding.required": "WSL er påkrævet for at fortsætte.",
"wsl.onboarding.checkingDistros": "Tjekker distributioner...",
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
"wsl.onboarding.checkingDistro": "Tjekker {{distro}}...",
"wsl.onboarding.listingDistros": "Henter liste over distributioner...",
"wsl.onboarding.distroReady": "{{distro}} er klar.",
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installeret endnu.",
"wsl.onboarding.openDistroOnce": "Åbn {{distro}} én gang for at fuldføre opsætningen.",
"wsl.onboarding.finishingDistro": "Fuldfører opsætningen af {{distro}}.",
"wsl.onboarding.pickDistro": "Vælg en distribution, eller installer en nedenfor.",
"wsl.onboarding.checkingOpencode": "Tjekker OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Tjekker OpenCode i {{distro}}...",
"wsl.onboarding.updatingOpencode": "Opdaterer OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Opdaterer OpenCode i {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Opdater OpenCode i {{distro}}.",
"wsl.onboarding.updateOpencode": "Opdater OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode er klar.",
"wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.",
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Vælg først en distribution.",
"wsl.onboarding.loadFailed": "Kunne ikke indlæse WSL-status.",
"wsl.onboarding.loading": "Indlæser...",
"wsl.onboarding.installedDistros": "Installerede distributioner",
"wsl.onboarding.checkAgain": "Tjek igen",
"wsl.onboarding.distroStatus.ready": "Klar",
"wsl.onboarding.distroStatus.checking": "Tjekker...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installeret",
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Ikke understøttet · Brug WSL 2",
"wsl.onboarding.needAnotherDistro": "Har du brug for en anden distribution?",
"wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribution fra WSL-kataloget",
"wsl.onboarding.wslNotInstalled.title": "WSL er ikke installeret",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) er påkrævet, før OpenCode kan tilføje en WSL-server",
"wsl.onboarding.wslUnavailable.title": "WSL er ikke tilgængelig",
"wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekræfte WSL på denne maskine.",
"wsl.onboarding.installWsl": "Installer WSL",
"wsl.onboarding.windowsRestartRequired":
"Genstart Windows for at fuldføre installationen af WSL, og åbn derefter OpenCode igen.",
"wsl.onboarding.next": "Næste",
"wsl.onboarding.refresh": "Opdater",
"wsl.onboarding.allDistrosAdded": "Alle installerede distributioner er allerede tilføjet.",
"wsl.onboarding.noDistros": "Ingen distributioner fundet endnu.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installerer...",
"wsl.onboarding.installDistro": "Installer distribution",
"wsl.onboarding.searchDistros": "Søg efter distributioner",
"wsl.onboarding.wsl2Required": "WSL 2 er påkrævet.",
"wsl.onboarding.toolsRequired": "Denne distribution kræver bash og curl.",
"wsl.onboarding.openTerminal": "Åbn terminal",
"wsl.onboarding.path": "Sti: {{path}}",
"wsl.onboarding.notFound": "ikke fundet",
"wsl.onboarding.version": "Version: {{version}}",
"wsl.onboarding.unknown": "ukendt",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Den installerede version matcher ikke desktopappens version.",
"wsl.onboarding.adding": "Tilføjer...",
"dialog.project.edit.title": "Rediger projekt", "dialog.project.edit.title": "Rediger projekt",
"dialog.project.edit.name": "Navn", "dialog.project.edit.name": "Navn",
@ -403,6 +484,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -450,10 +532,12 @@ export const dict = {
"error.page.title": "Noget gik galt", "error.page.title": "Noget gik galt",
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.", "error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
"error.page.description.localServerStartup": "Der opstod en fejl under start af den lokale server.",
"error.page.details.label": "Fejldetaljer", "error.page.details.label": "Fejldetaljer",
"error.page.action.restart": "Genstart", "error.page.action.restart": "Genstart",
"error.page.action.report": "Rapportér fejl", "error.page.action.report": "Rapportér fejl",
"error.page.action.reported": "Fejl rapporteret", "error.page.action.reported": "Fejl rapporteret",
"error.page.action.exportLogs": "Eksportér logfiler",
"error.page.action.checking": "Tjekker...", "error.page.action.checking": "Tjekker...",
"error.page.action.checkUpdates": "Tjek for opdateringer", "error.page.action.checkUpdates": "Tjek for opdateringer",
"error.page.action.updateTo": "Opdater til {{version}}", "error.page.action.updateTo": "Opdater til {{version}}",
@ -501,11 +585,31 @@ export const dict = {
"home.recentProjects": "Seneste projekter", "home.recentProjects": "Seneste projekter",
"home.empty.title": "Ingen seneste projekter", "home.empty.title": "Ingen seneste projekter",
"home.empty.description": "Kom i gang ved at åbne et lokalt projekt", "home.empty.description": "Kom i gang ved at åbne et lokalt projekt",
"home.title": "Hjem",
"home.projects": "Projekter",
"home.project.add": "Tilføj projekt",
"home.recentlyClosed": "Lukket for nylig",
"home.server.collapse": "Skjul serverprojekter",
"home.server.expand": "Udvid serverprojekter",
"home.sessions.search.placeholder": "Søg i sessioner",
"home.sessions.search.placeholder.scoped": "Søg i sessioner i {{scope}}",
"home.sessions.search.sessions": "Sessioner",
"home.sessions.search.noResults": "Ingen sessioner fundet for {{query}}",
"home.sessions.empty": "Intet her endnu",
"home.sessions.empty.description": "Opret en session for at komme i gang",
"home.sessions.group.today": "I dag",
"home.sessions.group.yesterday": "I går",
"home.sessions.group.older": "Ældre",
"session.tab.session": "Session", "session.tab.session": "Session",
"session.tab.review": "Gennemgang", "session.tab.review": "Gennemgang",
"session.tab.context": "Kontekst", "session.tab.context": "Kontekst",
"session.tab.unknown": "Ukendt session",
"session.panel.reviewAndFiles": "Gennemgang og filer", "session.panel.reviewAndFiles": "Gennemgang og filer",
"session.error.notFound": "Denne session kan ikke findes",
"session.error.notFound.description": "Denne fane henviser til en session, der ikke længere findes på denne server.",
"session.error.notFound.closeTab": "Luk fane",
"session.error.serverConnection": "Kan ikke oprette forbindelse til denne server",
"session.review.filesChanged": "{{count}} Filer ændret", "session.review.filesChanged": "{{count}} Filer ændret",
"session.review.change.one": "Ændring", "session.review.change.one": "Ændring",
"session.review.change.other": "Ændringer", "session.review.change.other": "Ændringer",
@ -515,6 +619,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige", "Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige",
"session.review.noChanges": "Ingen ændringer", "session.review.noChanges": "Ingen ændringer",
"session.review.noUncommittedChanges": "Ingen ændringer uden commit endnu",
"session.review.noBranchChanges": "Ingen grenændringer endnu",
"session.files.selectToOpen": "Vælg en fil at åbne", "session.files.selectToOpen": "Vælg en fil at åbne",
"session.files.all": "Alle filer", "session.files.all": "Alle filer",
"session.files.empty": "Ingen filer", "session.files.empty": "Ingen filer",
@ -529,6 +635,10 @@ export const dict = {
"session.todo.title": "Opgaver", "session.todo.title": "Opgaver",
"session.todo.collapse": "Skjul", "session.todo.collapse": "Skjul",
"session.todo.expand": "Udvid", "session.todo.expand": "Udvid",
"session.question.minimize": "Minimer spørgsmål",
"session.question.restore": "Gendan spørgsmål",
"session.question.pending.one": "{{count}} afventende spørgsmål",
"session.question.pending.other": "{{count}} afventende spørgsmål",
"session.followupDock.summary.one": "{{count}} besked i kø", "session.followupDock.summary.one": "{{count}} besked i kø",
"session.followupDock.summary.other": "{{count}} beskeder i kø", "session.followupDock.summary.other": "{{count}} beskeder i kø",
"session.followupDock.sendNow": "Send nu", "session.followupDock.sendNow": "Send nu",
@ -542,9 +652,16 @@ export const dict = {
"session.revertDock.restore": "Gendan besked", "session.revertDock.restore": "Gendan besked",
"session.new.title": "Byg hvad som helst", "session.new.title": "Byg hvad som helst",
"session.new.project.new": "Nyt projekt",
"session.new.project.search": "Søg efter projekter",
"session.new.project.add": "Tilføj projekt",
"session.new.worktree.main": "Hovedgren", "session.new.worktree.main": "Hovedgren",
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})", "session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
"session.new.worktree.create": "Opret nyt worktree", "session.new.worktree.create": "Opret nyt worktree",
"session.new.workspace.runIn": "Kør session i",
"session.new.workspace.triggerLocal": "Lokal",
"session.new.workspace.local": "Lokalt repository",
"session.new.workspace.existing": "Arbejdsområde…",
"session.new.lastModified": "Sidst ændret", "session.new.lastModified": "Sidst ændret",
"session.header.search.placeholder": "Søg {{project}}", "session.header.search.placeholder": "Søg {{project}}",
@ -614,6 +731,8 @@ export const dict = {
"sidebar.project.recentSessions": "Seneste sessioner", "sidebar.project.recentSessions": "Seneste sessioner",
"sidebar.project.viewAllSessions": "Vis alle sessioner", "sidebar.project.viewAllSessions": "Vis alle sessioner",
"sidebar.project.clearNotifications": "Ryd notifikationer", "sidebar.project.clearNotifications": "Ryd notifikationer",
"sidebar.empty.title": "Ingen åbne projekter",
"sidebar.empty.description": "Åbn et projekt for at komme i gang",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
@ -625,6 +744,7 @@ export const dict = {
"settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.", "settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.",
"settings.general.section.appearance": "Udseende", "settings.general.section.appearance": "Udseende",
"settings.general.section.advanced": "Avanceret",
"settings.general.section.notifications": "Systemmeddelelser", "settings.general.section.notifications": "Systemmeddelelser",
"settings.general.section.updates": "Opdateringer", "settings.general.section.updates": "Opdateringer",
"settings.general.section.sounds": "Lydeffekter", "settings.general.section.sounds": "Lydeffekter",
@ -633,6 +753,11 @@ export const dict = {
"settings.general.row.language.title": "Sprog", "settings.general.row.language.title": "Sprog",
"settings.general.row.language.description": "Ændr visningssproget for OpenCode", "settings.general.row.language.description": "Ændr visningssproget for OpenCode",
"settings.general.row.shell.title": "Terminalshell",
"settings.general.row.shell.description":
"Vælg den shell, der bruges i din terminal. Kompatible shells bruges også til agentens værktøjskald.",
"settings.general.row.shell.autoDefault": "Automatisk (standard)",
"settings.general.row.shell.terminalOnly": "kun terminal",
"settings.general.row.appearance.title": "Udseende", "settings.general.row.appearance.title": "Udseende",
"settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed", "settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed",
"settings.general.row.colorScheme.title": "Farveskema", "settings.general.row.colorScheme.title": "Farveskema",
@ -641,14 +766,29 @@ export const dict = {
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.", "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
"settings.general.row.font.title": "Kode-skrifttype", "settings.general.row.font.title": "Kode-skrifttype",
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke", "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminalskrifttype",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Tilpas den skrifttype, der bruges i terminalen",
"settings.general.row.uiFont.title": "UI-skrifttype", "settings.general.row.uiFont.title": "UI-skrifttype",
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen", "settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
"settings.general.row.followup.title": "Opfølgningsadfærd", "settings.general.row.followup.title": "Opfølgningsadfærd",
"settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø", "settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
"settings.general.row.followup.option.queue": "Kø", "settings.general.row.followup.option.queue": "Kø",
"settings.general.row.followup.option.steer": "Styr", "settings.general.row.followup.option.steer": "Styr",
"settings.general.row.showFileTree.title": "Filtræ",
"settings.general.row.showFileTree.description": "Vis filtræspanelet i sessioner",
"settings.general.row.showNavigation.title": "Navigationsknapper",
"settings.general.row.showNavigation.description": "Vis tilbage- og frem-knapperne i desktopappens titellinje",
"settings.general.row.showSearch.title": "Kommandopalette",
"settings.general.row.showSearch.description": "Vis knappen til søgning og kommandopaletten i titellinjen",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Vis terminalknappen i desktopappens titellinje",
"settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Vis serverstatusknappen i titellinjen",
"settings.general.row.mobileTitlebarBottom.title": "Navigation nederst",
"settings.general.row.mobileTitlebarBottom.description":
"Placer titellinjen og sessionsfanerne nederst på skærmen på mobilenheder",
"settings.general.row.showCustomAgents.title": "Brugerdefinerede agenter",
"settings.general.row.showCustomAgents.description": "Vis agentvælgeren i promptfeltet",
"settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter", "settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter",
"settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen", "settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen",
@ -657,6 +797,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele", "settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen", "Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
"settings.general.row.newLayoutDesigns.title": "Nyt layout og design",
"settings.general.row.newLayoutDesigns.description":
"Aktivér det nydesignede layout, startsiden, promptfeltet og sessionsbrugerfladen",
"settings.general.row.pinchZoom.title": "Knib for at zoome",
"settings.general.row.pinchZoom.description": "Tillad knibebevægelser på pegefeltet og Ctrl-rulning for at zoome",
"settings.general.row.wayland.title": "Brug native Wayland", "settings.general.row.wayland.title": "Brug native Wayland",
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.", "settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -671,6 +816,8 @@ export const dict = {
"settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig", "settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig",
"settings.updates.action.checkNow": "Tjek nu", "settings.updates.action.checkNow": "Tjek nu",
"settings.updates.action.checking": "Tjekker...", "settings.updates.action.checking": "Tjekker...",
"settings.updates.action.downloading": "Downloader...",
"settings.updates.action.installing": "Installerer...",
"settings.updates.toast.latest.title": "Du er opdateret", "settings.updates.toast.latest.title": "Du er opdateret",
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.", "settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",

View file

@ -25,6 +25,9 @@ export const dict = {
"theme.scheme.dark": "Dunkel", "theme.scheme.dark": "Dunkel",
"command.sidebar.toggle": "Seitenleiste umschalten", "command.sidebar.toggle": "Seitenleiste umschalten",
"command.project.open": "Projekt öffnen", "command.project.open": "Projekt öffnen",
"command.project.previous": "Vorheriges Projekt",
"command.project.next": "Nächstes Projekt",
"command.project.index": "Zu Projekt {{index}} wechseln",
"command.provider.connect": "Anbieter verbinden", "command.provider.connect": "Anbieter verbinden",
"command.server.switch": "Server wechseln", "command.server.switch": "Server wechseln",
"command.settings.open": "Einstellungen öffnen", "command.settings.open": "Einstellungen öffnen",
@ -43,6 +46,7 @@ export const dict = {
"command.session.new": "Neue Sitzung", "command.session.new": "Neue Sitzung",
"command.file.open": "Datei öffnen", "command.file.open": "Datei öffnen",
"command.tab.close": "Tab schließen", "command.tab.close": "Tab schließen",
"command.tab.reopenClosed": "Geschlossenen Tab wieder öffnen",
"command.context.addSelection": "Auswahl zum Kontext hinzufügen", "command.context.addSelection": "Auswahl zum Kontext hinzufügen",
"command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen", "command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen",
"command.input.focus": "Eingabefeld fokussieren", "command.input.focus": "Eingabefeld fokussieren",
@ -198,6 +202,11 @@ export const dict = {
"model.input.video": "Video", "model.input.video": "Video",
"model.input.pdf": "PDF", "model.input.pdf": "PDF",
"model.tooltip.allows": "Erlaubt: {{inputs}}", "model.tooltip.allows": "Erlaubt: {{inputs}}",
"model.tooltip.context.label": "Kontext",
"model.tooltip.inputs": "Eingaben",
"model.tooltip.model": "Modell",
"model.tooltip.provider": "Anbieter",
"model.tooltip.reasoning": "Reasoning",
"model.tooltip.reasoning.allowed": "Erlaubt Reasoning", "model.tooltip.reasoning.allowed": "Erlaubt Reasoning",
"model.tooltip.reasoning.none": "Kein Reasoning", "model.tooltip.reasoning.none": "Kein Reasoning",
"model.tooltip.context": "Kontextlimit {{limit}}", "model.tooltip.context": "Kontextlimit {{limit}}",
@ -207,6 +216,7 @@ export const dict = {
"common.loading": "Laden", "common.loading": "Laden",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Abbrechen", "common.cancel": "Abbrechen",
"common.clear": "Leeren",
"common.connect": "Verbinden", "common.connect": "Verbinden",
"common.disconnect": "Trennen", "common.disconnect": "Trennen",
"common.continue": "Absenden", "common.continue": "Absenden",
@ -223,6 +233,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc zum Verlassen", "prompt.mode.shell.exit": "esc zum Verlassen",
"session.child.promptDisabled": "Unteragenten-Sitzungen können keine Prompts erhalten.",
"session.child.backToParent": "Zurück zur Hauptsitzung.",
"prompt.example.1": "Ein TODO in der Codebasis beheben", "prompt.example.1": "Ein TODO in der Codebasis beheben",
"prompt.example.2": "Was ist der Tech-Stack dieses Projekts?", "prompt.example.2": "Was ist der Tech-Stack dieses Projekts?",
"prompt.example.3": "Fehlerhafte Tests beheben", "prompt.example.3": "Fehlerhafte Tests beheben",
@ -320,6 +332,75 @@ export const dict = {
"dialog.server.menu.delete": "Löschen", "dialog.server.menu.delete": "Löschen",
"dialog.server.current": "Aktueller Server", "dialog.server.current": "Aktueller Server",
"dialog.server.status.default": "Standard", "dialog.server.status.default": "Standard",
"wsl.server.add": "WSL-Server hinzufügen",
"wsl.server.addShort": "WSL hinzufügen",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-Server",
"wsl.server.retryStart": "Start erneut versuchen",
"wsl.server.updating": "Wird aktualisiert...",
"wsl.onboarding.step.distro": "Distribution auswählen",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL wird geprüft...",
"wsl.onboarding.restartRequired": "Windows muss neu gestartet werden, um die WSL-Installation abzuschließen.",
"wsl.onboarding.ready": "WSL ist bereit.",
"wsl.onboarding.required": "WSL ist erforderlich, um fortzufahren.",
"wsl.onboarding.checkingDistros": "Distributionen werden geprüft...",
"wsl.onboarding.installingDistro": "{{distro}} wird installiert...",
"wsl.onboarding.checkingDistro": "{{distro}} wird geprüft...",
"wsl.onboarding.listingDistros": "Distributionen werden aufgelistet...",
"wsl.onboarding.distroReady": "{{distro}} ist bereit.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ist noch nicht installiert.",
"wsl.onboarding.openDistroOnce": "Öffnen Sie {{distro}} einmal, um die Einrichtung abzuschließen.",
"wsl.onboarding.finishingDistro": "Einrichtung von {{distro}} wird abgeschlossen.",
"wsl.onboarding.pickDistro": "Wählen Sie eine Distribution aus oder installieren Sie unten eine.",
"wsl.onboarding.checkingOpencode": "OpenCode wird geprüft...",
"wsl.onboarding.checkingOpencodeIn": "OpenCode in {{distro}} wird geprüft...",
"wsl.onboarding.updatingOpencode": "OpenCode wird aktualisiert...",
"wsl.onboarding.updatingOpencodeIn": "OpenCode in {{distro}} wird aktualisiert...",
"wsl.onboarding.updateOpencodeIn": "Aktualisieren Sie OpenCode in {{distro}}.",
"wsl.onboarding.updateOpencode": "OpenCode aktualisieren",
"wsl.onboarding.opencodeReadyIn": "OpenCode ist in {{distro}} bereit.",
"wsl.onboarding.opencodeReady": "OpenCode ist bereit.",
"wsl.onboarding.installOpencodeIn": "Installieren Sie OpenCode in {{distro}}.",
"wsl.onboarding.installOpencode": "OpenCode installieren",
"wsl.onboarding.chooseDistroFirst": "Wählen Sie zuerst eine Distribution aus.",
"wsl.onboarding.loadFailed": "WSL-Status konnte nicht geladen werden.",
"wsl.onboarding.loading": "Wird geladen...",
"wsl.onboarding.installedDistros": "Installierte Distributionen",
"wsl.onboarding.checkAgain": "Erneut prüfen",
"wsl.onboarding.distroStatus.ready": "Bereit",
"wsl.onboarding.distroStatus.checking": "Wird geprüft...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nicht installiert",
"wsl.onboarding.distroStatus.missingTools": "Fehlend: bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Nicht unterstützt · WSL 2 verwenden",
"wsl.onboarding.needAnotherDistro": "Andere Distribution benötigt?",
"wsl.onboarding.needAnotherDistroHint": "Installieren Sie eine Linux-Distribution aus dem WSL-Katalog",
"wsl.onboarding.wslNotInstalled.title": "WSL nicht installiert",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) ist erforderlich, bevor OpenCode einen WSL-Server hinzufügen kann",
"wsl.onboarding.wslUnavailable.title": "WSL nicht verfügbar",
"wsl.onboarding.wslUnavailable.description": "OpenCode konnte WSL auf diesem Computer nicht überprüfen.",
"wsl.onboarding.installWsl": "WSL installieren",
"wsl.onboarding.windowsRestartRequired":
"Starten Sie Windows neu, um die WSL-Installation abzuschließen, und öffnen Sie dann OpenCode erneut.",
"wsl.onboarding.next": "Weiter",
"wsl.onboarding.refresh": "Aktualisieren",
"wsl.onboarding.allDistrosAdded": "Alle installierten Distributionen wurden bereits hinzugefügt.",
"wsl.onboarding.noDistros": "Noch keine Distributionen erkannt.",
"wsl.onboarding.install": "Installieren",
"wsl.onboarding.installing": "Wird installiert...",
"wsl.onboarding.installDistro": "Distribution installieren",
"wsl.onboarding.searchDistros": "Distributionen durchsuchen",
"wsl.onboarding.wsl2Required": "WSL 2 ist erforderlich.",
"wsl.onboarding.toolsRequired": "Diese Distribution benötigt bash und curl.",
"wsl.onboarding.openTerminal": "Terminal öffnen",
"wsl.onboarding.path": "Pfad: {{path}}",
"wsl.onboarding.notFound": "nicht gefunden",
"wsl.onboarding.version": "Version: {{version}}",
"wsl.onboarding.unknown": "unbekannt",
"wsl.onboarding.desktopVersion": "Desktop {{version}}",
"wsl.onboarding.versionMismatch": "Die installierte Version entspricht nicht der Version der Desktop-App.",
"wsl.onboarding.adding": "Wird hinzugefügt...",
"dialog.project.edit.title": "Projekt bearbeiten", "dialog.project.edit.title": "Projekt bearbeiten",
"dialog.project.edit.name": "Name", "dialog.project.edit.name": "Name",
"dialog.project.edit.icon": "Icon", "dialog.project.edit.icon": "Icon",
@ -378,6 +459,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Sprache", "toast.language.title": "Sprache",
@ -414,10 +496,12 @@ export const dict = {
"toast.update.action.notYet": "Noch nicht", "toast.update.action.notYet": "Noch nicht",
"error.page.title": "Etwas ist schiefgelaufen", "error.page.title": "Etwas ist schiefgelaufen",
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.", "error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
"error.page.description.localServerStartup": "Beim Starten des lokalen Servers ist ein Fehler aufgetreten.",
"error.page.details.label": "Fehlerdetails", "error.page.details.label": "Fehlerdetails",
"error.page.action.restart": "Neustart", "error.page.action.restart": "Neustart",
"error.page.action.report": "Fehler melden", "error.page.action.report": "Fehler melden",
"error.page.action.reported": "Fehler gemeldet", "error.page.action.reported": "Fehler gemeldet",
"error.page.action.exportLogs": "Protokolle exportieren",
"error.page.action.checking": "Prüfen...", "error.page.action.checking": "Prüfen...",
"error.page.action.checkUpdates": "Nach Updates suchen", "error.page.action.checkUpdates": "Nach Updates suchen",
"error.page.action.updateTo": "Auf {{version}} aktualisieren", "error.page.action.updateTo": "Auf {{version}} aktualisieren",
@ -461,10 +545,31 @@ export const dict = {
"home.recentProjects": "Letzte Projekte", "home.recentProjects": "Letzte Projekte",
"home.empty.title": "Keine letzten Projekte", "home.empty.title": "Keine letzten Projekte",
"home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen", "home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen",
"home.title": "Startseite",
"home.projects": "Projekte",
"home.project.add": "Projekt hinzufügen",
"home.recentlyClosed": "Kürzlich geschlossen",
"home.server.collapse": "Serverprojekte einklappen",
"home.server.expand": "Serverprojekte ausklappen",
"home.sessions.search.placeholder": "Sitzungen durchsuchen",
"home.sessions.search.placeholder.scoped": "Sitzungen in {{scope}} durchsuchen",
"home.sessions.search.sessions": "Sitzungen",
"home.sessions.search.noResults": "Keine Sitzungen für {{query}} gefunden",
"home.sessions.empty": "Hier ist noch nichts",
"home.sessions.empty.description": "Erstellen Sie eine Sitzung, um loszulegen",
"home.sessions.group.today": "Heute",
"home.sessions.group.yesterday": "Gestern",
"home.sessions.group.older": "Älter",
"session.tab.session": "Sitzung", "session.tab.session": "Sitzung",
"session.tab.review": "Überprüfung", "session.tab.review": "Überprüfung",
"session.tab.context": "Kontext", "session.tab.context": "Kontext",
"session.tab.unknown": "Unbekannte Sitzung",
"session.panel.reviewAndFiles": "Überprüfung und Dateien", "session.panel.reviewAndFiles": "Überprüfung und Dateien",
"session.error.notFound": "Diese Sitzung wurde nicht gefunden",
"session.error.notFound.description":
"Dieser Tab verweist auf eine Sitzung, die auf diesem Server nicht mehr existiert.",
"session.error.notFound.closeTab": "Tab schließen",
"session.error.serverConnection": "Verbindung zu diesem Server nicht möglich",
"session.review.filesChanged": "{{count}} Dateien geändert", "session.review.filesChanged": "{{count}} Dateien geändert",
"session.review.change.one": "Änderung", "session.review.change.one": "Änderung",
"session.review.change.other": "Änderungen", "session.review.change.other": "Änderungen",
@ -474,6 +579,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar", "Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar",
"session.review.noChanges": "Keine Änderungen", "session.review.noChanges": "Keine Änderungen",
"session.review.noUncommittedChanges": "Noch keine nicht committeten Änderungen",
"session.review.noBranchChanges": "Noch keine Branch-Änderungen",
"session.files.selectToOpen": "Datei zum Öffnen auswählen", "session.files.selectToOpen": "Datei zum Öffnen auswählen",
"session.files.all": "Alle Dateien", "session.files.all": "Alle Dateien",
"session.files.empty": "Keine Dateien", "session.files.empty": "Keine Dateien",
@ -487,6 +594,10 @@ export const dict = {
"session.todo.title": "Aufgaben", "session.todo.title": "Aufgaben",
"session.todo.collapse": "Einklappen", "session.todo.collapse": "Einklappen",
"session.todo.expand": "Ausklappen", "session.todo.expand": "Ausklappen",
"session.question.minimize": "Frage minimieren",
"session.question.restore": "Frage wiederherstellen",
"session.question.pending.one": "{{count}} ausstehende Frage",
"session.question.pending.other": "{{count}} ausstehende Fragen",
"session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange", "session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange",
"session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange", "session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange",
"session.followupDock.sendNow": "Jetzt senden", "session.followupDock.sendNow": "Jetzt senden",
@ -499,9 +610,16 @@ export const dict = {
"session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen", "session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen",
"session.revertDock.restore": "Nachricht wiederherstellen", "session.revertDock.restore": "Nachricht wiederherstellen",
"session.new.title": "Baue, was du willst", "session.new.title": "Baue, was du willst",
"session.new.project.new": "Neues Projekt",
"session.new.project.search": "Projekte durchsuchen",
"session.new.project.add": "Projekt hinzufügen",
"session.new.worktree.main": "Haupt-Branch", "session.new.worktree.main": "Haupt-Branch",
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})", "session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
"session.new.worktree.create": "Neuen Worktree erstellen", "session.new.worktree.create": "Neuen Worktree erstellen",
"session.new.workspace.runIn": "Sitzung ausführen in",
"session.new.workspace.triggerLocal": "Lokal",
"session.new.workspace.local": "Lokales Repository",
"session.new.workspace.existing": "Arbeitsbereich…",
"session.new.lastModified": "Zuletzt geändert", "session.new.lastModified": "Zuletzt geändert",
"session.header.search.placeholder": "{{project}} durchsuchen", "session.header.search.placeholder": "{{project}} durchsuchen",
"session.header.searchFiles": "Dateien suchen", "session.header.searchFiles": "Dateien suchen",
@ -566,6 +684,8 @@ export const dict = {
"sidebar.project.recentSessions": "Letzte Sitzungen", "sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen", "sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
"sidebar.project.clearNotifications": "Benachrichtigungen löschen", "sidebar.project.clearNotifications": "Benachrichtigungen löschen",
"sidebar.empty.title": "Keine Projekte geöffnet",
"sidebar.empty.description": "Öffnen Sie ein Projekt, um loszulegen",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Server", "settings.section.server": "Server",
@ -575,6 +695,7 @@ export const dict = {
"settings.desktop.wsl.title": "WSL-Integration", "settings.desktop.wsl.title": "WSL-Integration",
"settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.", "settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.",
"settings.general.section.appearance": "Erscheinungsbild", "settings.general.section.appearance": "Erscheinungsbild",
"settings.general.section.advanced": "Erweitert",
"settings.general.section.notifications": "Systembenachrichtigungen", "settings.general.section.notifications": "Systembenachrichtigungen",
"settings.general.section.updates": "Updates", "settings.general.section.updates": "Updates",
"settings.general.section.sounds": "Soundeffekte", "settings.general.section.sounds": "Soundeffekte",
@ -582,6 +703,11 @@ export const dict = {
"settings.general.section.display": "Anzeige", "settings.general.section.display": "Anzeige",
"settings.general.row.language.title": "Sprache", "settings.general.row.language.title": "Sprache",
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
"settings.general.row.shell.title": "Terminal-Shell",
"settings.general.row.shell.description":
"Wählen Sie die Shell für Ihr Terminal aus. Kompatible Shells werden auch für Tool-Aufrufe von Agenten verwendet.",
"settings.general.row.shell.autoDefault": "Automatisch (Standard)",
"settings.general.row.shell.terminalOnly": "nur Terminal",
"settings.general.row.appearance.title": "Erscheinungsbild", "settings.general.row.appearance.title": "Erscheinungsbild",
"settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht", "settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
"settings.general.row.colorScheme.title": "Farbschema", "settings.general.row.colorScheme.title": "Farbschema",
@ -591,8 +717,8 @@ export const dict = {
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.", "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
"settings.general.row.font.title": "Code-Schriftart", "settings.general.row.font.title": "Code-Schriftart",
"settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen", "settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminalschriftart",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Passe die im Terminal verwendete Schriftart an",
"settings.general.row.uiFont.title": "UI-Schriftart", "settings.general.row.uiFont.title": "UI-Schriftart",
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen", "settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
"settings.general.row.followup.title": "Verhalten bei Folgefragen", "settings.general.row.followup.title": "Verhalten bei Folgefragen",
@ -600,6 +726,23 @@ export const dict = {
"Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten", "Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
"settings.general.row.followup.option.queue": "Warteschlange", "settings.general.row.followup.option.queue": "Warteschlange",
"settings.general.row.followup.option.steer": "Steuern", "settings.general.row.followup.option.steer": "Steuern",
"settings.general.row.showFileTree.title": "Dateibaum",
"settings.general.row.showFileTree.description": "Dateibaum in Sitzungen anzeigen",
"settings.general.row.showNavigation.title": "Navigationssteuerung",
"settings.general.row.showNavigation.description":
"Schaltflächen zum Vor- und Zurücknavigieren in der Desktop-Titelleiste anzeigen",
"settings.general.row.showSearch.title": "Befehlspalette",
"settings.general.row.showSearch.description":
"Schaltfläche für Suche und Befehlspalette in der Titelleiste anzeigen",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Terminal-Schaltfläche in der Desktop-Titelleiste anzeigen",
"settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Serverstatus-Schaltfläche in der Titelleiste anzeigen",
"settings.general.row.mobileTitlebarBottom.title": "Navigation unten",
"settings.general.row.mobileTitlebarBottom.description":
"Titelleiste und Sitzungs-Tabs auf Mobilgeräten am unteren Bildschirmrand platzieren",
"settings.general.row.showCustomAgents.title": "Benutzerdefinierte Agenten",
"settings.general.row.showCustomAgents.description": "Agentenauswahl im Eingabebereich anzeigen",
"settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen", "settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen",
"settings.general.row.reasoningSummaries.description": "settings.general.row.reasoningSummaries.description":
"Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen", "Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen",
@ -609,6 +752,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen", "settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen", "Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
"settings.general.row.newLayoutDesigns.title": "Neues Layout und Design",
"settings.general.row.newLayoutDesigns.description":
"Neu gestaltetes Layout, Startseite, Eingabebereich und Sitzungsoberfläche aktivieren",
"settings.general.row.pinchZoom.title": "Zoom per Fingergeste",
"settings.general.row.pinchZoom.description": "Zoomen per Zwei-Finger- und Ctrl-Scroll-Geste erlauben",
"settings.general.row.wayland.title": "Natives Wayland verwenden", "settings.general.row.wayland.title": "Natives Wayland verwenden",
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.", "settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -621,6 +769,8 @@ export const dict = {
"settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar", "settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar",
"settings.updates.action.checkNow": "Jetzt prüfen", "settings.updates.action.checkNow": "Jetzt prüfen",
"settings.updates.action.checking": "Wird geprüft...", "settings.updates.action.checking": "Wird geprüft...",
"settings.updates.action.downloading": "Wird heruntergeladen...",
"settings.updates.action.installing": "Wird installiert...",
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand", "settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.", "settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
"sound.option.none": "Keine", "sound.option.none": "Keine",

View file

@ -212,6 +212,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Allows: {{inputs}}", "model.tooltip.allows": "Allows: {{inputs}}",
"model.tooltip.context.label": "Context",
"model.tooltip.inputs": "Inputs",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Provider",
"model.tooltip.reasoning": "Reasoning",
"model.tooltip.reasoning.allowed": "Allows reasoning", "model.tooltip.reasoning.allowed": "Allows reasoning",
"model.tooltip.reasoning.none": "No reasoning", "model.tooltip.reasoning.none": "No reasoning",
"model.tooltip.context": "Context limit {{limit}}", "model.tooltip.context": "Context limit {{limit}}",

View file

@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Alternar barra lateral", "command.sidebar.toggle": "Alternar barra lateral",
"command.project.open": "Abrir proyecto", "command.project.open": "Abrir proyecto",
"command.project.previous": "Proyecto anterior",
"command.project.next": "Siguiente proyecto",
"command.project.index": "Cambiar al proyecto {{index}}",
"command.provider.connect": "Conectar proveedor", "command.provider.connect": "Conectar proveedor",
"command.server.switch": "Cambiar servidor", "command.server.switch": "Cambiar servidor",
"command.settings.open": "Abrir ajustes", "command.settings.open": "Abrir ajustes",
@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Nueva sesión", "command.session.new": "Nueva sesión",
"command.file.open": "Abrir archivo", "command.file.open": "Abrir archivo",
"command.tab.close": "Cerrar pestaña", "command.tab.close": "Cerrar pestaña",
"command.tab.reopenClosed": "Reabrir pestaña cerrada",
"command.context.addSelection": "Añadir selección al contexto", "command.context.addSelection": "Añadir selección al contexto",
"command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual", "command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual",
"command.input.focus": "Enfocar entrada", "command.input.focus": "Enfocar entrada",
@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Permite: {{inputs}}", "model.tooltip.allows": "Permite: {{inputs}}",
"model.tooltip.context.label": "Contexto",
"model.tooltip.inputs": "Entradas",
"model.tooltip.model": "Modelo",
"model.tooltip.provider": "Proveedor",
"model.tooltip.reasoning": "Razonamiento",
"model.tooltip.reasoning.allowed": "Permite razonamiento", "model.tooltip.reasoning.allowed": "Permite razonamiento",
"model.tooltip.reasoning.none": "Sin razonamiento", "model.tooltip.reasoning.none": "Sin razonamiento",
"model.tooltip.context": "Límite de contexto {{limit}}", "model.tooltip.context": "Límite de contexto {{limit}}",
@ -218,6 +227,7 @@ export const dict = {
"common.loading": "Cargando", "common.loading": "Cargando",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Cancelar", "common.cancel": "Cancelar",
"common.clear": "Limpiar",
"common.connect": "Conectar", "common.connect": "Conectar",
"common.disconnect": "Desconectar", "common.disconnect": "Desconectar",
"common.continue": "Enviar", "common.continue": "Enviar",
@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc para salir", "prompt.mode.shell.exit": "esc para salir",
"session.child.promptDisabled": "No se pueden enviar prompts a las sesiones de subagentes.",
"session.child.backToParent": "Volver a la sesión principal.",
"prompt.example.1": "Arreglar un TODO en el código", "prompt.example.1": "Arreglar un TODO en el código",
"prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?", "prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?",
@ -341,6 +353,75 @@ export const dict = {
"dialog.server.menu.delete": "Eliminar", "dialog.server.menu.delete": "Eliminar",
"dialog.server.current": "Servidor actual", "dialog.server.current": "Servidor actual",
"dialog.server.status.default": "Predeterminado", "dialog.server.status.default": "Predeterminado",
"wsl.server.add": "Añadir servidor WSL",
"wsl.server.addShort": "Añadir WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Servidor WSL",
"wsl.server.retryStart": "Reintentar el inicio",
"wsl.server.updating": "Actualizando...",
"wsl.onboarding.step.distro": "Elegir distribución",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Comprobando WSL...",
"wsl.onboarding.restartRequired": "Es necesario reiniciar Windows para terminar de instalar WSL.",
"wsl.onboarding.ready": "WSL está listo.",
"wsl.onboarding.required": "Se necesita WSL para continuar.",
"wsl.onboarding.checkingDistros": "Comprobando distribuciones...",
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
"wsl.onboarding.checkingDistro": "Comprobando {{distro}}...",
"wsl.onboarding.listingDistros": "Listando distribuciones...",
"wsl.onboarding.distroReady": "La distribución {{distro}} está lista.",
"wsl.onboarding.distroNotInstalled": "La distribución {{distro}} aún no está instalada.",
"wsl.onboarding.openDistroOnce": "Abre {{distro}} una vez para completar la configuración.",
"wsl.onboarding.finishingDistro": "Completando la configuración de {{distro}}.",
"wsl.onboarding.pickDistro": "Elige una distribución o instala una abajo.",
"wsl.onboarding.checkingOpencode": "Comprobando OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Comprobando OpenCode en {{distro}}...",
"wsl.onboarding.updatingOpencode": "Actualizando OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Actualizando OpenCode en {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Actualiza OpenCode en {{distro}}.",
"wsl.onboarding.updateOpencode": "Actualizar OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode está listo en {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode está listo.",
"wsl.onboarding.installOpencodeIn": "Instala OpenCode en {{distro}}.",
"wsl.onboarding.installOpencode": "Instalar OpenCode",
"wsl.onboarding.chooseDistroFirst": "Elige primero una distribución.",
"wsl.onboarding.loadFailed": "No se pudo cargar el estado de WSL.",
"wsl.onboarding.loading": "Cargando...",
"wsl.onboarding.installedDistros": "Distribuciones instaladas",
"wsl.onboarding.checkAgain": "Comprobar de nuevo",
"wsl.onboarding.distroStatus.ready": "Lista",
"wsl.onboarding.distroStatus.checking": "Comprobando...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode no está instalado",
"wsl.onboarding.distroStatus.missingTools": "Faltan bash y curl",
"wsl.onboarding.distroStatus.unsupported": "No compatible · Usa WSL 2",
"wsl.onboarding.needAnotherDistro": "¿Necesitas otra distribución?",
"wsl.onboarding.needAnotherDistroHint": "Instala una distribución de Linux desde el catálogo de WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL no está instalado",
"wsl.onboarding.wslNotInstalled.description":
"Se necesita WSL (Subsistema de Windows para Linux) para que OpenCode pueda añadir un servidor WSL",
"wsl.onboarding.wslUnavailable.title": "WSL no disponible",
"wsl.onboarding.wslUnavailable.description": "OpenCode no pudo comprobar WSL en este equipo.",
"wsl.onboarding.installWsl": "Instalar WSL",
"wsl.onboarding.windowsRestartRequired":
"Reinicia Windows para terminar de instalar WSL y, después, vuelve a abrir OpenCode.",
"wsl.onboarding.next": "Siguiente",
"wsl.onboarding.refresh": "Actualizar",
"wsl.onboarding.allDistrosAdded": "Ya se han añadido todas las distribuciones instaladas.",
"wsl.onboarding.noDistros": "Aún no se han detectado distribuciones.",
"wsl.onboarding.install": "Instalar",
"wsl.onboarding.installing": "Instalando...",
"wsl.onboarding.installDistro": "Instalar distribución",
"wsl.onboarding.searchDistros": "Buscar distribuciones",
"wsl.onboarding.wsl2Required": "Se necesita WSL 2.",
"wsl.onboarding.toolsRequired": "Esta distribución necesita bash y curl.",
"wsl.onboarding.openTerminal": "Abrir terminal",
"wsl.onboarding.path": "Ruta: {{path}}",
"wsl.onboarding.notFound": "no encontrado",
"wsl.onboarding.version": "Versión: {{version}}",
"wsl.onboarding.unknown": "desconocida",
"wsl.onboarding.desktopVersion": "escritorio {{version}}",
"wsl.onboarding.versionMismatch": "La versión instalada no coincide con la versión de la aplicación de escritorio.",
"wsl.onboarding.adding": "Añadiendo...",
"dialog.project.edit.title": "Editar proyecto", "dialog.project.edit.title": "Editar proyecto",
"dialog.project.edit.name": "Nombre", "dialog.project.edit.name": "Nombre",
@ -405,6 +486,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -453,10 +535,12 @@ export const dict = {
"error.page.title": "Algo salió mal", "error.page.title": "Algo salió mal",
"error.page.description": "Ocurrió un error al cargar la aplicación.", "error.page.description": "Ocurrió un error al cargar la aplicación.",
"error.page.description.localServerStartup": "Ocurrió un error al iniciar el servidor local.",
"error.page.details.label": "Detalles del error", "error.page.details.label": "Detalles del error",
"error.page.action.restart": "Reiniciar", "error.page.action.restart": "Reiniciar",
"error.page.action.report": "Informar error", "error.page.action.report": "Informar error",
"error.page.action.reported": "Error informado", "error.page.action.reported": "Error informado",
"error.page.action.exportLogs": "Exportar registros",
"error.page.action.checking": "Comprobando...", "error.page.action.checking": "Comprobando...",
"error.page.action.checkUpdates": "Buscar actualizaciones", "error.page.action.checkUpdates": "Buscar actualizaciones",
"error.page.action.updateTo": "Actualizar a {{version}}", "error.page.action.updateTo": "Actualizar a {{version}}",
@ -505,11 +589,31 @@ export const dict = {
"home.recentProjects": "Proyectos recientes", "home.recentProjects": "Proyectos recientes",
"home.empty.title": "Sin proyectos recientes", "home.empty.title": "Sin proyectos recientes",
"home.empty.description": "Empieza abriendo un proyecto local", "home.empty.description": "Empieza abriendo un proyecto local",
"home.title": "Inicio",
"home.projects": "Proyectos",
"home.project.add": "Añadir proyecto",
"home.recentlyClosed": "Proyectos cerrados recientemente",
"home.server.collapse": "Contraer proyectos del servidor",
"home.server.expand": "Expandir proyectos del servidor",
"home.sessions.search.placeholder": "Buscar sesiones",
"home.sessions.search.placeholder.scoped": "Buscar sesiones en {{scope}}",
"home.sessions.search.sessions": "Sesiones",
"home.sessions.search.noResults": "No se encontraron sesiones para {{query}}",
"home.sessions.empty": "Aún no hay nada",
"home.sessions.empty.description": "Crea una sesión para empezar",
"home.sessions.group.today": "Hoy",
"home.sessions.group.yesterday": "Ayer",
"home.sessions.group.older": "Anteriores",
"session.tab.session": "Sesión", "session.tab.session": "Sesión",
"session.tab.review": "Revisión", "session.tab.review": "Revisión",
"session.tab.context": "Contexto", "session.tab.context": "Contexto",
"session.tab.unknown": "Sesión desconocida",
"session.panel.reviewAndFiles": "Revisión y archivos", "session.panel.reviewAndFiles": "Revisión y archivos",
"session.error.notFound": "No se encuentra esta sesión",
"session.error.notFound.description": "Esta pestaña apunta a una sesión que ya no existe en este servidor.",
"session.error.notFound.closeTab": "Cerrar pestaña",
"session.error.serverConnection": "No se puede conectar a este servidor",
"session.review.filesChanged": "{{count}} Archivos Cambiados", "session.review.filesChanged": "{{count}} Archivos Cambiados",
"session.review.change.one": "Cambio", "session.review.change.one": "Cambio",
"session.review.change.other": "Cambios", "session.review.change.other": "Cambios",
@ -519,6 +623,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles", "El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles",
"session.review.noChanges": "Sin cambios", "session.review.noChanges": "Sin cambios",
"session.review.noUncommittedChanges": "Aún no hay cambios sin confirmar",
"session.review.noBranchChanges": "Aún no hay cambios en la rama",
"session.files.selectToOpen": "Selecciona un archivo para abrir", "session.files.selectToOpen": "Selecciona un archivo para abrir",
"session.files.all": "Todos los archivos", "session.files.all": "Todos los archivos",
@ -535,6 +641,10 @@ export const dict = {
"session.todo.title": "Tareas", "session.todo.title": "Tareas",
"session.todo.collapse": "Contraer", "session.todo.collapse": "Contraer",
"session.todo.expand": "Expandir", "session.todo.expand": "Expandir",
"session.question.minimize": "Minimizar pregunta",
"session.question.restore": "Restaurar pregunta",
"session.question.pending.one": "{{count}} pregunta pendiente",
"session.question.pending.other": "{{count}} preguntas pendientes",
"session.followupDock.summary.one": "{{count}} mensaje en cola", "session.followupDock.summary.one": "{{count}} mensaje en cola",
"session.followupDock.summary.other": "{{count}} mensajes en cola", "session.followupDock.summary.other": "{{count}} mensajes en cola",
"session.followupDock.sendNow": "Enviar ahora", "session.followupDock.sendNow": "Enviar ahora",
@ -548,9 +658,16 @@ export const dict = {
"session.revertDock.restore": "Restaurar mensaje", "session.revertDock.restore": "Restaurar mensaje",
"session.new.title": "Construye lo que quieras", "session.new.title": "Construye lo que quieras",
"session.new.project.new": "Nuevo proyecto",
"session.new.project.search": "Buscar proyectos",
"session.new.project.add": "Añadir proyecto",
"session.new.worktree.main": "Rama principal", "session.new.worktree.main": "Rama principal",
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})", "session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
"session.new.worktree.create": "Crear nuevo árbol de trabajo", "session.new.worktree.create": "Crear nuevo árbol de trabajo",
"session.new.workspace.runIn": "Ejecutar sesión en",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Repositorio local",
"session.new.workspace.existing": "Espacio de trabajo…",
"session.new.lastModified": "Última modificación", "session.new.lastModified": "Última modificación",
"session.header.search.placeholder": "Buscar {{project}}", "session.header.search.placeholder": "Buscar {{project}}",
@ -621,6 +738,8 @@ export const dict = {
"sidebar.project.recentSessions": "Sesiones recientes", "sidebar.project.recentSessions": "Sesiones recientes",
"sidebar.project.viewAllSessions": "Ver todas las sesiones", "sidebar.project.viewAllSessions": "Ver todas las sesiones",
"sidebar.project.clearNotifications": "Borrar notificaciones", "sidebar.project.clearNotifications": "Borrar notificaciones",
"sidebar.empty.title": "No hay proyectos abiertos",
"sidebar.empty.description": "Abre un proyecto para empezar",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@ -633,6 +752,7 @@ export const dict = {
"settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.", "settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.",
"settings.general.section.appearance": "Apariencia", "settings.general.section.appearance": "Apariencia",
"settings.general.section.advanced": "Avanzado",
"settings.general.section.notifications": "Notificaciones del sistema", "settings.general.section.notifications": "Notificaciones del sistema",
"settings.general.section.updates": "Actualizaciones", "settings.general.section.updates": "Actualizaciones",
"settings.general.section.sounds": "Efectos de sonido", "settings.general.section.sounds": "Efectos de sonido",
@ -641,6 +761,11 @@ export const dict = {
"settings.general.row.language.title": "Idioma", "settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
"settings.general.row.shell.title": "Shell del terminal",
"settings.general.row.shell.description":
"Elige el shell que se usará en el terminal. Los shells compatibles también se usarán para las llamadas a herramientas del agente.",
"settings.general.row.shell.autoDefault": "Automático (Predeterminado)",
"settings.general.row.shell.terminalOnly": "solo en el terminal",
"settings.general.row.appearance.title": "Apariencia", "settings.general.row.appearance.title": "Apariencia",
"settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo", "settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
"settings.general.row.colorScheme.title": "Esquema de color", "settings.general.row.colorScheme.title": "Esquema de color",
@ -649,8 +774,8 @@ export const dict = {
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.", "settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
"settings.general.row.font.title": "Fuente de código", "settings.general.row.font.title": "Fuente de código",
"settings.general.row.font.description": "Personaliza la fuente usada en bloques de código", "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Fuente del terminal",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Personaliza la fuente utilizada en el terminal",
"settings.general.row.uiFont.title": "Fuente de la interfaz", "settings.general.row.uiFont.title": "Fuente de la interfaz",
"settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz", "settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
"settings.general.row.followup.title": "Comportamiento de seguimiento", "settings.general.row.followup.title": "Comportamiento de seguimiento",
@ -658,6 +783,23 @@ export const dict = {
"Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola", "Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
"settings.general.row.followup.option.queue": "Cola", "settings.general.row.followup.option.queue": "Cola",
"settings.general.row.followup.option.steer": "Dirigir", "settings.general.row.followup.option.steer": "Dirigir",
"settings.general.row.showFileTree.title": "Árbol de archivos",
"settings.general.row.showFileTree.description": "Mostrar el panel del árbol de archivos en las sesiones",
"settings.general.row.showNavigation.title": "Controles de navegación",
"settings.general.row.showNavigation.description":
"Mostrar los botones para retroceder y avanzar en la barra de título de la aplicación de escritorio",
"settings.general.row.showSearch.title": "Paleta de comandos",
"settings.general.row.showSearch.description":
"Mostrar el botón de búsqueda y paleta de comandos en la barra de título",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Mostrar el botón del terminal en la barra de título",
"settings.general.row.showStatus.title": "Estado del servidor",
"settings.general.row.showStatus.description": "Mostrar el botón de estado del servidor en la barra de título",
"settings.general.row.mobileTitlebarBottom.title": "Navegación inferior",
"settings.general.row.mobileTitlebarBottom.description":
"Colocar la barra de título y las pestañas de sesión en la parte inferior de la pantalla en dispositivos móviles",
"settings.general.row.showCustomAgents.title": "Agentes personalizados",
"settings.general.row.showCustomAgents.description": "Mostrar el selector de agentes en el editor",
"settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento", "settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento",
"settings.general.row.reasoningSummaries.description": "settings.general.row.reasoningSummaries.description":
"Mostrar resúmenes del razonamiento del modelo en la línea de tiempo", "Mostrar resúmenes del razonamiento del modelo en la línea de tiempo",
@ -667,6 +809,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición", "settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo", "Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
"settings.general.row.newLayoutDesigns.title": "Nuevo diseño y estilos",
"settings.general.row.newLayoutDesigns.description":
"Activar el nuevo diseño de la interfaz, la página de inicio, el editor y las sesiones",
"settings.general.row.pinchZoom.title": "Pellizcar para ampliar",
"settings.general.row.pinchZoom.description":
"Permitir ampliar con el gesto de pellizco del panel táctil y con Ctrl-scroll",
"settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.", "settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -682,6 +830,8 @@ export const dict = {
"settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna", "settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna",
"settings.updates.action.checkNow": "Buscar ahora", "settings.updates.action.checkNow": "Buscar ahora",
"settings.updates.action.checking": "Buscando...", "settings.updates.action.checking": "Buscando...",
"settings.updates.action.downloading": "Descargando...",
"settings.updates.action.installing": "Instalando...",
"settings.updates.toast.latest.title": "Estás al día", "settings.updates.toast.latest.title": "Estás al día",
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.", "settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
"sound.option.none": "Ninguno", "sound.option.none": "Ninguno",

View file

@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "Sombre", "theme.scheme.dark": "Sombre",
"command.sidebar.toggle": "Basculer la barre latérale", "command.sidebar.toggle": "Basculer la barre latérale",
"command.project.open": "Ouvrir un projet", "command.project.open": "Ouvrir un projet",
"command.project.previous": "Projet précédent",
"command.project.next": "Projet suivant",
"command.project.index": "Passer au projet {{index}}",
"command.provider.connect": "Connecter un fournisseur", "command.provider.connect": "Connecter un fournisseur",
"command.server.switch": "Changer de serveur", "command.server.switch": "Changer de serveur",
"command.settings.open": "Ouvrir les paramètres", "command.settings.open": "Ouvrir les paramètres",
@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "Nouvelle session", "command.session.new": "Nouvelle session",
"command.file.open": "Ouvrir un fichier", "command.file.open": "Ouvrir un fichier",
"command.tab.close": "Fermer l'onglet", "command.tab.close": "Fermer l'onglet",
"command.tab.reopenClosed": "Rouvrir l'onglet fermé",
"command.context.addSelection": "Ajouter la sélection au contexte", "command.context.addSelection": "Ajouter la sélection au contexte",
"command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel", "command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel",
"command.input.focus": "Focus sur l'entrée", "command.input.focus": "Focus sur l'entrée",
@ -193,6 +197,11 @@ export const dict = {
"model.input.video": "vidéo", "model.input.video": "vidéo",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Autorise : {{inputs}}", "model.tooltip.allows": "Autorise : {{inputs}}",
"model.tooltip.context.label": "Contexte",
"model.tooltip.inputs": "Entrées",
"model.tooltip.model": "Modèle",
"model.tooltip.provider": "Fournisseur",
"model.tooltip.reasoning": "Raisonnement",
"model.tooltip.reasoning.allowed": "Autorise le raisonnement", "model.tooltip.reasoning.allowed": "Autorise le raisonnement",
"model.tooltip.reasoning.none": "Sans raisonnement", "model.tooltip.reasoning.none": "Sans raisonnement",
"model.tooltip.context": "Limite de contexte {{limit}}", "model.tooltip.context": "Limite de contexte {{limit}}",
@ -202,6 +211,7 @@ export const dict = {
"common.loading": "Chargement", "common.loading": "Chargement",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Annuler", "common.cancel": "Annuler",
"common.clear": "Effacer",
"common.connect": "Connecter", "common.connect": "Connecter",
"common.disconnect": "Déconnecter", "common.disconnect": "Déconnecter",
"common.continue": "Soumettre", "common.continue": "Soumettre",
@ -218,6 +228,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc pour quitter", "prompt.mode.shell.exit": "esc pour quitter",
"session.child.promptDisabled": "Les sessions de sous-agents ne peuvent pas recevoir de messages.",
"session.child.backToParent": "Revenir à la session principale.",
"prompt.example.1": "Corriger un TODO dans la base de code", "prompt.example.1": "Corriger un TODO dans la base de code",
"prompt.example.2": "Quelle est la pile technique de ce projet ?", "prompt.example.2": "Quelle est la pile technique de ce projet ?",
"prompt.example.3": "Réparer les tests échoués", "prompt.example.3": "Réparer les tests échoués",
@ -315,6 +327,75 @@ export const dict = {
"dialog.server.menu.delete": "Supprimer", "dialog.server.menu.delete": "Supprimer",
"dialog.server.current": "Serveur actuel", "dialog.server.current": "Serveur actuel",
"dialog.server.status.default": "Défaut", "dialog.server.status.default": "Défaut",
"wsl.server.add": "Ajouter un serveur WSL",
"wsl.server.addShort": "Ajouter WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Serveur WSL",
"wsl.server.retryStart": "Réessayer de démarrer",
"wsl.server.updating": "Mise à jour...",
"wsl.onboarding.step.distro": "Choisir une distribution",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Vérification de WSL...",
"wsl.onboarding.restartRequired": "Windows doit redémarrer pour terminer l'installation de WSL.",
"wsl.onboarding.ready": "WSL est prêt.",
"wsl.onboarding.required": "WSL est requis pour continuer.",
"wsl.onboarding.checkingDistros": "Vérification des distributions...",
"wsl.onboarding.installingDistro": "Installation de {{distro}}...",
"wsl.onboarding.checkingDistro": "Vérification de {{distro}}...",
"wsl.onboarding.listingDistros": "Récupération de la liste des distributions...",
"wsl.onboarding.distroReady": "{{distro}} est prête.",
"wsl.onboarding.distroNotInstalled": "{{distro}} n'est pas encore installée.",
"wsl.onboarding.openDistroOnce": "Ouvrez {{distro}} une fois pour terminer la configuration.",
"wsl.onboarding.finishingDistro": "Finalisation de la configuration de {{distro}}.",
"wsl.onboarding.pickDistro": "Choisissez une distribution ou installez-en une ci-dessous.",
"wsl.onboarding.checkingOpencode": "Vérification d'OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Vérification d'OpenCode dans {{distro}}...",
"wsl.onboarding.updatingOpencode": "Mise à jour d'OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Mise à jour d'OpenCode dans {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Mettez à jour OpenCode dans {{distro}}.",
"wsl.onboarding.updateOpencode": "Mettre à jour OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode est prêt dans {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode est prêt.",
"wsl.onboarding.installOpencodeIn": "Installez OpenCode dans {{distro}}.",
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Choisissez d'abord une distribution.",
"wsl.onboarding.loadFailed": "Impossible de charger l'état de WSL.",
"wsl.onboarding.loading": "Chargement...",
"wsl.onboarding.installedDistros": "Distributions installées",
"wsl.onboarding.checkAgain": "Vérifier à nouveau",
"wsl.onboarding.distroStatus.ready": "Prête",
"wsl.onboarding.distroStatus.checking": "Vérification...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode non installé",
"wsl.onboarding.distroStatus.missingTools": "bash et curl manquants",
"wsl.onboarding.distroStatus.unsupported": "Non prise en charge · Utilisez WSL 2",
"wsl.onboarding.needAnotherDistro": "Besoin d'une autre distribution ?",
"wsl.onboarding.needAnotherDistroHint": "Installez une distribution Linux depuis le catalogue WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL n'est pas installé",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) est requis avant qu'OpenCode puisse ajouter un serveur WSL",
"wsl.onboarding.wslUnavailable.title": "WSL indisponible",
"wsl.onboarding.wslUnavailable.description": "OpenCode n'a pas pu vérifier WSL sur cet appareil.",
"wsl.onboarding.installWsl": "Installer WSL",
"wsl.onboarding.windowsRestartRequired":
"Redémarrez Windows pour terminer l'installation de WSL, puis rouvrez OpenCode.",
"wsl.onboarding.next": "Suivant",
"wsl.onboarding.refresh": "Actualiser",
"wsl.onboarding.allDistrosAdded": "Toutes les distributions installées ont déjà été ajoutées.",
"wsl.onboarding.noDistros": "Aucune distribution détectée pour l'instant.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installation...",
"wsl.onboarding.installDistro": "Installer une distribution",
"wsl.onboarding.searchDistros": "Rechercher des distributions",
"wsl.onboarding.wsl2Required": "WSL 2 est requis.",
"wsl.onboarding.toolsRequired": "Cette distribution nécessite bash et curl.",
"wsl.onboarding.openTerminal": "Ouvrir le terminal",
"wsl.onboarding.path": "Chemin : {{path}}",
"wsl.onboarding.notFound": "introuvable",
"wsl.onboarding.version": "Version : {{version}}",
"wsl.onboarding.unknown": "inconnue",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "La version installée ne correspond pas à celle de l'application desktop.",
"wsl.onboarding.adding": "Ajout...",
"dialog.project.edit.title": "Modifier le projet", "dialog.project.edit.title": "Modifier le projet",
"dialog.project.edit.name": "Nom", "dialog.project.edit.name": "Nom",
"dialog.project.edit.icon": "Icône", "dialog.project.edit.icon": "Icône",
@ -373,6 +454,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Langue", "toast.language.title": "Langue",
@ -410,10 +492,12 @@ export const dict = {
"toast.update.action.notYet": "Pas encore", "toast.update.action.notYet": "Pas encore",
"error.page.title": "Quelque chose s'est mal passé", "error.page.title": "Quelque chose s'est mal passé",
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.", "error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
"error.page.description.localServerStartup": "Une erreur s'est produite lors du démarrage du serveur local.",
"error.page.details.label": "Détails de l'erreur", "error.page.details.label": "Détails de l'erreur",
"error.page.action.restart": "Redémarrer", "error.page.action.restart": "Redémarrer",
"error.page.action.report": "Signaler l'erreur", "error.page.action.report": "Signaler l'erreur",
"error.page.action.reported": "Erreur signalée", "error.page.action.reported": "Erreur signalée",
"error.page.action.exportLogs": "Exporter les journaux",
"error.page.action.checking": "Vérification...", "error.page.action.checking": "Vérification...",
"error.page.action.checkUpdates": "Vérifier les mises à jour", "error.page.action.checkUpdates": "Vérifier les mises à jour",
"error.page.action.updateTo": "Mettre à jour vers {{version}}", "error.page.action.updateTo": "Mettre à jour vers {{version}}",
@ -458,16 +542,38 @@ export const dict = {
"home.recentProjects": "Projets récents", "home.recentProjects": "Projets récents",
"home.empty.title": "Aucun projet récent", "home.empty.title": "Aucun projet récent",
"home.empty.description": "Commencez par ouvrir un projet local", "home.empty.description": "Commencez par ouvrir un projet local",
"home.title": "Accueil",
"home.projects": "Projets",
"home.project.add": "Ajouter un projet",
"home.recentlyClosed": "Récemment fermés",
"home.server.collapse": "Réduire les projets du serveur",
"home.server.expand": "Développer les projets du serveur",
"home.sessions.search.placeholder": "Rechercher des sessions",
"home.sessions.search.placeholder.scoped": "Rechercher les sessions dans {{scope}}",
"home.sessions.search.sessions": "Sessions",
"home.sessions.search.noResults": "Aucune session trouvée pour {{query}}",
"home.sessions.empty": "Rien ici pour l'instant",
"home.sessions.empty.description": "Créez une session pour commencer",
"home.sessions.group.today": "Aujourd'hui",
"home.sessions.group.yesterday": "Hier",
"home.sessions.group.older": "Plus anciennes",
"session.tab.session": "Session", "session.tab.session": "Session",
"session.tab.review": "Revue", "session.tab.review": "Revue",
"session.tab.context": "Contexte", "session.tab.context": "Contexte",
"session.tab.unknown": "Session inconnue",
"session.panel.reviewAndFiles": "Revue et fichiers", "session.panel.reviewAndFiles": "Revue et fichiers",
"session.error.notFound": "Cette session est introuvable",
"session.error.notFound.description": "Cet onglet pointe vers une session qui n'existe plus sur ce serveur.",
"session.error.notFound.closeTab": "Fermer l'onglet",
"session.error.serverConnection": "Impossible de se connecter à ce serveur",
"session.review.filesChanged": "{{count}} fichiers modifiés", "session.review.filesChanged": "{{count}} fichiers modifiés",
"session.review.change.one": "Modification", "session.review.change.one": "Modification",
"session.review.change.other": "Modifications", "session.review.change.other": "Modifications",
"session.review.loadingChanges": "Chargement des modifications...", "session.review.loadingChanges": "Chargement des modifications...",
"session.review.empty": "Aucune modification dans cette session pour l'instant", "session.review.empty": "Aucune modification dans cette session pour l'instant",
"session.review.noChanges": "Aucune modification", "session.review.noChanges": "Aucune modification",
"session.review.noUncommittedChanges": "Aucune modification non validée pour l'instant",
"session.review.noBranchChanges": "Aucune modification de branche pour l'instant",
"session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées", "session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées",
"session.review.noSnapshot": "session.review.noSnapshot":
"Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles", "Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles",
@ -484,6 +590,10 @@ export const dict = {
"session.todo.title": "Tâches", "session.todo.title": "Tâches",
"session.todo.collapse": "Réduire", "session.todo.collapse": "Réduire",
"session.todo.expand": "Développer", "session.todo.expand": "Développer",
"session.question.minimize": "Réduire la question",
"session.question.restore": "Restaurer la question",
"session.question.pending.one": "{{count}} question en attente",
"session.question.pending.other": "{{count}} questions en attente",
"session.followupDock.summary.one": "{{count}} message en file d'attente", "session.followupDock.summary.one": "{{count}} message en file d'attente",
"session.followupDock.summary.other": "{{count}} messages en file d'attente", "session.followupDock.summary.other": "{{count}} messages en file d'attente",
"session.followupDock.sendNow": "Envoyer maintenant", "session.followupDock.sendNow": "Envoyer maintenant",
@ -496,9 +606,16 @@ export const dict = {
"session.revertDock.expand": "Développer les messages annulés", "session.revertDock.expand": "Développer les messages annulés",
"session.revertDock.restore": "Restaurer le message", "session.revertDock.restore": "Restaurer le message",
"session.new.title": "Créez ce que vous voulez", "session.new.title": "Créez ce que vous voulez",
"session.new.project.new": "Nouveau projet",
"session.new.project.search": "Rechercher des projets",
"session.new.project.add": "Ajouter un projet",
"session.new.worktree.main": "Branche principale", "session.new.worktree.main": "Branche principale",
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})", "session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
"session.new.worktree.create": "Créer un nouvel arbre de travail", "session.new.worktree.create": "Créer un nouvel arbre de travail",
"session.new.workspace.runIn": "Exécuter la session dans",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Dépôt local",
"session.new.workspace.existing": "Espace de travail…",
"session.new.lastModified": "Dernière modification", "session.new.lastModified": "Dernière modification",
"session.header.search.placeholder": "Rechercher {{project}}", "session.header.search.placeholder": "Rechercher {{project}}",
"session.header.searchFiles": "Rechercher des fichiers", "session.header.searchFiles": "Rechercher des fichiers",
@ -564,6 +681,8 @@ export const dict = {
"sidebar.project.recentSessions": "Sessions récentes", "sidebar.project.recentSessions": "Sessions récentes",
"sidebar.project.viewAllSessions": "Voir toutes les sessions", "sidebar.project.viewAllSessions": "Voir toutes les sessions",
"sidebar.project.clearNotifications": "Effacer les notifications", "sidebar.project.clearNotifications": "Effacer les notifications",
"sidebar.empty.title": "Aucun projet ouvert",
"sidebar.empty.description": "Ouvrez un projet pour commencer",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Bureau", "settings.section.desktop": "Bureau",
"settings.section.server": "Serveur", "settings.section.server": "Serveur",
@ -573,6 +692,7 @@ export const dict = {
"settings.desktop.wsl.title": "Intégration WSL", "settings.desktop.wsl.title": "Intégration WSL",
"settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.", "settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.",
"settings.general.section.appearance": "Apparence", "settings.general.section.appearance": "Apparence",
"settings.general.section.advanced": "Avancé",
"settings.general.section.notifications": "Notifications système", "settings.general.section.notifications": "Notifications système",
"settings.general.section.updates": "Mises à jour", "settings.general.section.updates": "Mises à jour",
"settings.general.section.sounds": "Effets sonores", "settings.general.section.sounds": "Effets sonores",
@ -580,6 +700,11 @@ export const dict = {
"settings.general.section.display": "Affichage", "settings.general.section.display": "Affichage",
"settings.general.row.language.title": "Langue", "settings.general.row.language.title": "Langue",
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
"settings.general.row.shell.title": "Shell du terminal",
"settings.general.row.shell.description":
"Choisissez le shell utilisé par votre terminal. Les shells compatibles sont également utilisés pour les appels d'outils de l'agent.",
"settings.general.row.shell.autoDefault": "Auto (par défaut)",
"settings.general.row.shell.terminalOnly": "terminal uniquement",
"settings.general.row.appearance.title": "Apparence", "settings.general.row.appearance.title": "Apparence",
"settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil", "settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
"settings.general.row.colorScheme.title": "Schéma de couleurs", "settings.general.row.colorScheme.title": "Schéma de couleurs",
@ -588,8 +713,8 @@ export const dict = {
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.", "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
"settings.general.row.font.title": "Police de code", "settings.general.row.font.title": "Police de code",
"settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code", "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Police du terminal",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Personnalisez la police utilisée dans le terminal",
"settings.general.row.uiFont.title": "Police de l'interface", "settings.general.row.uiFont.title": "Police de l'interface",
"settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface", "settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
"settings.general.row.followup.title": "Comportement de suivi", "settings.general.row.followup.title": "Comportement de suivi",
@ -597,6 +722,24 @@ export const dict = {
"Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente", "Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
"settings.general.row.followup.option.queue": "File d'attente", "settings.general.row.followup.option.queue": "File d'attente",
"settings.general.row.followup.option.steer": "Diriger", "settings.general.row.followup.option.steer": "Diriger",
"settings.general.row.showFileTree.title": "Arborescence des fichiers",
"settings.general.row.showFileTree.description": "Afficher le panneau d'arborescence des fichiers dans les sessions",
"settings.general.row.showNavigation.title": "Commandes de navigation",
"settings.general.row.showNavigation.description":
"Afficher les boutons précédent et suivant dans la barre de titre de l'application desktop",
"settings.general.row.showSearch.title": "Palette de commandes",
"settings.general.row.showSearch.description":
"Afficher le bouton de recherche et de palette de commandes dans la barre de titre",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description":
"Afficher le bouton du terminal dans la barre de titre de l'application desktop",
"settings.general.row.showStatus.title": "État du serveur",
"settings.general.row.showStatus.description": "Afficher le bouton d'état du serveur dans la barre de titre",
"settings.general.row.mobileTitlebarBottom.title": "Navigation en bas",
"settings.general.row.mobileTitlebarBottom.description":
"Placer la barre de titre et les onglets de session en bas de l'écran sur mobile",
"settings.general.row.showCustomAgents.title": "Agents personnalisés",
"settings.general.row.showCustomAgents.description": "Afficher le sélecteur d'agent dans la zone de saisie",
"settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement", "settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement",
"settings.general.row.reasoningSummaries.description": "settings.general.row.reasoningSummaries.description":
"Afficher les résumés de raisonnement du modèle dans la chronologie", "Afficher les résumés de raisonnement du modèle dans la chronologie",
@ -606,6 +749,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit", "settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie", "Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
"settings.general.row.newLayoutDesigns.title": "Nouvelle mise en page et nouveaux designs",
"settings.general.row.newLayoutDesigns.description":
"Activer la nouvelle mise en page et les nouvelles interfaces d'accueil, de saisie et de session",
"settings.general.row.pinchZoom.title": "Pincer pour zoomer",
"settings.general.row.pinchZoom.description":
"Autoriser les gestes de pincement du pavé tactile et Ctrl-défilement pour zoomer",
"settings.general.row.wayland.title": "Utiliser Wayland natif", "settings.general.row.wayland.title": "Utiliser Wayland natif",
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.", "settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -618,6 +767,8 @@ export const dict = {
"settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible", "settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible",
"settings.updates.action.checkNow": "Vérifier maintenant", "settings.updates.action.checkNow": "Vérifier maintenant",
"settings.updates.action.checking": "Vérification...", "settings.updates.action.checking": "Vérification...",
"settings.updates.action.downloading": "Téléchargement...",
"settings.updates.action.installing": "Installation...",
"settings.updates.toast.latest.title": "Vous êtes à jour", "settings.updates.toast.latest.title": "Vous êtes à jour",
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.", "settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
"sound.option.none": "Aucun", "sound.option.none": "Aucun",

View file

@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "ダーク", "theme.scheme.dark": "ダーク",
"command.sidebar.toggle": "サイドバーの切り替え", "command.sidebar.toggle": "サイドバーの切り替え",
"command.project.open": "プロジェクトを開く", "command.project.open": "プロジェクトを開く",
"command.project.previous": "前のプロジェクト",
"command.project.next": "次のプロジェクト",
"command.project.index": "プロジェクト{{index}}に切り替え",
"command.provider.connect": "プロバイダーに接続", "command.provider.connect": "プロバイダーに接続",
"command.server.switch": "サーバーの切り替え", "command.server.switch": "サーバーの切り替え",
"command.settings.open": "設定を開く", "command.settings.open": "設定を開く",
@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "新しいセッション", "command.session.new": "新しいセッション",
"command.file.open": "ファイルを開く", "command.file.open": "ファイルを開く",
"command.tab.close": "タブを閉じる", "command.tab.close": "タブを閉じる",
"command.tab.reopenClosed": "閉じたタブを再度開く",
"command.context.addSelection": "選択範囲をコンテキストに追加", "command.context.addSelection": "選択範囲をコンテキストに追加",
"command.context.addSelection.description": "現在のファイルから選択した行を追加", "command.context.addSelection.description": "現在のファイルから選択した行を追加",
"command.input.focus": "入力欄にフォーカス", "command.input.focus": "入力欄にフォーカス",
@ -149,7 +153,7 @@ export const dict = {
"provider.custom.field.providerID.placeholder": "myprovider", "provider.custom.field.providerID.placeholder": "myprovider",
"provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア", "provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア",
"provider.custom.field.name.label": "表示名", "provider.custom.field.name.label": "表示名",
"provider.custom.field.name.placeholder": "My AI Provider", "provider.custom.field.name.placeholder": "マイAIプロバイダー",
"provider.custom.field.baseURL.label": "ベースURL", "provider.custom.field.baseURL.label": "ベースURL",
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
"provider.custom.field.apiKey.label": "APIキー", "provider.custom.field.apiKey.label": "APIキー",
@ -192,6 +196,11 @@ export const dict = {
"model.input.video": "動画", "model.input.video": "動画",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "対応: {{inputs}}", "model.tooltip.allows": "対応: {{inputs}}",
"model.tooltip.context.label": "コンテキスト",
"model.tooltip.inputs": "入力",
"model.tooltip.model": "モデル",
"model.tooltip.provider": "プロバイダー",
"model.tooltip.reasoning": "推論",
"model.tooltip.reasoning.allowed": "推論を許可", "model.tooltip.reasoning.allowed": "推論を許可",
"model.tooltip.reasoning.none": "推論なし", "model.tooltip.reasoning.none": "推論なし",
"model.tooltip.context": "コンテキスト上限 {{limit}}", "model.tooltip.context": "コンテキスト上限 {{limit}}",
@ -201,6 +210,7 @@ export const dict = {
"common.loading": "読み込み中", "common.loading": "読み込み中",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "キャンセル", "common.cancel": "キャンセル",
"common.clear": "クリア",
"common.connect": "接続", "common.connect": "接続",
"common.disconnect": "切断", "common.disconnect": "切断",
"common.continue": "送信", "common.continue": "送信",
@ -217,6 +227,8 @@ export const dict = {
"prompt.mode.shell": "シェル", "prompt.mode.shell": "シェル",
"prompt.mode.normal": "プロンプト", "prompt.mode.normal": "プロンプト",
"prompt.mode.shell.exit": "escで終了", "prompt.mode.shell.exit": "escで終了",
"session.child.promptDisabled": "サブエージェントのセッションにはプロンプトを送信できません。",
"session.child.backToParent": "メインセッションに戻る。",
"prompt.example.1": "コードベースのTODOを修正", "prompt.example.1": "コードベースのTODOを修正",
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?", "prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
"prompt.example.3": "壊れたテストを修正", "prompt.example.3": "壊れたテストを修正",
@ -313,6 +325,75 @@ export const dict = {
"dialog.server.menu.delete": "削除", "dialog.server.menu.delete": "削除",
"dialog.server.current": "現在のサーバー", "dialog.server.current": "現在のサーバー",
"dialog.server.status.default": "デフォルト", "dialog.server.status.default": "デフォルト",
"wsl.server.add": "WSLサーバーを追加",
"wsl.server.addShort": "WSLを追加",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSLサーバー",
"wsl.server.retryStart": "起動を再試行",
"wsl.server.updating": "更新中...",
"wsl.onboarding.step.distro": "ディストリビューションを選択",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSLを確認中...",
"wsl.onboarding.restartRequired": "WSLのインストールを完了するにはWindowsの再起動が必要です。",
"wsl.onboarding.ready": "WSLの準備ができました。",
"wsl.onboarding.required": "続行するにはWSLが必要です。",
"wsl.onboarding.checkingDistros": "ディストリビューションを確認中...",
"wsl.onboarding.installingDistro": "{{distro}}をインストール中...",
"wsl.onboarding.checkingDistro": "{{distro}}を確認中...",
"wsl.onboarding.listingDistros": "ディストリビューションの一覧を取得中...",
"wsl.onboarding.distroReady": "{{distro}}の準備ができました。",
"wsl.onboarding.distroNotInstalled": "{{distro}}はまだインストールされていません。",
"wsl.onboarding.openDistroOnce": "セットアップを完了するには、{{distro}}を一度開いてください。",
"wsl.onboarding.finishingDistro": "{{distro}}のセットアップを完了しています。",
"wsl.onboarding.pickDistro": "ディストリビューションを選択するか、以下からインストールしてください。",
"wsl.onboarding.checkingOpencode": "OpenCodeを確認中...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}内のOpenCodeを確認中...",
"wsl.onboarding.updatingOpencode": "OpenCodeを更新中...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}内のOpenCodeを更新中...",
"wsl.onboarding.updateOpencodeIn": "{{distro}}内のOpenCodeを更新してください。",
"wsl.onboarding.updateOpencode": "OpenCodeを更新",
"wsl.onboarding.opencodeReadyIn": "{{distro}}内のOpenCodeの準備ができました。",
"wsl.onboarding.opencodeReady": "OpenCodeの準備ができました。",
"wsl.onboarding.installOpencodeIn": "{{distro}}にOpenCodeをインストールしてください。",
"wsl.onboarding.installOpencode": "OpenCodeをインストール",
"wsl.onboarding.chooseDistroFirst": "先にディストリビューションを選択してください。",
"wsl.onboarding.loadFailed": "WSLの状態を読み込めませんでした。",
"wsl.onboarding.loading": "読み込み中...",
"wsl.onboarding.installedDistros": "インストール済みのディストリビューション",
"wsl.onboarding.checkAgain": "再確認",
"wsl.onboarding.distroStatus.ready": "準備完了",
"wsl.onboarding.distroStatus.checking": "確認中...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCodeがインストールされていません",
"wsl.onboarding.distroStatus.missingTools": "bash、curlがありません",
"wsl.onboarding.distroStatus.unsupported": "非対応 · WSL 2を使用してください",
"wsl.onboarding.needAnotherDistro": "別のディストリビューションが必要ですか?",
"wsl.onboarding.needAnotherDistroHint": "WSLカタログからLinuxディストリビューションをインストール",
"wsl.onboarding.wslNotInstalled.title": "WSLがインストールされていません",
"wsl.onboarding.wslNotInstalled.description":
"OpenCodeでWSLサーバーを追加するには、WSL (Windows Subsystem for Linux) が必要です",
"wsl.onboarding.wslUnavailable.title": "WSLを利用できません",
"wsl.onboarding.wslUnavailable.description": "このマシンのWSLを確認できませんでした。",
"wsl.onboarding.installWsl": "WSLをインストール",
"wsl.onboarding.windowsRestartRequired":
"WSLのインストールを完了するにはWindowsを再起動し、OpenCodeをもう一度開いてください。",
"wsl.onboarding.next": "次へ",
"wsl.onboarding.refresh": "更新",
"wsl.onboarding.allDistrosAdded": "インストール済みのディストリビューションはすべて追加済みです。",
"wsl.onboarding.noDistros": "ディストリビューションはまだ検出されていません。",
"wsl.onboarding.install": "インストール",
"wsl.onboarding.installing": "インストール中...",
"wsl.onboarding.installDistro": "ディストリビューションをインストール",
"wsl.onboarding.searchDistros": "ディストリビューションを検索",
"wsl.onboarding.wsl2Required": "WSL 2が必要です。",
"wsl.onboarding.toolsRequired": "このディストリビューションにはbashとcurlが必要です。",
"wsl.onboarding.openTerminal": "ターミナルを開く",
"wsl.onboarding.path": "パス: {{path}}",
"wsl.onboarding.notFound": "見つかりません",
"wsl.onboarding.version": "バージョン: {{version}}",
"wsl.onboarding.unknown": "不明",
"wsl.onboarding.desktopVersion": "デスクトップ {{version}}",
"wsl.onboarding.versionMismatch": "インストールされているバージョンがデスクトップアプリのバージョンと一致しません。",
"wsl.onboarding.adding": "追加中...",
"dialog.project.edit.title": "プロジェクトを編集", "dialog.project.edit.title": "プロジェクトを編集",
"dialog.project.edit.name": "名前", "dialog.project.edit.name": "名前",
"dialog.project.edit.icon": "アイコン", "dialog.project.edit.icon": "アイコン",
@ -370,6 +451,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "言語", "toast.language.title": "言語",
@ -406,10 +488,12 @@ export const dict = {
"toast.update.action.notYet": "今はしない", "toast.update.action.notYet": "今はしない",
"error.page.title": "問題が発生しました", "error.page.title": "問題が発生しました",
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。", "error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
"error.page.description.localServerStartup": "ローカルサーバーの起動中にエラーが発生しました。",
"error.page.details.label": "エラー詳細", "error.page.details.label": "エラー詳細",
"error.page.action.restart": "再起動", "error.page.action.restart": "再起動",
"error.page.action.report": "エラーを報告", "error.page.action.report": "エラーを報告",
"error.page.action.reported": "エラーを報告しました", "error.page.action.reported": "エラーを報告しました",
"error.page.action.exportLogs": "ログをエクスポート",
"error.page.action.checking": "確認中...", "error.page.action.checking": "確認中...",
"error.page.action.checkUpdates": "アップデートを確認", "error.page.action.checkUpdates": "アップデートを確認",
"error.page.action.updateTo": "{{version}}にアップデート", "error.page.action.updateTo": "{{version}}にアップデート",
@ -451,10 +535,30 @@ export const dict = {
"home.recentProjects": "最近のプロジェクト", "home.recentProjects": "最近のプロジェクト",
"home.empty.title": "最近のプロジェクトはありません", "home.empty.title": "最近のプロジェクトはありません",
"home.empty.description": "ローカルプロジェクトを開いて始めましょう", "home.empty.description": "ローカルプロジェクトを開いて始めましょう",
"home.title": "ホーム",
"home.projects": "プロジェクト",
"home.project.add": "プロジェクトを追加",
"home.recentlyClosed": "最近閉じたプロジェクト",
"home.server.collapse": "サーバーのプロジェクトを折りたたむ",
"home.server.expand": "サーバーのプロジェクトを展開",
"home.sessions.search.placeholder": "セッションを検索",
"home.sessions.search.placeholder.scoped": "{{scope}}のセッションを検索",
"home.sessions.search.sessions": "セッション",
"home.sessions.search.noResults": "{{query}}に一致するセッションが見つかりません",
"home.sessions.empty": "まだ何もありません",
"home.sessions.empty.description": "セッションを作成して始めましょう",
"home.sessions.group.today": "今日",
"home.sessions.group.yesterday": "昨日",
"home.sessions.group.older": "それ以前",
"session.tab.session": "セッション", "session.tab.session": "セッション",
"session.tab.review": "レビュー", "session.tab.review": "レビュー",
"session.tab.context": "コンテキスト", "session.tab.context": "コンテキスト",
"session.tab.unknown": "不明なセッション",
"session.panel.reviewAndFiles": "レビューとファイル", "session.panel.reviewAndFiles": "レビューとファイル",
"session.error.notFound": "このセッションが見つかりません",
"session.error.notFound.description": "このタブが参照するセッションは、このサーバーには存在しません。",
"session.error.notFound.closeTab": "タブを閉じる",
"session.error.serverConnection": "このサーバーに接続できません",
"session.review.filesChanged": "{{count}} ファイル変更", "session.review.filesChanged": "{{count}} ファイル変更",
"session.review.change.one": "変更", "session.review.change.one": "変更",
"session.review.change.other": "変更", "session.review.change.other": "変更",
@ -463,6 +567,8 @@ export const dict = {
"session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません", "session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません",
"session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません", "session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません",
"session.review.noChanges": "変更なし", "session.review.noChanges": "変更なし",
"session.review.noUncommittedChanges": "コミットされていない変更はまだありません",
"session.review.noBranchChanges": "ブランチの変更はまだありません",
"session.files.selectToOpen": "開くファイルを選択", "session.files.selectToOpen": "開くファイルを選択",
"session.files.all": "すべてのファイル", "session.files.all": "すべてのファイル",
"session.files.empty": "ファイルなし", "session.files.empty": "ファイルなし",
@ -476,6 +582,10 @@ export const dict = {
"session.todo.title": "ToDo", "session.todo.title": "ToDo",
"session.todo.collapse": "折りたたむ", "session.todo.collapse": "折りたたむ",
"session.todo.expand": "展開", "session.todo.expand": "展開",
"session.question.minimize": "質問を最小化",
"session.question.restore": "質問を復元",
"session.question.pending.one": "{{count}}件の保留中の質問",
"session.question.pending.other": "{{count}}件の保留中の質問",
"session.followupDock.summary.one": "{{count}} 件のメッセージが待機中", "session.followupDock.summary.one": "{{count}} 件のメッセージが待機中",
"session.followupDock.summary.other": "{{count}} 件のメッセージが待機中", "session.followupDock.summary.other": "{{count}} 件のメッセージが待機中",
"session.followupDock.sendNow": "今すぐ送信", "session.followupDock.sendNow": "今すぐ送信",
@ -488,9 +598,16 @@ export const dict = {
"session.revertDock.expand": "ロールバックされたメッセージを展開", "session.revertDock.expand": "ロールバックされたメッセージを展開",
"session.revertDock.restore": "メッセージを復元", "session.revertDock.restore": "メッセージを復元",
"session.new.title": "何でも作る", "session.new.title": "何でも作る",
"session.new.project.new": "新しいプロジェクト",
"session.new.project.search": "プロジェクトを検索",
"session.new.project.add": "プロジェクトを追加",
"session.new.worktree.main": "メインブランチ", "session.new.worktree.main": "メインブランチ",
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})", "session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
"session.new.worktree.create": "新しいワークツリーを作成", "session.new.worktree.create": "新しいワークツリーを作成",
"session.new.workspace.runIn": "セッションの実行先",
"session.new.workspace.triggerLocal": "ローカル",
"session.new.workspace.local": "ローカルリポジトリ",
"session.new.workspace.existing": "ワークスペース…",
"session.new.lastModified": "最終更新", "session.new.lastModified": "最終更新",
"session.header.search.placeholder": "{{project}}を検索", "session.header.search.placeholder": "{{project}}を検索",
"session.header.searchFiles": "ファイルを検索", "session.header.searchFiles": "ファイルを検索",
@ -554,6 +671,8 @@ export const dict = {
"sidebar.project.recentSessions": "最近のセッション", "sidebar.project.recentSessions": "最近のセッション",
"sidebar.project.viewAllSessions": "すべてのセッションを表示", "sidebar.project.viewAllSessions": "すべてのセッションを表示",
"sidebar.project.clearNotifications": "通知をクリア", "sidebar.project.clearNotifications": "通知をクリア",
"sidebar.empty.title": "開いているプロジェクトはありません",
"sidebar.empty.description": "プロジェクトを開いて始めましょう",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "デスクトップ", "settings.section.desktop": "デスクトップ",
"settings.section.server": "サーバー", "settings.section.server": "サーバー",
@ -563,6 +682,7 @@ export const dict = {
"settings.desktop.wsl.title": "WSL連携", "settings.desktop.wsl.title": "WSL連携",
"settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。", "settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。",
"settings.general.section.appearance": "外観", "settings.general.section.appearance": "外観",
"settings.general.section.advanced": "詳細設定",
"settings.general.section.notifications": "システム通知", "settings.general.section.notifications": "システム通知",
"settings.general.section.updates": "アップデート", "settings.general.section.updates": "アップデート",
"settings.general.section.sounds": "効果音", "settings.general.section.sounds": "効果音",
@ -570,6 +690,11 @@ export const dict = {
"settings.general.section.display": "ディスプレイ", "settings.general.section.display": "ディスプレイ",
"settings.general.row.language.title": "言語", "settings.general.row.language.title": "言語",
"settings.general.row.language.description": "OpenCodeの表示言語を変更します", "settings.general.row.language.description": "OpenCodeの表示言語を変更します",
"settings.general.row.shell.title": "ターミナルシェル",
"settings.general.row.shell.description":
"ターミナルで使用するシェルを選択します。互換性のあるシェルはエージェントのツール呼び出しにも使用されます。",
"settings.general.row.shell.autoDefault": "自動 (デフォルト)",
"settings.general.row.shell.terminalOnly": "ターミナルのみ",
"settings.general.row.appearance.title": "外観", "settings.general.row.appearance.title": "外観",
"settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします", "settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします",
"settings.general.row.colorScheme.title": "配色", "settings.general.row.colorScheme.title": "配色",
@ -578,8 +703,8 @@ export const dict = {
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。", "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
"settings.general.row.font.title": "コードフォント", "settings.general.row.font.title": "コードフォント",
"settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします", "settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "ターミナルのフォント",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "ターミナルで使用するフォントをカスタマイズ",
"settings.general.row.uiFont.title": "UIフォント", "settings.general.row.uiFont.title": "UIフォント",
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします", "settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
"settings.general.row.followup.title": "フォローアップの動作", "settings.general.row.followup.title": "フォローアップの動作",
@ -587,6 +712,21 @@ export const dict = {
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します", "フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
"settings.general.row.followup.option.queue": "キューに追加", "settings.general.row.followup.option.queue": "キューに追加",
"settings.general.row.followup.option.steer": "即座に実行 (Steer)", "settings.general.row.followup.option.steer": "即座に実行 (Steer)",
"settings.general.row.showFileTree.title": "ファイルツリー",
"settings.general.row.showFileTree.description": "セッションにファイルツリーパネルを表示します",
"settings.general.row.showNavigation.title": "ナビゲーションコントロール",
"settings.general.row.showNavigation.description": "デスクトップのタイトルバーに戻るボタンと進むボタンを表示します",
"settings.general.row.showSearch.title": "コマンドパレット",
"settings.general.row.showSearch.description": "タイトルバーに検索とコマンドパレットのボタンを表示します",
"settings.general.row.showTerminal.title": "ターミナル",
"settings.general.row.showTerminal.description": "デスクトップのタイトルバーにターミナルボタンを表示します",
"settings.general.row.showStatus.title": "サーバーステータス",
"settings.general.row.showStatus.description": "タイトルバーにサーバーステータスボタンを表示します",
"settings.general.row.mobileTitlebarBottom.title": "下部ナビゲーション",
"settings.general.row.mobileTitlebarBottom.description":
"モバイルではタイトルバーとセッションタブを画面下部に配置します",
"settings.general.row.showCustomAgents.title": "カスタムエージェント",
"settings.general.row.showCustomAgents.description": "コンポーザーにエージェント選択を表示します",
"settings.general.row.reasoningSummaries.title": "推論の要約を表示", "settings.general.row.reasoningSummaries.title": "推論の要約を表示",
"settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します", "settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します",
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開", "settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
@ -595,6 +735,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開", "settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します", "タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
"settings.general.row.newLayoutDesigns.title": "新しいレイアウトとデザイン",
"settings.general.row.newLayoutDesigns.description":
"刷新されたレイアウト、ホーム、コンポーザー、セッションUIを有効にします",
"settings.general.row.pinchZoom.title": "ピンチでズーム",
"settings.general.row.pinchZoom.description":
"トラックパッドのピンチ操作とCtrl+スクロール操作によるズームを許可します",
"settings.general.row.wayland.title": "ネイティブWaylandを使用", "settings.general.row.wayland.title": "ネイティブWaylandを使用",
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。", "settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -607,6 +753,8 @@ export const dict = {
"settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします", "settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします",
"settings.updates.action.checkNow": "今すぐ確認", "settings.updates.action.checkNow": "今すぐ確認",
"settings.updates.action.checking": "確認中...", "settings.updates.action.checking": "確認中...",
"settings.updates.action.downloading": "ダウンロード中...",
"settings.updates.action.installing": "インストール中...",
"settings.updates.toast.latest.title": "最新です", "settings.updates.toast.latest.title": "最新です",
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。", "settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
"sound.option.none": "なし", "sound.option.none": "なし",

View file

@ -575,8 +575,8 @@ export const dict = {
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정", "settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
"settings.general.row.font.title": "코드 글꼴", "settings.general.row.font.title": "코드 글꼴",
"settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정", "settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "터미널 글꼴",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "터미널에서 사용할 글꼴을 설정합니다",
"settings.general.row.uiFont.title": "UI 글꼴", "settings.general.row.uiFont.title": "UI 글꼴",
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정", "settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
"settings.general.row.followup.title": "후속 조치 동작", "settings.general.row.followup.title": "후속 조치 동작",
@ -848,4 +848,155 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패", "error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패",
"error.childStore.storeCreateFailed": "저장소 생성 실패", "error.childStore.storeCreateFailed": "저장소 생성 실패",
"terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}", "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}",
"command.project.previous": "이전 프로젝트",
"command.project.next": "다음 프로젝트",
"command.project.index": "{{index}}번 프로젝트로 전환",
"command.tab.reopenClosed": "닫은 탭 다시 열기",
"model.tooltip.context.label": "컨텍스트",
"model.tooltip.inputs": "입력",
"model.tooltip.model": "모델",
"model.tooltip.provider": "공급자",
"model.tooltip.reasoning": "추론",
"common.clear": "지우기",
"session.child.promptDisabled": "하위 에이전트 세션에는 프롬프트를 입력할 수 없습니다.",
"session.child.backToParent": "메인 세션으로 돌아가기.",
"wsl.server.add": "WSL 서버 추가",
"wsl.server.addShort": "WSL 추가",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL 서버",
"wsl.server.retryStart": "시작 재시도",
"wsl.server.updating": "업데이트 중...",
"wsl.onboarding.step.distro": "배포판 선택",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL 확인 중...",
"wsl.onboarding.restartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작해야 합니다.",
"wsl.onboarding.ready": "WSL이 준비되었습니다.",
"wsl.onboarding.required": "계속하려면 WSL이 필요합니다.",
"wsl.onboarding.checkingDistros": "배포판 확인 중...",
"wsl.onboarding.installingDistro": "{{distro}} 배포판 설치 중...",
"wsl.onboarding.checkingDistro": "{{distro}} 배포판 확인 중...",
"wsl.onboarding.listingDistros": "배포판 목록 확인 중...",
"wsl.onboarding.distroReady": "{{distro}} 배포판이 준비되었습니다.",
"wsl.onboarding.distroNotInstalled": "{{distro}} 배포판이 아직 설치되지 않았습니다.",
"wsl.onboarding.openDistroOnce": "설정을 완료하려면 {{distro}} 배포판을 한 번 여세요.",
"wsl.onboarding.finishingDistro": "{{distro}} 배포판 설정을 마무리하는 중입니다.",
"wsl.onboarding.pickDistro": "배포판을 선택하거나 아래에서 설치하세요.",
"wsl.onboarding.checkingOpencode": "OpenCode 확인 중...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}에서 OpenCode 확인 중...",
"wsl.onboarding.updatingOpencode": "OpenCode 업데이트 중...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}에서 OpenCode 업데이트 중...",
"wsl.onboarding.updateOpencodeIn": "{{distro}}에서 OpenCode를 업데이트하세요.",
"wsl.onboarding.updateOpencode": "OpenCode 업데이트",
"wsl.onboarding.opencodeReadyIn": "{{distro}}에서 OpenCode를 사용할 준비가 되었습니다.",
"wsl.onboarding.opencodeReady": "OpenCode가 준비되었습니다.",
"wsl.onboarding.installOpencodeIn": "{{distro}}에 OpenCode를 설치하세요.",
"wsl.onboarding.installOpencode": "OpenCode 설치",
"wsl.onboarding.chooseDistroFirst": "먼저 배포판을 선택하세요.",
"wsl.onboarding.loadFailed": "WSL 상태를 불러오지 못했습니다.",
"wsl.onboarding.loading": "로딩 중...",
"wsl.onboarding.installedDistros": "설치된 배포판",
"wsl.onboarding.checkAgain": "다시 확인",
"wsl.onboarding.distroStatus.ready": "준비됨",
"wsl.onboarding.distroStatus.checking": "확인 중...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode가 설치되지 않음",
"wsl.onboarding.distroStatus.missingTools": "bash, curl 없음",
"wsl.onboarding.distroStatus.unsupported": "지원되지 않음 · WSL 2 사용",
"wsl.onboarding.needAnotherDistro": "다른 배포판이 필요하신가요?",
"wsl.onboarding.needAnotherDistroHint": "WSL 카탈로그에서 Linux 배포판을 설치하세요",
"wsl.onboarding.wslNotInstalled.title": "WSL이 설치되지 않음",
"wsl.onboarding.wslNotInstalled.description":
"OpenCode에서 WSL 서버를 추가하려면 WSL (Windows Subsystem for Linux)이 필요합니다",
"wsl.onboarding.wslUnavailable.title": "WSL을 사용할 수 없음",
"wsl.onboarding.wslUnavailable.description": "OpenCode에서 이 컴퓨터의 WSL을 확인할 수 없습니다.",
"wsl.onboarding.installWsl": "WSL 설치",
"wsl.onboarding.windowsRestartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작한 다음 OpenCode를 다시 여세요.",
"wsl.onboarding.next": "다음",
"wsl.onboarding.refresh": "새로 고침",
"wsl.onboarding.allDistrosAdded": "설치된 모든 배포판이 이미 추가되었습니다.",
"wsl.onboarding.noDistros": "아직 감지된 배포판이 없습니다.",
"wsl.onboarding.install": "설치",
"wsl.onboarding.installing": "설치 중...",
"wsl.onboarding.installDistro": "배포판 설치",
"wsl.onboarding.searchDistros": "배포판 검색",
"wsl.onboarding.wsl2Required": "WSL 2가 필요합니다.",
"wsl.onboarding.toolsRequired": "이 배포판에는 bash와 curl이 필요합니다.",
"wsl.onboarding.openTerminal": "터미널 열기",
"wsl.onboarding.path": "경로: {{path}}",
"wsl.onboarding.notFound": "찾을 수 없음",
"wsl.onboarding.version": "버전: {{version}}",
"wsl.onboarding.unknown": "알 수 없음",
"wsl.onboarding.desktopVersion": "데스크톱 {{version}}",
"wsl.onboarding.versionMismatch": "설치된 버전이 데스크톱 앱 버전과 일치하지 않습니다.",
"wsl.onboarding.adding": "추가 중...",
"language.uk": "Українська",
"error.page.description.localServerStartup": "로컬 서버를 시작하는 동안 오류가 발생했습니다.",
"error.page.action.exportLogs": "로그 내보내기",
"home.title": "홈",
"home.projects": "프로젝트",
"home.project.add": "프로젝트 추가",
"home.recentlyClosed": "최근에 닫은 항목",
"home.server.collapse": "서버 프로젝트 접기",
"home.server.expand": "서버 프로젝트 펼치기",
"home.sessions.search.placeholder": "세션 검색",
"home.sessions.search.placeholder.scoped": "{{scope}}에서 세션 검색",
"home.sessions.search.sessions": "세션",
"home.sessions.search.noResults": "{{query}}에 해당하는 세션을 찾을 수 없습니다",
"home.sessions.empty": "아직 아무것도 없습니다",
"home.sessions.empty.description": "세션을 생성하여 시작하세요",
"home.sessions.group.today": "오늘",
"home.sessions.group.yesterday": "어제",
"home.sessions.group.older": "이전",
"session.tab.unknown": "알 수 없는 세션",
"session.error.notFound": "이 세션을 찾을 수 없습니다",
"session.error.notFound.description": "이 탭은 이 서버에 더 이상 존재하지 않는 세션을 가리킵니다.",
"session.error.notFound.closeTab": "탭 닫기",
"session.error.serverConnection": "이 서버에 연결할 수 없습니다",
"session.review.noUncommittedChanges": "아직 커밋되지 않은 변경 사항이 없습니다",
"session.review.noBranchChanges": "아직 브랜치 변경 사항이 없습니다",
"session.question.minimize": "질문 최소화",
"session.question.restore": "질문 복원",
"session.question.pending.one": "{{count}}개의 대기 중인 질문",
"session.question.pending.other": "{{count}}개의 대기 중인 질문",
"session.new.project.new": "새 프로젝트",
"session.new.project.search": "프로젝트 검색",
"session.new.project.add": "프로젝트 추가",
"session.new.workspace.runIn": "세션 실행 위치",
"session.new.workspace.triggerLocal": "로컬",
"session.new.workspace.local": "로컬 저장소",
"session.new.workspace.existing": "작업 공간…",
"sidebar.empty.title": "열린 프로젝트 없음",
"sidebar.empty.description": "프로젝트를 열어 시작하세요",
"settings.general.section.advanced": "고급",
"settings.general.row.shell.title": "터미널 셸",
"settings.general.row.shell.description":
"터미널에서 사용할 셸을 선택하세요. 호환되는 셸은 에이전트 도구 호출에도 사용됩니다.",
"settings.general.row.shell.autoDefault": "자동 (기본값)",
"settings.general.row.shell.terminalOnly": "터미널 전용",
"settings.general.row.showFileTree.title": "파일 트리",
"settings.general.row.showFileTree.description": "세션에 파일 트리 패널 표시",
"settings.general.row.showNavigation.title": "탐색 컨트롤",
"settings.general.row.showNavigation.description": "데스크톱 제목 표시줄에 뒤로 및 앞으로 버튼 표시",
"settings.general.row.showSearch.title": "명령 팔레트",
"settings.general.row.showSearch.description": "제목 표시줄에 검색 및 명령 팔레트 버튼 표시",
"settings.general.row.showTerminal.title": "터미널",
"settings.general.row.showTerminal.description": "데스크톱 제목 표시줄에 터미널 버튼 표시",
"settings.general.row.showStatus.title": "서버 상태",
"settings.general.row.showStatus.description": "제목 표시줄에 서버 상태 버튼 표시",
"settings.general.row.mobileTitlebarBottom.title": "하단 탐색",
"settings.general.row.mobileTitlebarBottom.description": "모바일에서 제목 표시줄과 세션 탭을 화면 하단에 배치",
"settings.general.row.showCustomAgents.title": "사용자 지정 에이전트",
"settings.general.row.showCustomAgents.description": "입력창에 에이전트 선택기 표시",
"settings.general.row.newLayoutDesigns.title": "새 레이아웃 및 디자인",
"settings.general.row.newLayoutDesigns.description": "새롭게 디자인된 레이아웃, 홈, 입력창 및 세션 UI 활성화",
"settings.general.row.pinchZoom.title": "핀치 줌",
"settings.general.row.pinchZoom.description": "트랙패드 핀치 및 Ctrl-스크롤 제스처로 확대/축소 허용",
"settings.updates.action.downloading": "다운로드 중...",
"settings.updates.action.installing": "설치 중...",
} }

View file

@ -77,7 +77,7 @@ export const dict = {
"command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk", "command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk",
"command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser", "command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser",
"command.workspace.toggle": "Veksle arbeidsområder", "command.workspace.toggle": "Veksle arbeidsområder",
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar", "command.workspace.toggle.description": "Aktiver eller deaktiver flere arbeidsområder i sidefeltet",
"command.session.undo": "Angre", "command.session.undo": "Angre",
"command.session.undo.description": "Angre siste melding", "command.session.undo.description": "Angre siste melding",
"command.session.redo": "Gjør om", "command.session.redo": "Gjør om",
@ -469,7 +469,7 @@ export const dict = {
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?", "Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?",
"error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?", "error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
"directory.error.invalidUrl": "Invalid directory in URL.", "directory.error.invalidUrl": "Ugyldig mappe i URL-en.",
"error.chain.unknown": "Ukjent feil", "error.chain.unknown": "Ukjent feil",
"error.chain.causedBy": "Forårsaket av:", "error.chain.causedBy": "Forårsaket av:",
@ -649,8 +649,8 @@ export const dict = {
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.", "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
"settings.general.row.font.title": "Kodefont", "settings.general.row.font.title": "Kodefont",
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker", "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminalskrift",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Tilpass skrifttypen som brukes i terminalen",
"settings.general.row.uiFont.title": "UI-skrift", "settings.general.row.uiFont.title": "UI-skrift",
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet", "settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
"settings.general.row.followup.title": "Oppfølgingsadferd", "settings.general.row.followup.title": "Oppfølgingsadferd",
@ -936,4 +936,163 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon", "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon",
"error.childStore.storeCreateFailed": "Kunne ikke opprette lager", "error.childStore.storeCreateFailed": "Kunne ikke opprette lager",
"terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}", "terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}",
"command.project.previous": "Forrige prosjekt",
"command.project.next": "Neste prosjekt",
"command.project.index": "Bytt til prosjekt {{index}}",
"command.tab.reopenClosed": "Åpne lukket fane på nytt",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Inndata",
"model.tooltip.model": "Modell",
"model.tooltip.provider": "Leverandør",
"model.tooltip.reasoning": "Resonnering",
"common.clear": "Tøm",
"session.child.promptDisabled": "Underagentsesjoner kan ikke motta forespørsler.",
"session.child.backToParent": "Tilbake til hovedsesjonen.",
"wsl.server.add": "Legg til WSL-server",
"wsl.server.addShort": "Legg til WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-server",
"wsl.server.retryStart": "Prøv å starte på nytt",
"wsl.server.updating": "Oppdaterer...",
"wsl.onboarding.step.distro": "Velg distribusjon",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Sjekker WSL...",
"wsl.onboarding.restartRequired": "Windows må startes på nytt for å fullføre installasjonen av WSL.",
"wsl.onboarding.ready": "WSL er klart.",
"wsl.onboarding.required": "WSL kreves for å fortsette.",
"wsl.onboarding.checkingDistros": "Sjekker distribusjoner...",
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
"wsl.onboarding.checkingDistro": "Sjekker {{distro}}...",
"wsl.onboarding.listingDistros": "Lister distribusjoner...",
"wsl.onboarding.distroReady": "{{distro}} er klar.",
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installert ennå.",
"wsl.onboarding.openDistroOnce": "Åpne {{distro}} én gang for å fullføre oppsettet.",
"wsl.onboarding.finishingDistro": "Fullfører oppsettet for {{distro}}.",
"wsl.onboarding.pickDistro": "Velg en distribusjon eller installer en nedenfor.",
"wsl.onboarding.checkingOpencode": "Sjekker OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Sjekker OpenCode i {{distro}}...",
"wsl.onboarding.updatingOpencode": "Oppdaterer OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Oppdaterer OpenCode i {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Oppdater OpenCode i {{distro}}.",
"wsl.onboarding.updateOpencode": "Oppdater OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode er klar.",
"wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.",
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Velg en distribusjon først.",
"wsl.onboarding.loadFailed": "Kunne ikke laste inn WSL-status.",
"wsl.onboarding.loading": "Laster...",
"wsl.onboarding.installedDistros": "Installerte distribusjoner",
"wsl.onboarding.checkAgain": "Sjekk på nytt",
"wsl.onboarding.distroStatus.ready": "Klar",
"wsl.onboarding.distroStatus.checking": "Sjekker...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installert",
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Ikke støttet · Bruk WSL 2",
"wsl.onboarding.needAnotherDistro": "Trenger du en annen distribusjon?",
"wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribusjon fra WSL-katalogen",
"wsl.onboarding.wslNotInstalled.title": "WSL er ikke installert",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) kreves før OpenCode kan legge til en WSL-server",
"wsl.onboarding.wslUnavailable.title": "WSL er utilgjengelig",
"wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekrefte WSL på denne maskinen.",
"wsl.onboarding.installWsl": "Installer WSL",
"wsl.onboarding.windowsRestartRequired":
"Start Windows på nytt for å fullføre installasjonen av WSL, og åpne deretter OpenCode på nytt.",
"wsl.onboarding.next": "Neste",
"wsl.onboarding.refresh": "Oppdater",
"wsl.onboarding.allDistrosAdded": "Alle installerte distribusjoner er allerede lagt til.",
"wsl.onboarding.noDistros": "Ingen distribusjoner oppdaget ennå.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installerer...",
"wsl.onboarding.installDistro": "Installer distribusjon",
"wsl.onboarding.searchDistros": "Søk etter distribusjoner",
"wsl.onboarding.wsl2Required": "WSL 2 er påkrevd.",
"wsl.onboarding.toolsRequired": "Denne distribusjonen trenger bash og curl.",
"wsl.onboarding.openTerminal": "Åpne terminal",
"wsl.onboarding.path": "Bane: {{path}}",
"wsl.onboarding.notFound": "ikke funnet",
"wsl.onboarding.version": "Versjon: {{version}}",
"wsl.onboarding.unknown": "ukjent",
"wsl.onboarding.desktopVersion": "skrivebordsapp {{version}}",
"wsl.onboarding.versionMismatch": "Den installerte versjonen samsvarer ikke med versjonen av skrivebordsappen.",
"wsl.onboarding.adding": "Legger til...",
"language.uk": "Українська",
"error.page.description.localServerStartup": "Det oppstod en feil under oppstart av den lokale serveren.",
"error.page.action.exportLogs": "Eksporter logger",
"home.title": "Hjem",
"home.projects": "Prosjekter",
"home.project.add": "Legg til prosjekt",
"home.recentlyClosed": "Nylig lukket",
"home.server.collapse": "Fold sammen serverprosjekter",
"home.server.expand": "Utvid serverprosjekter",
"home.sessions.search.placeholder": "Søk etter sesjoner",
"home.sessions.search.placeholder.scoped": "Søk etter sesjoner i {{scope}}",
"home.sessions.search.sessions": "Sesjoner",
"home.sessions.search.noResults": "Ingen sesjoner funnet for {{query}}",
"home.sessions.empty": "Ingenting her ennå",
"home.sessions.empty.description": "Opprett en sesjon for å komme i gang",
"home.sessions.group.today": "I dag",
"home.sessions.group.yesterday": "I går",
"home.sessions.group.older": "Eldre",
"session.tab.unknown": "Ukjent sesjon",
"session.error.notFound": "Denne sesjonen finnes ikke",
"session.error.notFound.description": "Denne fanen peker til en sesjon som ikke lenger finnes på denne serveren.",
"session.error.notFound.closeTab": "Lukk fane",
"session.error.serverConnection": "Kan ikke koble til denne serveren",
"session.review.noUncommittedChanges": "Ingen ucommittede endringer ennå",
"session.review.noBranchChanges": "Ingen grenendringer ennå",
"session.question.minimize": "Minimer spørsmål",
"session.question.restore": "Gjenopprett spørsmål",
"session.question.pending.one": "{{count}} ventende spørsmål",
"session.question.pending.other": "{{count}} ventende spørsmål",
"session.new.project.new": "Nytt prosjekt",
"session.new.project.search": "Søk etter prosjekter",
"session.new.project.add": "Legg til prosjekt",
"session.new.workspace.runIn": "Kjør sesjonen i",
"session.new.workspace.triggerLocal": "Lokalt",
"session.new.workspace.local": "Lokalt depot",
"session.new.workspace.existing": "Arbeidsområde…",
"sidebar.empty.title": "Ingen åpne prosjekter",
"sidebar.empty.description": "Åpne et prosjekt for å komme i gang",
"settings.general.section.advanced": "Avansert",
"settings.general.row.shell.title": "Terminalskall",
"settings.general.row.shell.description":
"Velg skallet som skal brukes i terminalen. Kompatible skall brukes også til agentens verktøykall.",
"settings.general.row.shell.autoDefault": "Automatisk (standard)",
"settings.general.row.shell.terminalOnly": "kun terminal",
"settings.general.row.showFileTree.title": "Filtre",
"settings.general.row.showFileTree.description": "Vis filtrepanelet i sesjoner",
"settings.general.row.showNavigation.title": "Navigasjonskontroller",
"settings.general.row.showNavigation.description":
"Vis tilbake- og fremoverknappene på tittellinjen i skrivebordsappen",
"settings.general.row.showSearch.title": "Kommandopalett",
"settings.general.row.showSearch.description": "Vis knappen for søk og kommandopalett på tittellinjen",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Vis terminalknappen på tittellinjen i skrivebordsappen",
"settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Vis serverstatusknappen på tittellinjen",
"settings.general.row.mobileTitlebarBottom.title": "Navigasjon nederst",
"settings.general.row.mobileTitlebarBottom.description":
"Plasser tittellinjen og sesjonsfanene nederst på mobilskjermen",
"settings.general.row.showCustomAgents.title": "Egendefinerte agenter",
"settings.general.row.showCustomAgents.description": "Vis agentvelgeren i skrivefeltet",
"settings.general.row.newLayoutDesigns.title": "Ny layout og utforming",
"settings.general.row.newLayoutDesigns.description":
"Aktiver ny layout og utforming for startsiden, skrivefeltet og sesjonsgrensesnittet",
"settings.general.row.pinchZoom.title": "Knip for å zoome",
"settings.general.row.pinchZoom.description": "Tillat knipebevegelser på styreflaten og Ctrl-rulling for å zoome",
"settings.updates.action.downloading": "Laster ned...",
"settings.updates.action.installing": "Installerer...",
} satisfies Partial<Record<Keys, string>> } satisfies Partial<Record<Keys, string>>

View file

@ -1,33 +1,118 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { dict as en } from "./en"
import { dict as ar } from "./ar"
import { dict as br } from "./br"
import { dict as bs } from "./bs"
import { dict as da } from "./da"
import { dict as de } from "./de"
import { dict as es } from "./es"
import { dict as fr } from "./fr"
import { dict as ja } from "./ja"
import { dict as ko } from "./ko"
import { dict as no } from "./no"
import { dict as pl } from "./pl"
import { dict as ru } from "./ru"
import { dict as uk } from "./uk"
import { dict as th } from "./th"
import { dict as zh } from "./zh"
import { dict as zht } from "./zht"
import { dict as tr } from "./tr"
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, uk, th, tr, zh, zht] const appLocales = [
const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const "ar",
"br",
"bs",
"da",
"de",
"es",
"fr",
"ja",
"ko",
"no",
"pl",
"ru",
"uk",
"th",
"tr",
"zh",
"zht",
] as const
const desktopLocales = appLocales.filter((locale) => locale !== "th" && locale !== "tr")
const domains = [
{
name: "app",
source: "./en.ts",
target: (locale: string) => `./${locale}.ts`,
locales: appLocales,
},
{
name: "ui",
source: "../../../ui/src/i18n/en.ts",
target: (locale: string) => `../../../ui/src/i18n/${locale}.ts`,
locales: appLocales,
},
{
name: "desktop",
source: "../../../desktop/src/renderer/i18n/en.ts",
target: (locale: string) => `../../../desktop/src/renderer/i18n/${locale}.ts`,
locales: desktopLocales,
},
] as const
describe("i18n parity", () => { describe("i18n parity", () => {
test("non-English locales translate targeted unseen session keys", () => { test("non-English locales have every English key", async () => {
for (const locale of locales) { for (const domain of domains) {
for (const key of keys) { const source = await dictionary(domain.source)
expect(locale[key]).toBeDefined() for (const locale of domain.locales) {
expect(locale[key]).not.toBe(en[key]) const target = await dictionary(domain.target(locale))
const missing = Object.keys(source).filter((key) => !Object.hasOwn(target, key))
const extra = Object.keys(target).filter((key) => !Object.hasOwn(source, key))
expect({ domain: domain.name, locale, missing, extra }).toEqual({
domain: domain.name,
locale,
missing: [],
extra: [],
})
}
}
})
test("non-English locales preserve English placeholders", async () => {
for (const domain of domains) {
const source = await dictionary(domain.source)
for (const locale of domain.locales) {
const target = await dictionary(domain.target(locale))
const mismatched = Object.keys(source).filter(
(key) => Object.hasOwn(target, key) && placeholders(source[key]).join() !== placeholders(target[key]).join(),
)
expect({ domain: domain.name, locale, mismatched }).toEqual({ domain: domain.name, locale, mismatched: [] })
}
}
})
test("non-English locales translate targeted unseen session keys", async () => {
const source = await dictionary("./en.ts")
for (const locale of appLocales) {
const target = await dictionary(`./${locale}.ts`)
for (const key of ["command.session.previous.unseen", "command.session.next.unseen"]) {
expect(target[key]).toBeDefined()
expect(target[key]).not.toBe(source[key])
}
}
})
test("changed-file summary keys preserve rendered English copy and localize complete phrases", async () => {
const source = await dictionary("../../../ui/src/i18n/en.ts")
expect(source["ui.sessionTurn.diffs.changed.one"].replace("{{count}}", "1")).toBe("1 Changed file")
expect(source["ui.sessionTurn.diffs.changed.other"].replace("{{count}}", "2")).toBe("2 Changed files")
expect(source["ui.sessionTurn.diffs.changed"]).toBeUndefined()
for (const locale of appLocales) {
const target = await dictionary(`../../../ui/src/i18n/${locale}.ts`)
for (const key of ["ui.sessionTurn.diffs.changed.one", "ui.sessionTurn.diffs.changed.other"]) {
expect(target[key].trim()).not.toBe("")
expect(placeholders(target[key])).toEqual(["count"])
} }
} }
}) })
}) })
async function dictionary(file: string) {
const module: unknown = await import(file)
if (typeof module !== "object" || module === null || !("dict" in module) || !isDictionary(module.dict)) {
throw new Error(`Invalid translation dictionary: ${file}`)
}
return module.dict
}
function isDictionary(value: unknown): value is Record<string, string> {
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
return Object.values(value).every((item) => typeof item === "string")
}
function placeholders(value: string) {
return Array.from(value.matchAll(/{{\s*([^}]+?)\s*}}/g), (match) => match[1]).sort()
}

View file

@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "Ciemny", "theme.scheme.dark": "Ciemny",
"command.sidebar.toggle": "Przełącz pasek boczny", "command.sidebar.toggle": "Przełącz pasek boczny",
"command.project.open": "Otwórz projekt", "command.project.open": "Otwórz projekt",
"command.project.previous": "Poprzedni projekt",
"command.project.next": "Następny projekt",
"command.project.index": "Przełącz na projekt {{index}}",
"command.provider.connect": "Połącz dostawcę", "command.provider.connect": "Połącz dostawcę",
"command.server.switch": "Przełącz serwer", "command.server.switch": "Przełącz serwer",
"command.settings.open": "Otwórz ustawienia", "command.settings.open": "Otwórz ustawienia",
@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "Nowa sesja", "command.session.new": "Nowa sesja",
"command.file.open": "Otwórz plik", "command.file.open": "Otwórz plik",
"command.tab.close": "Zamknij kartę", "command.tab.close": "Zamknij kartę",
"command.tab.reopenClosed": "Otwórz ponownie zamkniętą kartę",
"command.context.addSelection": "Dodaj zaznaczenie do kontekstu", "command.context.addSelection": "Dodaj zaznaczenie do kontekstu",
"command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku", "command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku",
"command.input.focus": "Fokus na pole wejściowe", "command.input.focus": "Fokus na pole wejściowe",
@ -194,6 +198,11 @@ export const dict = {
"model.input.video": "wideo", "model.input.video": "wideo",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Obsługuje: {{inputs}}", "model.tooltip.allows": "Obsługuje: {{inputs}}",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Dane wejściowe",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Dostawca",
"model.tooltip.reasoning": "Wnioskowanie",
"model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie", "model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie",
"model.tooltip.reasoning.none": "Brak wnioskowania", "model.tooltip.reasoning.none": "Brak wnioskowania",
"model.tooltip.context": "Limit kontekstu {{limit}}", "model.tooltip.context": "Limit kontekstu {{limit}}",
@ -203,6 +212,7 @@ export const dict = {
"common.loading": "Ładowanie", "common.loading": "Ładowanie",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Anuluj", "common.cancel": "Anuluj",
"common.clear": "Wyczyść",
"common.connect": "Połącz", "common.connect": "Połącz",
"common.disconnect": "Rozłącz", "common.disconnect": "Rozłącz",
"common.continue": "Prześlij", "common.continue": "Prześlij",
@ -219,6 +229,8 @@ export const dict = {
"prompt.mode.shell": "Terminal", "prompt.mode.shell": "Terminal",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc aby wyjść", "prompt.mode.shell.exit": "esc aby wyjść",
"session.child.promptDisabled": "Nie można wysyłać promptów w sesjach podagentów.",
"session.child.backToParent": "Wróć do głównej sesji.",
"prompt.example.1": "Napraw TODO w bazie kodu", "prompt.example.1": "Napraw TODO w bazie kodu",
"prompt.example.2": "Jaki jest stos technologiczny tego projektu?", "prompt.example.2": "Jaki jest stos technologiczny tego projektu?",
"prompt.example.3": "Napraw zepsute testy", "prompt.example.3": "Napraw zepsute testy",
@ -315,6 +327,75 @@ export const dict = {
"dialog.server.menu.delete": "Usuń", "dialog.server.menu.delete": "Usuń",
"dialog.server.current": "Obecny serwer", "dialog.server.current": "Obecny serwer",
"dialog.server.status.default": "Domyślny", "dialog.server.status.default": "Domyślny",
"wsl.server.add": "Dodaj serwer WSL",
"wsl.server.addShort": "Dodaj WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Serwer WSL",
"wsl.server.retryStart": "Ponów uruchamianie",
"wsl.server.updating": "Aktualizowanie...",
"wsl.onboarding.step.distro": "Wybierz dystrybucję",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Sprawdzanie WSL...",
"wsl.onboarding.restartRequired": "Aby dokończyć instalację WSL, uruchom ponownie system Windows.",
"wsl.onboarding.ready": "WSL jest gotowy.",
"wsl.onboarding.required": "Aby kontynuować, wymagany jest WSL.",
"wsl.onboarding.checkingDistros": "Sprawdzanie dystrybucji...",
"wsl.onboarding.installingDistro": "Instalowanie {{distro}}...",
"wsl.onboarding.checkingDistro": "Sprawdzanie {{distro}}...",
"wsl.onboarding.listingDistros": "Pobieranie listy dystrybucji...",
"wsl.onboarding.distroReady": "Dystrybucja {{distro}} jest gotowa.",
"wsl.onboarding.distroNotInstalled": "Dystrybucja {{distro}} nie jest jeszcze zainstalowana.",
"wsl.onboarding.openDistroOnce": "Uruchom raz {{distro}}, aby dokończyć konfigurację.",
"wsl.onboarding.finishingDistro": "Kończenie konfiguracji {{distro}}.",
"wsl.onboarding.pickDistro": "Wybierz dystrybucję lub zainstaluj jedną z poniższych.",
"wsl.onboarding.checkingOpencode": "Sprawdzanie OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Sprawdzanie OpenCode w dystrybucji {{distro}}...",
"wsl.onboarding.updatingOpencode": "Aktualizowanie OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Aktualizowanie OpenCode w dystrybucji {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Zaktualizuj OpenCode w dystrybucji {{distro}}.",
"wsl.onboarding.updateOpencode": "Zaktualizuj OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode jest gotowy w dystrybucji {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode jest gotowy.",
"wsl.onboarding.installOpencodeIn": "Zainstaluj OpenCode w dystrybucji {{distro}}.",
"wsl.onboarding.installOpencode": "Zainstaluj OpenCode",
"wsl.onboarding.chooseDistroFirst": "Najpierw wybierz dystrybucję.",
"wsl.onboarding.loadFailed": "Nie udało się załadować stanu WSL.",
"wsl.onboarding.loading": "Ładowanie...",
"wsl.onboarding.installedDistros": "Zainstalowane dystrybucje",
"wsl.onboarding.checkAgain": "Sprawdź ponownie",
"wsl.onboarding.distroStatus.ready": "Gotowa",
"wsl.onboarding.distroStatus.checking": "Sprawdzanie...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nie jest zainstalowany",
"wsl.onboarding.distroStatus.missingTools": "Brak bash i curl",
"wsl.onboarding.distroStatus.unsupported": "Nieobsługiwana · Użyj WSL 2",
"wsl.onboarding.needAnotherDistro": "Potrzebujesz innej dystrybucji?",
"wsl.onboarding.needAnotherDistroHint": "Zainstaluj dystrybucję systemu Linux z katalogu WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL nie jest zainstalowany",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Podsystem Windows dla systemu Linux) jest wymagany, zanim OpenCode będzie mógł dodać serwer WSL",
"wsl.onboarding.wslUnavailable.title": "WSL jest niedostępny",
"wsl.onboarding.wslUnavailable.description": "OpenCode nie mógł zweryfikować WSL na tym komputerze.",
"wsl.onboarding.installWsl": "Zainstaluj WSL",
"wsl.onboarding.windowsRestartRequired":
"Uruchom ponownie system Windows, aby dokończyć instalację WSL, a następnie ponownie otwórz OpenCode.",
"wsl.onboarding.next": "Dalej",
"wsl.onboarding.refresh": "Odśwież",
"wsl.onboarding.allDistrosAdded": "Wszystkie zainstalowane dystrybucje zostały już dodane.",
"wsl.onboarding.noDistros": "Nie wykryto jeszcze żadnych dystrybucji.",
"wsl.onboarding.install": "Zainstaluj",
"wsl.onboarding.installing": "Instalowanie...",
"wsl.onboarding.installDistro": "Zainstaluj dystrybucję",
"wsl.onboarding.searchDistros": "Szukaj dystrybucji",
"wsl.onboarding.wsl2Required": "Wymagany jest WSL 2.",
"wsl.onboarding.toolsRequired": "Ta dystrybucja wymaga bash i curl.",
"wsl.onboarding.openTerminal": "Otwórz terminal",
"wsl.onboarding.path": "Ścieżka: {{path}}",
"wsl.onboarding.notFound": "nie znaleziono",
"wsl.onboarding.version": "Wersja: {{version}}",
"wsl.onboarding.unknown": "nieznana",
"wsl.onboarding.desktopVersion": "aplikacja komputerowa {{version}}",
"wsl.onboarding.versionMismatch": "Zainstalowana wersja nie jest zgodna z wersją aplikacji komputerowej.",
"wsl.onboarding.adding": "Dodawanie...",
"dialog.project.edit.title": "Edytuj projekt", "dialog.project.edit.title": "Edytuj projekt",
"dialog.project.edit.name": "Nazwa", "dialog.project.edit.name": "Nazwa",
"dialog.project.edit.icon": "Ikona", "dialog.project.edit.icon": "Ikona",
@ -324,7 +405,8 @@ export const dict = {
"dialog.project.edit.color": "Kolor", "dialog.project.edit.color": "Kolor",
"dialog.project.edit.color.select": "Wybierz kolor {{color}}", "dialog.project.edit.color.select": "Wybierz kolor {{color}}",
"dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej", "dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej",
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).", "dialog.project.edit.worktree.startup.description":
"Uruchamiany po utworzeniu nowej przestrzeni roboczej (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "np. bun install", "dialog.project.edit.worktree.startup.placeholder": "np. bun install",
"context.breakdown.title": "Podział kontekstu", "context.breakdown.title": "Podział kontekstu",
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.', "context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
@ -371,6 +453,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Język", "toast.language.title": "Język",
@ -407,10 +490,12 @@ export const dict = {
"toast.update.action.notYet": "Jeszcze nie", "toast.update.action.notYet": "Jeszcze nie",
"error.page.title": "Coś poszło nie tak", "error.page.title": "Coś poszło nie tak",
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.", "error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
"error.page.description.localServerStartup": "Wystąpił błąd podczas uruchamiania serwera lokalnego.",
"error.page.details.label": "Szczegóły błędu", "error.page.details.label": "Szczegóły błędu",
"error.page.action.restart": "Restartuj", "error.page.action.restart": "Restartuj",
"error.page.action.report": "Zgłoś błąd", "error.page.action.report": "Zgłoś błąd",
"error.page.action.reported": "Błąd zgłoszony", "error.page.action.reported": "Błąd zgłoszony",
"error.page.action.exportLogs": "Eksportuj dzienniki",
"error.page.action.checking": "Sprawdzanie...", "error.page.action.checking": "Sprawdzanie...",
"error.page.action.checkUpdates": "Sprawdź aktualizacje", "error.page.action.checkUpdates": "Sprawdź aktualizacje",
"error.page.action.updateTo": "Zaktualizuj do {{version}}", "error.page.action.updateTo": "Zaktualizuj do {{version}}",
@ -430,7 +515,8 @@ export const dict = {
"error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}", "error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}",
"error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}", "error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli", "error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.', "error.chain.mcpFailed":
"Serwer MCP „{{name}}” nie uruchomił się. Uwaga: OpenCode nie obsługuje jeszcze uwierzytelniania MCP.",
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}", "error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
"error.chain.providerInitFailed": "error.chain.providerInitFailed":
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.', 'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
@ -452,10 +538,30 @@ export const dict = {
"home.recentProjects": "Ostatnie projekty", "home.recentProjects": "Ostatnie projekty",
"home.empty.title": "Brak ostatnich projektów", "home.empty.title": "Brak ostatnich projektów",
"home.empty.description": "Zacznij od otwarcia lokalnego projektu", "home.empty.description": "Zacznij od otwarcia lokalnego projektu",
"home.title": "Strona główna",
"home.projects": "Projekty",
"home.project.add": "Dodaj projekt",
"home.recentlyClosed": "Ostatnio zamknięte",
"home.server.collapse": "Zwiń projekty serwera",
"home.server.expand": "Rozwiń projekty serwera",
"home.sessions.search.placeholder": "Szukaj sesji",
"home.sessions.search.placeholder.scoped": "Szukaj sesji w {{scope}}",
"home.sessions.search.sessions": "Sesje",
"home.sessions.search.noResults": "Nie znaleziono sesji dla {{query}}",
"home.sessions.empty": "Jeszcze nic tu nie ma",
"home.sessions.empty.description": "Utwórz sesję, aby rozpocząć",
"home.sessions.group.today": "Dzisiaj",
"home.sessions.group.yesterday": "Wczoraj",
"home.sessions.group.older": "Starsze",
"session.tab.session": "Sesja", "session.tab.session": "Sesja",
"session.tab.review": "Przegląd", "session.tab.review": "Przegląd",
"session.tab.context": "Kontekst", "session.tab.context": "Kontekst",
"session.tab.unknown": "Nieznana sesja",
"session.panel.reviewAndFiles": "Przegląd i pliki", "session.panel.reviewAndFiles": "Przegląd i pliki",
"session.error.notFound": "Nie można znaleźć tej sesji",
"session.error.notFound.description": "Ta karta wskazuje sesję, która nie istnieje już na tym serwerze.",
"session.error.notFound.closeTab": "Zamknij kartę",
"session.error.serverConnection": "Nie można połączyć się z tym serwerem",
"session.review.filesChanged": "Zmieniono {{count}} plików", "session.review.filesChanged": "Zmieniono {{count}} plików",
"session.review.change.one": "Zmiana", "session.review.change.one": "Zmiana",
"session.review.change.other": "Zmiany", "session.review.change.other": "Zmiany",
@ -464,6 +570,8 @@ export const dict = {
"session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane", "session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane",
"session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne", "session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne",
"session.review.noChanges": "Brak zmian", "session.review.noChanges": "Brak zmian",
"session.review.noUncommittedChanges": "Brak jeszcze niezatwierdzonych zmian",
"session.review.noBranchChanges": "Brak jeszcze zmian w gałęzi",
"session.files.selectToOpen": "Wybierz plik do otwarcia", "session.files.selectToOpen": "Wybierz plik do otwarcia",
"session.files.all": "Wszystkie pliki", "session.files.all": "Wszystkie pliki",
"session.files.empty": "Brak plików", "session.files.empty": "Brak plików",
@ -477,6 +585,10 @@ export const dict = {
"session.todo.title": "Zadania", "session.todo.title": "Zadania",
"session.todo.collapse": "Zwiń", "session.todo.collapse": "Zwiń",
"session.todo.expand": "Rozwiń", "session.todo.expand": "Rozwiń",
"session.question.minimize": "Zminimalizuj pytanie",
"session.question.restore": "Przywróć pytanie",
"session.question.pending.one": "{{count}} oczekujące pytanie",
"session.question.pending.other": "Pytania oczekujące: {{count}}",
"session.followupDock.summary.one": "{{count}} wiadomość w kolejce", "session.followupDock.summary.one": "{{count}} wiadomość w kolejce",
"session.followupDock.summary.other": "{{count}} wiadomości w kolejce", "session.followupDock.summary.other": "{{count}} wiadomości w kolejce",
"session.followupDock.sendNow": "Wyślij teraz", "session.followupDock.sendNow": "Wyślij teraz",
@ -489,9 +601,16 @@ export const dict = {
"session.revertDock.expand": "Rozwiń cofnięte wiadomości", "session.revertDock.expand": "Rozwiń cofnięte wiadomości",
"session.revertDock.restore": "Przywróć wiadomość", "session.revertDock.restore": "Przywróć wiadomość",
"session.new.title": "Zbuduj cokolwiek", "session.new.title": "Zbuduj cokolwiek",
"session.new.project.new": "Nowy projekt",
"session.new.project.search": "Szukaj projektów",
"session.new.project.add": "Dodaj projekt",
"session.new.worktree.main": "Główna gałąź", "session.new.worktree.main": "Główna gałąź",
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})", "session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
"session.new.worktree.create": "Utwórz nowe drzewo robocze", "session.new.worktree.create": "Utwórz nowe drzewo robocze",
"session.new.workspace.runIn": "Uruchom sesję w",
"session.new.workspace.triggerLocal": "Lokalnie",
"session.new.workspace.local": "Lokalne repozytorium",
"session.new.workspace.existing": "Przestrzeń robocza…",
"session.new.lastModified": "Ostatnio zmodyfikowano", "session.new.lastModified": "Ostatnio zmodyfikowano",
"session.header.search.placeholder": "Szukaj {{project}}", "session.header.search.placeholder": "Szukaj {{project}}",
"session.header.searchFiles": "Szukaj plików", "session.header.searchFiles": "Szukaj plików",
@ -555,15 +674,18 @@ export const dict = {
"sidebar.project.recentSessions": "Ostatnie sesje", "sidebar.project.recentSessions": "Ostatnie sesje",
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje", "sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
"sidebar.project.clearNotifications": "Wyczyść powiadomienia", "sidebar.project.clearNotifications": "Wyczyść powiadomienia",
"sidebar.empty.title": "Brak otwartych projektów",
"sidebar.empty.description": "Otwórz projekt, aby rozpocząć",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Pulpit", "settings.section.desktop": "Pulpit",
"settings.section.server": "Serwer", "settings.section.server": "Serwer",
"settings.tab.general": "Ogólne", "settings.tab.general": "Ogólne",
"settings.tab.shortcuts": "Skróty", "settings.tab.shortcuts": "Skróty",
"settings.desktop.section.wsl": "WSL", "settings.desktop.section.wsl": "WSL",
"settings.desktop.wsl.title": "WSL integration", "settings.desktop.wsl.title": "Integracja z WSL",
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.", "settings.desktop.wsl.description": "Uruchamiaj serwer OpenCode wewnątrz WSL w systemie Windows.",
"settings.general.section.appearance": "Wygląd", "settings.general.section.appearance": "Wygląd",
"settings.general.section.advanced": "Zaawansowane",
"settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.notifications": "Powiadomienia systemowe",
"settings.general.section.updates": "Aktualizacje", "settings.general.section.updates": "Aktualizacje",
"settings.general.section.sounds": "Efekty dźwiękowe", "settings.general.section.sounds": "Efekty dźwiękowe",
@ -571,6 +693,11 @@ export const dict = {
"settings.general.section.display": "Ekran", "settings.general.section.display": "Ekran",
"settings.general.row.language.title": "Język", "settings.general.row.language.title": "Język",
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
"settings.general.row.shell.title": "Powłoka terminala",
"settings.general.row.shell.description":
"Wybierz powłokę używaną w terminalu. Zgodne powłoki są również używane przez wywołania narzędzi agenta.",
"settings.general.row.shell.autoDefault": "Automatycznie (domyślna)",
"settings.general.row.shell.terminalOnly": "tylko terminal",
"settings.general.row.appearance.title": "Wygląd", "settings.general.row.appearance.title": "Wygląd",
"settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu", "settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
"settings.general.row.colorScheme.title": "Schemat kolorów", "settings.general.row.colorScheme.title": "Schemat kolorów",
@ -580,14 +707,30 @@ export const dict = {
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.", "settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
"settings.general.row.font.title": "Czcionka kodu", "settings.general.row.font.title": "Czcionka kodu",
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu", "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Czcionka terminala",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Dostosuj czcionkę używaną w terminalu",
"settings.general.row.uiFont.title": "Czcionka interfejsu", "settings.general.row.uiFont.title": "Czcionka interfejsu",
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie", "settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
"settings.general.row.followup.title": "Zachowanie kontynuacji", "settings.general.row.followup.title": "Zachowanie kontynuacji",
"settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce", "settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
"settings.general.row.followup.option.queue": "Kolejka", "settings.general.row.followup.option.queue": "Kolejka",
"settings.general.row.followup.option.steer": "Sterowanie", "settings.general.row.followup.option.steer": "Sterowanie",
"settings.general.row.showFileTree.title": "Drzewo plików",
"settings.general.row.showFileTree.description": "Pokaż panel drzewa plików w sesjach",
"settings.general.row.showNavigation.title": "Elementy nawigacji",
"settings.general.row.showNavigation.description":
"Pokaż przyciski Wstecz i Dalej na pasku tytułu aplikacji komputerowej",
"settings.general.row.showSearch.title": "Paleta poleceń",
"settings.general.row.showSearch.description": "Pokaż przycisk wyszukiwania i palety poleceń na pasku tytułu",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Pokaż przycisk terminala na pasku tytułu aplikacji komputerowej",
"settings.general.row.showStatus.title": "Status serwera",
"settings.general.row.showStatus.description": "Pokaż przycisk statusu serwera na pasku tytułu",
"settings.general.row.mobileTitlebarBottom.title": "Dolna nawigacja",
"settings.general.row.mobileTitlebarBottom.description":
"Umieść pasek tytułu i karty sesji u dołu ekranu na urządzeniach mobilnych",
"settings.general.row.showCustomAgents.title": "Agenci niestandardowi",
"settings.general.row.showCustomAgents.description": "Pokaż wybór agenta w edytorze wiadomości",
"settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania", "settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania",
"settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu", "settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu",
"settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell", "settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
@ -596,6 +739,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit", "settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu", "Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
"settings.general.row.newLayoutDesigns.title": "Nowy układ i wygląd",
"settings.general.row.newLayoutDesigns.description":
"Włącz przeprojektowany układ, stronę główną, edytor wiadomości i interfejs sesji",
"settings.general.row.pinchZoom.title": "Powiększanie gestem szczypania",
"settings.general.row.pinchZoom.description":
"Zezwalaj na powiększanie gestem szczypania na gładziku i przewijaniem z klawiszem Ctrl",
"settings.general.row.wayland.title": "Użyj natywnego Wayland", "settings.general.row.wayland.title": "Użyj natywnego Wayland",
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.", "settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -608,6 +757,8 @@ export const dict = {
"settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne", "settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne",
"settings.updates.action.checkNow": "Sprawdź teraz", "settings.updates.action.checkNow": "Sprawdź teraz",
"settings.updates.action.checking": "Sprawdzanie...", "settings.updates.action.checking": "Sprawdzanie...",
"settings.updates.action.downloading": "Pobieranie...",
"settings.updates.action.installing": "Instalowanie...",
"settings.updates.toast.latest.title": "Masz najnowszą wersję", "settings.updates.toast.latest.title": "Masz najnowszą wersję",
"settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.", "settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.",
"sound.option.none": "Brak", "sound.option.none": "Brak",

View file

@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Переключить боковую панель", "command.sidebar.toggle": "Переключить боковую панель",
"command.project.open": "Открыть проект", "command.project.open": "Открыть проект",
"command.project.previous": "Предыдущий проект",
"command.project.next": "Следующий проект",
"command.project.index": "Переключиться на проект {{index}}",
"command.provider.connect": "Подключить провайдера", "command.provider.connect": "Подключить провайдера",
"command.server.switch": "Переключить сервер", "command.server.switch": "Переключить сервер",
"command.settings.open": "Открыть настройки", "command.settings.open": "Открыть настройки",
@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Новая сессия", "command.session.new": "Новая сессия",
"command.file.open": "Открыть файл", "command.file.open": "Открыть файл",
"command.tab.close": "Закрыть вкладку", "command.tab.close": "Закрыть вкладку",
"command.tab.reopenClosed": "Повторно открыть закрытую вкладку",
"command.context.addSelection": "Добавить выделение в контекст", "command.context.addSelection": "Добавить выделение в контекст",
"command.context.addSelection.description": "Добавить выбранные строки из текущего файла", "command.context.addSelection.description": "Добавить выбранные строки из текущего файла",
"command.input.focus": "Фокус на поле ввода", "command.input.focus": "Фокус на поле ввода",
@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "видео", "model.input.video": "видео",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Разрешено: {{inputs}}", "model.tooltip.allows": "Разрешено: {{inputs}}",
"model.tooltip.context.label": "Контекст",
"model.tooltip.inputs": "Входные данные",
"model.tooltip.model": "Модель",
"model.tooltip.provider": "Провайдер",
"model.tooltip.reasoning": "Рассуждение",
"model.tooltip.reasoning.allowed": "Разрешает рассуждение", "model.tooltip.reasoning.allowed": "Разрешает рассуждение",
"model.tooltip.reasoning.none": "Без рассуждения", "model.tooltip.reasoning.none": "Без рассуждения",
"model.tooltip.context": "Лимит контекста {{limit}}", "model.tooltip.context": "Лимит контекста {{limit}}",
@ -218,6 +227,7 @@ export const dict = {
"common.loading": "Загрузка", "common.loading": "Загрузка",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Отмена", "common.cancel": "Отмена",
"common.clear": "Очистить",
"common.connect": "Подключить", "common.connect": "Подключить",
"common.disconnect": "Отключить", "common.disconnect": "Отключить",
"common.continue": "Отправить", "common.continue": "Отправить",
@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "Оболочка", "prompt.mode.shell": "Оболочка",
"prompt.mode.normal": "Промпт", "prompt.mode.normal": "Промпт",
"prompt.mode.shell.exit": "esc для выхода", "prompt.mode.shell.exit": "esc для выхода",
"session.child.promptDisabled": "В сессиях субагентов нельзя отправлять запросы.",
"session.child.backToParent": "Вернуться к основной сессии.",
"prompt.example.1": "Исправить TODO в коде", "prompt.example.1": "Исправить TODO в коде",
"prompt.example.2": "Какой технологический стек этого проекта?", "prompt.example.2": "Какой технологический стек этого проекта?",
@ -341,6 +353,75 @@ export const dict = {
"dialog.server.menu.delete": "Удалить", "dialog.server.menu.delete": "Удалить",
"dialog.server.current": "Текущий сервер", "dialog.server.current": "Текущий сервер",
"dialog.server.status.default": "По умолч.", "dialog.server.status.default": "По умолч.",
"wsl.server.add": "Добавить сервер WSL",
"wsl.server.addShort": "Добавить WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Сервер WSL",
"wsl.server.retryStart": "Повторить запуск",
"wsl.server.updating": "Обновление...",
"wsl.onboarding.step.distro": "Выбрать дистрибутив",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Проверка WSL...",
"wsl.onboarding.restartRequired": "Чтобы завершить установку WSL, перезапустите Windows.",
"wsl.onboarding.ready": "WSL готова к работе.",
"wsl.onboarding.required": "Для продолжения требуется WSL.",
"wsl.onboarding.checkingDistros": "Проверка дистрибутивов...",
"wsl.onboarding.installingDistro": "Установка {{distro}}...",
"wsl.onboarding.checkingDistro": "Проверка {{distro}}...",
"wsl.onboarding.listingDistros": "Получение списка дистрибутивов...",
"wsl.onboarding.distroReady": "Дистрибутив {{distro}} готов к работе.",
"wsl.onboarding.distroNotInstalled": "Дистрибутив {{distro}} пока не установлен.",
"wsl.onboarding.openDistroOnce": "Откройте {{distro}} один раз, чтобы завершить настройку.",
"wsl.onboarding.finishingDistro": "Завершение настройки {{distro}}.",
"wsl.onboarding.pickDistro": "Выберите дистрибутив или установите его ниже.",
"wsl.onboarding.checkingOpencode": "Проверка OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Проверка OpenCode в {{distro}}...",
"wsl.onboarding.updatingOpencode": "Обновление OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Обновление OpenCode в {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Обновите OpenCode в {{distro}}.",
"wsl.onboarding.updateOpencode": "Обновить OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode в {{distro}} готов к работе.",
"wsl.onboarding.opencodeReady": "OpenCode готов к работе.",
"wsl.onboarding.installOpencodeIn": "Установите OpenCode в {{distro}}.",
"wsl.onboarding.installOpencode": "Установить OpenCode",
"wsl.onboarding.chooseDistroFirst": "Сначала выберите дистрибутив.",
"wsl.onboarding.loadFailed": "Не удалось загрузить состояние WSL.",
"wsl.onboarding.loading": "Загрузка...",
"wsl.onboarding.installedDistros": "Установленные дистрибутивы",
"wsl.onboarding.checkAgain": "Проверить снова",
"wsl.onboarding.distroStatus.ready": "Готов",
"wsl.onboarding.distroStatus.checking": "Проверка...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не установлен",
"wsl.onboarding.distroStatus.missingTools": "Отсутствуют bash и curl",
"wsl.onboarding.distroStatus.unsupported": "Не поддерживается · Используйте WSL 2",
"wsl.onboarding.needAnotherDistro": "Нужен другой дистрибутив?",
"wsl.onboarding.needAnotherDistroHint": "Установите дистрибутив Linux из каталога WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL не установлена",
"wsl.onboarding.wslNotInstalled.description":
"Для добавления сервера WSL в OpenCode требуется WSL (Windows Subsystem for Linux)",
"wsl.onboarding.wslUnavailable.title": "WSL недоступна",
"wsl.onboarding.wslUnavailable.description": "OpenCode не смог проверить WSL на этом компьютере.",
"wsl.onboarding.installWsl": "Установить WSL",
"wsl.onboarding.windowsRestartRequired":
"Перезапустите Windows, чтобы завершить установку WSL, затем снова откройте OpenCode.",
"wsl.onboarding.next": "Далее",
"wsl.onboarding.refresh": "Обновить",
"wsl.onboarding.allDistrosAdded": "Все установленные дистрибутивы уже добавлены.",
"wsl.onboarding.noDistros": "Дистрибутивы пока не обнаружены.",
"wsl.onboarding.install": "Установить",
"wsl.onboarding.installing": "Установка...",
"wsl.onboarding.installDistro": "Установить дистрибутив",
"wsl.onboarding.searchDistros": "Поиск дистрибутивов",
"wsl.onboarding.wsl2Required": "Требуется WSL 2.",
"wsl.onboarding.toolsRequired": "Для этого дистрибутива нужны bash и curl.",
"wsl.onboarding.openTerminal": "Открыть терминал",
"wsl.onboarding.path": "Путь: {{path}}",
"wsl.onboarding.notFound": "не найден",
"wsl.onboarding.version": "Версия: {{version}}",
"wsl.onboarding.unknown": "неизвестно",
"wsl.onboarding.desktopVersion": "приложение {{version}}",
"wsl.onboarding.versionMismatch": "Установленная версия не соответствует версии приложения.",
"wsl.onboarding.adding": "Добавление...",
"dialog.project.edit.title": "Редактировать проект", "dialog.project.edit.title": "Редактировать проект",
"dialog.project.edit.name": "Название", "dialog.project.edit.name": "Название",
@ -405,6 +486,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -452,10 +534,12 @@ export const dict = {
"error.page.title": "Что-то пошло не так", "error.page.title": "Что-то пошло не так",
"error.page.description": "Произошла ошибка при загрузке приложения.", "error.page.description": "Произошла ошибка при загрузке приложения.",
"error.page.description.localServerStartup": "Произошла ошибка при запуске локального сервера.",
"error.page.details.label": "Детали ошибки", "error.page.details.label": "Детали ошибки",
"error.page.action.restart": "Перезапустить", "error.page.action.restart": "Перезапустить",
"error.page.action.report": "Сообщить об ошибке", "error.page.action.report": "Сообщить об ошибке",
"error.page.action.reported": "Об ошибке сообщено", "error.page.action.reported": "Об ошибке сообщено",
"error.page.action.exportLogs": "Экспортировать журналы",
"error.page.action.checking": "Проверка...", "error.page.action.checking": "Проверка...",
"error.page.action.checkUpdates": "Проверить обновления", "error.page.action.checkUpdates": "Проверить обновления",
"error.page.action.updateTo": "Обновить до {{version}}", "error.page.action.updateTo": "Обновить до {{version}}",
@ -505,11 +589,31 @@ export const dict = {
"home.recentProjects": "Недавние проекты", "home.recentProjects": "Недавние проекты",
"home.empty.title": "Нет недавних проектов", "home.empty.title": "Нет недавних проектов",
"home.empty.description": "Начните с открытия локального проекта", "home.empty.description": "Начните с открытия локального проекта",
"home.title": "Главная",
"home.projects": "Проекты",
"home.project.add": "Добавить проект",
"home.recentlyClosed": "Недавно закрытые",
"home.server.collapse": "Свернуть проекты сервера",
"home.server.expand": "Развернуть проекты сервера",
"home.sessions.search.placeholder": "Поиск сессий",
"home.sessions.search.placeholder.scoped": "Поиск сессий в {{scope}}",
"home.sessions.search.sessions": "Сессии",
"home.sessions.search.noResults": "Сессии по запросу {{query}} не найдены",
"home.sessions.empty": "Здесь пока ничего нет",
"home.sessions.empty.description": "Создайте сессию, чтобы начать",
"home.sessions.group.today": "Сегодня",
"home.sessions.group.yesterday": "Вчера",
"home.sessions.group.older": "Ранее",
"session.tab.session": "Сессия", "session.tab.session": "Сессия",
"session.tab.review": "Обзор", "session.tab.review": "Обзор",
"session.tab.context": "Контекст", "session.tab.context": "Контекст",
"session.tab.unknown": "Неизвестная сессия",
"session.panel.reviewAndFiles": "Обзор и файлы", "session.panel.reviewAndFiles": "Обзор и файлы",
"session.error.notFound": "Эта сессия не найдена",
"session.error.notFound.description": "Эта вкладка указывает на сессию, которой больше нет на этом сервере.",
"session.error.notFound.closeTab": "Закрыть вкладку",
"session.error.serverConnection": "Не удаётся подключиться к этому серверу",
"session.review.filesChanged": "{{count}} файлов изменено", "session.review.filesChanged": "{{count}} файлов изменено",
"session.review.change.one": "Изменение", "session.review.change.one": "Изменение",
"session.review.change.other": "Изменения", "session.review.change.other": "Изменения",
@ -518,6 +622,8 @@ export const dict = {
"session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются", "session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются",
"session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны", "session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны",
"session.review.noChanges": "Нет изменений", "session.review.noChanges": "Нет изменений",
"session.review.noUncommittedChanges": "Пока нет незафиксированных изменений",
"session.review.noBranchChanges": "Пока нет изменений в ветке",
"session.files.selectToOpen": "Выберите файл, чтобы открыть", "session.files.selectToOpen": "Выберите файл, чтобы открыть",
"session.files.all": "Все файлы", "session.files.all": "Все файлы",
"session.files.empty": "Нет файлов", "session.files.empty": "Нет файлов",
@ -532,6 +638,10 @@ export const dict = {
"session.todo.title": "Задачи", "session.todo.title": "Задачи",
"session.todo.collapse": "Свернуть", "session.todo.collapse": "Свернуть",
"session.todo.expand": "Развернуть", "session.todo.expand": "Развернуть",
"session.question.minimize": "Свернуть вопрос",
"session.question.restore": "Восстановить вопрос",
"session.question.pending.one": "{{count}} вопрос без ответа",
"session.question.pending.other": "Вопросы без ответа: {{count}}",
"session.followupDock.summary.one": "{{count}} сообщение в очереди", "session.followupDock.summary.one": "{{count}} сообщение в очереди",
"session.followupDock.summary.other": "{{count}} сообщений в очереди", "session.followupDock.summary.other": "{{count}} сообщений в очереди",
"session.followupDock.sendNow": "Отправить сейчас", "session.followupDock.sendNow": "Отправить сейчас",
@ -545,9 +655,16 @@ export const dict = {
"session.revertDock.restore": "Восстановить сообщение", "session.revertDock.restore": "Восстановить сообщение",
"session.new.title": "Создавайте что угодно", "session.new.title": "Создавайте что угодно",
"session.new.project.new": "Новый проект",
"session.new.project.search": "Поиск проектов",
"session.new.project.add": "Добавить проект",
"session.new.worktree.main": "Основная ветка", "session.new.worktree.main": "Основная ветка",
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})", "session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
"session.new.worktree.create": "Создать новый worktree", "session.new.worktree.create": "Создать новый worktree",
"session.new.workspace.runIn": "Запустить сессию в",
"session.new.workspace.triggerLocal": "Локально",
"session.new.workspace.local": "Локальный репозиторий",
"session.new.workspace.existing": "Рабочее пространство…",
"session.new.lastModified": "Последнее изменение", "session.new.lastModified": "Последнее изменение",
"session.header.search.placeholder": "Поиск {{project}}", "session.header.search.placeholder": "Поиск {{project}}",
@ -619,6 +736,8 @@ export const dict = {
"sidebar.project.recentSessions": "Недавние сессии", "sidebar.project.recentSessions": "Недавние сессии",
"sidebar.project.viewAllSessions": "Посмотреть все сессии", "sidebar.project.viewAllSessions": "Посмотреть все сессии",
"sidebar.project.clearNotifications": "Очистить уведомления", "sidebar.project.clearNotifications": "Очистить уведомления",
"sidebar.empty.title": "Нет открытых проектов",
"sidebar.empty.description": "Откройте проект, чтобы начать",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Приложение", "settings.section.desktop": "Приложение",
@ -630,6 +749,7 @@ export const dict = {
"settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.", "settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.",
"settings.general.section.appearance": "Внешний вид", "settings.general.section.appearance": "Внешний вид",
"settings.general.section.advanced": "Дополнительно",
"settings.general.section.notifications": "Системные уведомления", "settings.general.section.notifications": "Системные уведомления",
"settings.general.section.updates": "Обновления", "settings.general.section.updates": "Обновления",
"settings.general.section.sounds": "Звуковые эффекты", "settings.general.section.sounds": "Звуковые эффекты",
@ -638,6 +758,11 @@ export const dict = {
"settings.general.row.language.title": "Язык", "settings.general.row.language.title": "Язык",
"settings.general.row.language.description": "Изменить язык отображения OpenCode", "settings.general.row.language.description": "Изменить язык отображения OpenCode",
"settings.general.row.shell.title": "Оболочка терминала",
"settings.general.row.shell.description":
"Выберите оболочку для терминала. Совместимые оболочки также используются агентом при вызове инструментов.",
"settings.general.row.shell.autoDefault": "Авто (по умолчанию)",
"settings.general.row.shell.terminalOnly": "только терминал",
"settings.general.row.appearance.title": "Внешний вид", "settings.general.row.appearance.title": "Внешний вид",
"settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве", "settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве",
"settings.general.row.colorScheme.title": "Цветовая схема", "settings.general.row.colorScheme.title": "Цветовая схема",
@ -646,8 +771,8 @@ export const dict = {
"settings.general.row.theme.description": "Настройте оформление OpenCode.", "settings.general.row.theme.description": "Настройте оформление OpenCode.",
"settings.general.row.font.title": "Шрифт кода", "settings.general.row.font.title": "Шрифт кода",
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода", "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Шрифт терминала",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Настройте шрифт, используемый в терминале",
"settings.general.row.uiFont.title": "Шрифт интерфейса", "settings.general.row.uiFont.title": "Шрифт интерфейса",
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе", "settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
"settings.general.row.followup.title": "Поведение уточняющих вопросов", "settings.general.row.followup.title": "Поведение уточняющих вопросов",
@ -655,6 +780,22 @@ export const dict = {
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь", "Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
"settings.general.row.followup.option.queue": "Очередь", "settings.general.row.followup.option.queue": "Очередь",
"settings.general.row.followup.option.steer": "Направлять", "settings.general.row.followup.option.steer": "Направлять",
"settings.general.row.showFileTree.title": "Дерево файлов",
"settings.general.row.showFileTree.description": "Показывать панель дерева файлов в сессиях",
"settings.general.row.showNavigation.title": "Элементы навигации",
"settings.general.row.showNavigation.description":
"Показывать кнопки «Назад» и «Вперёд» в строке заголовка приложения",
"settings.general.row.showSearch.title": "Палитра команд",
"settings.general.row.showSearch.description": "Показывать кнопку поиска и палитры команд в строке заголовка",
"settings.general.row.showTerminal.title": "Терминал",
"settings.general.row.showTerminal.description": "Показывать кнопку терминала в строке заголовка приложения",
"settings.general.row.showStatus.title": "Состояние сервера",
"settings.general.row.showStatus.description": "Показывать кнопку состояния сервера в строке заголовка",
"settings.general.row.mobileTitlebarBottom.title": "Нижняя навигация",
"settings.general.row.mobileTitlebarBottom.description":
"На мобильных устройствах размещать строку заголовка и вкладки сессии внизу экрана",
"settings.general.row.showCustomAgents.title": "Пользовательские агенты",
"settings.general.row.showCustomAgents.description": "Показывать выбор агента в редакторе запросов",
"settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений", "settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений",
"settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте", "settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте",
@ -664,6 +805,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit", "settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию", "Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
"settings.general.row.newLayoutDesigns.title": "Новая компоновка и оформление",
"settings.general.row.newLayoutDesigns.description":
"Включить обновлённую компоновку, главную страницу, редактор запросов и интерфейс сессии",
"settings.general.row.pinchZoom.title": "Масштабирование щипком",
"settings.general.row.pinchZoom.description":
"Разрешить масштабирование жестом щипка на трекпаде и прокруткой с Ctrl",
"settings.general.row.wayland.title": "Использовать нативный Wayland", "settings.general.row.wayland.title": "Использовать нативный Wayland",
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.", "settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@ -678,6 +825,8 @@ export const dict = {
"settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны", "settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны",
"settings.updates.action.checkNow": "Проверить сейчас", "settings.updates.action.checkNow": "Проверить сейчас",
"settings.updates.action.checking": "Проверка...", "settings.updates.action.checking": "Проверка...",
"settings.updates.action.downloading": "Загрузка...",
"settings.updates.action.installing": "Установка...",
"settings.updates.toast.latest.title": "У вас последняя версия", "settings.updates.toast.latest.title": "У вас последняя версия",
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.", "settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
"sound.option.none": "Нет", "sound.option.none": "Нет",

View file

@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "สลับแถบข้าง", "command.sidebar.toggle": "สลับแถบข้าง",
"command.project.open": "เปิดโปรเจกต์", "command.project.open": "เปิดโปรเจกต์",
"command.project.previous": "โปรเจกต์ก่อนหน้า",
"command.project.next": "โปรเจกต์ถัดไป",
"command.project.index": "สลับไปยังโปรเจกต์ {{index}}",
"command.provider.connect": "เชื่อมต่อผู้ให้บริการ", "command.provider.connect": "เชื่อมต่อผู้ให้บริการ",
"command.server.switch": "สลับเซิร์ฟเวอร์", "command.server.switch": "สลับเซิร์ฟเวอร์",
"command.settings.open": "เปิดการตั้งค่า", "command.settings.open": "เปิดการตั้งค่า",
@ -32,7 +35,7 @@ export const dict = {
"command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป", "command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป",
"command.session.archive": "จัดเก็บเซสชัน", "command.session.archive": "จัดเก็บเซสชัน",
"command.palette": "คำสั่งค้นหา", "command.palette": "ชุดคำสั่ง",
"command.theme.cycle": "เปลี่ยนธีม", "command.theme.cycle": "เปลี่ยนธีม",
"command.theme.set": "ใช้ธีม: {{theme}}", "command.theme.set": "ใช้ธีม: {{theme}}",
@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "เซสชันใหม่", "command.session.new": "เซสชันใหม่",
"command.file.open": "เปิดไฟล์", "command.file.open": "เปิดไฟล์",
"command.tab.close": "ปิดแท็บ", "command.tab.close": "ปิดแท็บ",
"command.tab.reopenClosed": "เปิดแท็บที่ปิดไปอีกครั้ง",
"command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท", "command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท",
"command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน", "command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน",
"command.input.focus": "โฟกัสช่องป้อนข้อมูล", "command.input.focus": "โฟกัสช่องป้อนข้อมูล",
@ -163,7 +167,7 @@ export const dict = {
"provider.custom.field.providerID.placeholder": "myprovider", "provider.custom.field.providerID.placeholder": "myprovider",
"provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง", "provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
"provider.custom.field.name.label": "ชื่อที่แสดง", "provider.custom.field.name.label": "ชื่อที่แสดง",
"provider.custom.field.name.placeholder": "My AI Provider", "provider.custom.field.name.placeholder": "ผู้ให้บริการ AI ของฉัน",
"provider.custom.field.baseURL.label": "URL พื้นฐาน", "provider.custom.field.baseURL.label": "URL พื้นฐาน",
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
"provider.custom.field.apiKey.label": "คีย์ API", "provider.custom.field.apiKey.label": "คีย์ API",
@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "วิดีโอ", "model.input.video": "วิดีโอ",
"model.input.pdf": "PDF", "model.input.pdf": "PDF",
"model.tooltip.allows": "อนุญาต: {{inputs}}", "model.tooltip.allows": "อนุญาต: {{inputs}}",
"model.tooltip.context.label": "บริบท",
"model.tooltip.inputs": "ข้อมูลนำเข้า",
"model.tooltip.model": "โมเดล",
"model.tooltip.provider": "ผู้ให้บริการ",
"model.tooltip.reasoning": "การใช้เหตุผล",
"model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล", "model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล",
"model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล", "model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล",
"model.tooltip.context": "ขีดจำกัดบริบท {{limit}}", "model.tooltip.context": "ขีดจำกัดบริบท {{limit}}",
@ -218,6 +227,7 @@ export const dict = {
"common.loading": "กำลังโหลด", "common.loading": "กำลังโหลด",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "ยกเลิก", "common.cancel": "ยกเลิก",
"common.clear": "ล้าง",
"common.connect": "เชื่อมต่อ", "common.connect": "เชื่อมต่อ",
"common.disconnect": "ยกเลิกการเชื่อมต่อ", "common.disconnect": "ยกเลิกการเชื่อมต่อ",
"common.continue": "ส่ง", "common.continue": "ส่ง",
@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "เชลล์", "prompt.mode.shell": "เชลล์",
"prompt.mode.normal": "พรอมต์", "prompt.mode.normal": "พรอมต์",
"prompt.mode.shell.exit": "กด esc เพื่อออก", "prompt.mode.shell.exit": "กด esc เพื่อออก",
"session.child.promptDisabled": "ไม่สามารถส่งพรอมต์ในเซสชันเอเจนต์ย่อยได้.",
"session.child.backToParent": "กลับไปยังเซสชันหลัก.",
"prompt.example.1": "แก้ไข TODO ในโค้ดเบส", "prompt.example.1": "แก้ไข TODO ในโค้ดเบส",
"prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?", "prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
@ -341,6 +353,75 @@ export const dict = {
"dialog.server.menu.delete": "ลบ", "dialog.server.menu.delete": "ลบ",
"dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน", "dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน",
"dialog.server.status.default": "ค่าเริ่มต้น", "dialog.server.status.default": "ค่าเริ่มต้น",
"wsl.server.add": "เพิ่มเซิร์ฟเวอร์ WSL",
"wsl.server.addShort": "เพิ่ม WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "เซิร์ฟเวอร์ WSL",
"wsl.server.retryStart": "ลองเริ่มอีกครั้ง",
"wsl.server.updating": "กำลังอัปเดต...",
"wsl.onboarding.step.distro": "เลือกดิสโทร",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "กำลังตรวจสอบ WSL...",
"wsl.onboarding.restartRequired": "ต้องรีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์.",
"wsl.onboarding.ready": "WSL พร้อมใช้งานแล้ว.",
"wsl.onboarding.required": "ต้องมี WSL เพื่อดำเนินการต่อ.",
"wsl.onboarding.checkingDistros": "กำลังตรวจสอบดิสโทร...",
"wsl.onboarding.installingDistro": "กำลังติดตั้ง {{distro}}...",
"wsl.onboarding.checkingDistro": "กำลังตรวจสอบ {{distro}}...",
"wsl.onboarding.listingDistros": "กำลังแสดงรายการดิสโทร...",
"wsl.onboarding.distroReady": "{{distro}} พร้อมใช้งานแล้ว.",
"wsl.onboarding.distroNotInstalled": "ยังไม่ได้ติดตั้ง {{distro}}.",
"wsl.onboarding.openDistroOnce": "เปิด {{distro}} หนึ่งครั้งเพื่อให้การตั้งค่าเสร็จสมบูรณ์.",
"wsl.onboarding.finishingDistro": "กำลังตั้งค่า {{distro}} ให้เสร็จสมบูรณ์.",
"wsl.onboarding.pickDistro": "เลือกดิสโทรหรือติดตั้งจากรายการด้านล่าง.",
"wsl.onboarding.checkingOpencode": "กำลังตรวจสอบ OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "กำลังตรวจสอบ OpenCode ใน {{distro}}...",
"wsl.onboarding.updatingOpencode": "กำลังอัปเดต OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "กำลังอัปเดต OpenCode ใน {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "อัปเดต OpenCode ใน {{distro}}.",
"wsl.onboarding.updateOpencode": "อัปเดต OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode พร้อมใช้งานใน {{distro}} แล้ว.",
"wsl.onboarding.opencodeReady": "OpenCode พร้อมใช้งานแล้ว.",
"wsl.onboarding.installOpencodeIn": "ติดตั้ง OpenCode ใน {{distro}}.",
"wsl.onboarding.installOpencode": "ติดตั้ง OpenCode",
"wsl.onboarding.chooseDistroFirst": "เลือกดิสโทรก่อน.",
"wsl.onboarding.loadFailed": "ไม่สามารถโหลดสถานะ WSL ได้.",
"wsl.onboarding.loading": "กำลังโหลด...",
"wsl.onboarding.installedDistros": "ดิสโทรที่ติดตั้งแล้ว",
"wsl.onboarding.checkAgain": "ตรวจสอบอีกครั้ง",
"wsl.onboarding.distroStatus.ready": "พร้อมใช้งาน",
"wsl.onboarding.distroStatus.checking": "กำลังตรวจสอบ...",
"wsl.onboarding.distroStatus.opencodeMissing": "ยังไม่ได้ติดตั้ง OpenCode",
"wsl.onboarding.distroStatus.missingTools": "ไม่มี bash, curl",
"wsl.onboarding.distroStatus.unsupported": "ไม่รองรับ · ใช้ WSL 2",
"wsl.onboarding.needAnotherDistro": "ต้องการดิสโทรอื่น?",
"wsl.onboarding.needAnotherDistroHint": "ติดตั้งดิสทริบิวชัน Linux จากแค็ตตาล็อก WSL",
"wsl.onboarding.wslNotInstalled.title": "ยังไม่ได้ติดตั้ง WSL",
"wsl.onboarding.wslNotInstalled.description":
"ต้องมี WSL (Windows Subsystem for Linux) ก่อนที่ OpenCode จะเพิ่มเซิร์ฟเวอร์ WSL ได้",
"wsl.onboarding.wslUnavailable.title": "WSL ไม่พร้อมใช้งาน",
"wsl.onboarding.wslUnavailable.description": "OpenCode ไม่สามารถตรวจสอบ WSL บนเครื่องนี้ได้.",
"wsl.onboarding.installWsl": "ติดตั้ง WSL",
"wsl.onboarding.windowsRestartRequired":
"รีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์ แล้วเปิด OpenCode อีกครั้ง.",
"wsl.onboarding.next": "ถัดไป",
"wsl.onboarding.refresh": "รีเฟรช",
"wsl.onboarding.allDistrosAdded": "เพิ่มดิสโทรที่ติดตั้งไว้ทั้งหมดแล้ว.",
"wsl.onboarding.noDistros": "ยังไม่พบดิสโทร.",
"wsl.onboarding.install": "ติดตั้ง",
"wsl.onboarding.installing": "กำลังติดตั้ง...",
"wsl.onboarding.installDistro": "ติดตั้งดิสโทร",
"wsl.onboarding.searchDistros": "ค้นหาดิสโทร",
"wsl.onboarding.wsl2Required": "ต้องใช้ WSL 2.",
"wsl.onboarding.toolsRequired": "ดิสโทรนี้ต้องมี bash และ curl.",
"wsl.onboarding.openTerminal": "เปิดเทอร์มินัล",
"wsl.onboarding.path": "เส้นทาง: {{path}}",
"wsl.onboarding.notFound": "ไม่พบ",
"wsl.onboarding.version": "เวอร์ชัน: {{version}}",
"wsl.onboarding.unknown": "ไม่ทราบ",
"wsl.onboarding.desktopVersion": "เดสก์ท็อป {{version}}",
"wsl.onboarding.versionMismatch": "เวอร์ชันที่ติดตั้งไม่ตรงกับเวอร์ชันของแอปเดสก์ท็อป.",
"wsl.onboarding.adding": "กำลังเพิ่ม...",
"dialog.project.edit.title": "แก้ไขโปรเจกต์", "dialog.project.edit.title": "แก้ไขโปรเจกต์",
"dialog.project.edit.name": "ชื่อ", "dialog.project.edit.name": "ชื่อ",
@ -403,6 +484,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -451,10 +533,12 @@ export const dict = {
"error.page.title": "เกิดข้อผิดพลาด", "error.page.title": "เกิดข้อผิดพลาด",
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน", "error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
"error.page.description.localServerStartup": "เกิดข้อผิดพลาดขณะเริ่มเซิร์ฟเวอร์ในเครื่อง.",
"error.page.details.label": "รายละเอียดข้อผิดพลาด", "error.page.details.label": "รายละเอียดข้อผิดพลาด",
"error.page.action.restart": "รีสตาร์ท", "error.page.action.restart": "รีสตาร์ท",
"error.page.action.report": "รายงานข้อผิดพลาด", "error.page.action.report": "รายงานข้อผิดพลาด",
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว", "error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
"error.page.action.exportLogs": "ส่งออกบันทึก",
"error.page.action.checking": "กำลังตรวจสอบ...", "error.page.action.checking": "กำลังตรวจสอบ...",
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต", "error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
"error.page.action.updateTo": "อัปเดตเป็น {{version}}", "error.page.action.updateTo": "อัปเดตเป็น {{version}}",
@ -500,11 +584,31 @@ export const dict = {
"home.recentProjects": "โปรเจกต์ล่าสุด", "home.recentProjects": "โปรเจกต์ล่าสุด",
"home.empty.title": "ไม่มีโปรเจกต์ล่าสุด", "home.empty.title": "ไม่มีโปรเจกต์ล่าสุด",
"home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง", "home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง",
"home.title": "หน้าหลัก",
"home.projects": "โปรเจกต์",
"home.project.add": "เพิ่มโปรเจกต์",
"home.recentlyClosed": "ที่ปิดไปล่าสุด",
"home.server.collapse": "ย่อโปรเจกต์ของเซิร์ฟเวอร์",
"home.server.expand": "ขยายโปรเจกต์ของเซิร์ฟเวอร์",
"home.sessions.search.placeholder": "ค้นหาเซสชัน",
"home.sessions.search.placeholder.scoped": "ค้นหาเซสชันใน {{scope}}",
"home.sessions.search.sessions": "เซสชัน",
"home.sessions.search.noResults": "ไม่พบเซสชันสำหรับ {{query}}",
"home.sessions.empty": "ยังไม่มีอะไรที่นี่",
"home.sessions.empty.description": "สร้างเซสชันเพื่อเริ่มต้น",
"home.sessions.group.today": "วันนี้",
"home.sessions.group.yesterday": "เมื่อวาน",
"home.sessions.group.older": "ก่อนหน้านี้",
"session.tab.session": "เซสชัน", "session.tab.session": "เซสชัน",
"session.tab.review": "ตรวจสอบ", "session.tab.review": "ตรวจสอบ",
"session.tab.context": "บริบท", "session.tab.context": "บริบท",
"session.tab.unknown": "เซสชันที่ไม่รู้จัก",
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์", "session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
"session.error.notFound": "ไม่พบเซสชันนี้",
"session.error.notFound.description": "แท็บนี้อ้างถึงเซสชันที่ไม่มีอยู่บนเซิร์ฟเวอร์นี้แล้ว.",
"session.error.notFound.closeTab": "ปิดแท็บ",
"session.error.serverConnection": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้",
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
"session.review.change.one": "การเปลี่ยนแปลง", "session.review.change.one": "การเปลี่ยนแปลง",
"session.review.change.other": "การเปลี่ยนแปลง", "session.review.change.other": "การเปลี่ยนแปลง",
@ -513,6 +617,8 @@ export const dict = {
"session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง", "session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง",
"session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน", "session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน",
"session.review.noChanges": "ไม่มีการเปลี่ยนแปลง", "session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
"session.review.noUncommittedChanges": "ยังไม่มีการเปลี่ยนแปลงที่รอคอมมิต",
"session.review.noBranchChanges": "ยังไม่มีการเปลี่ยนแปลงในสาขา",
"session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด", "session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
"session.files.empty": "ไม่มีไฟล์", "session.files.empty": "ไม่มีไฟล์",
@ -529,6 +635,10 @@ export const dict = {
"session.todo.title": "สิ่งที่ต้องทำ", "session.todo.title": "สิ่งที่ต้องทำ",
"session.todo.collapse": "ย่อ", "session.todo.collapse": "ย่อ",
"session.todo.expand": "ขยาย", "session.todo.expand": "ขยาย",
"session.question.minimize": "ย่อคำถาม",
"session.question.restore": "คืนค่าคำถาม",
"session.question.pending.one": "คำถามที่รอดำเนินการ {{count}} ข้อ",
"session.question.pending.other": "คำถามที่รอดำเนินการ {{count}} ข้อ",
"session.followupDock.summary.one": "{{count}} ข้อความในคิว", "session.followupDock.summary.one": "{{count}} ข้อความในคิว",
"session.followupDock.summary.other": "{{count}} ข้อความในคิว", "session.followupDock.summary.other": "{{count}} ข้อความในคิว",
"session.followupDock.sendNow": "ส่งทันที", "session.followupDock.sendNow": "ส่งทันที",
@ -542,9 +652,16 @@ export const dict = {
"session.revertDock.restore": "กู้คืนข้อความ", "session.revertDock.restore": "กู้คืนข้อความ",
"session.new.title": "สร้างอะไรก็ได้", "session.new.title": "สร้างอะไรก็ได้",
"session.new.project.new": "โปรเจกต์ใหม่",
"session.new.project.search": "ค้นหาโปรเจกต์",
"session.new.project.add": "เพิ่มโปรเจกต์",
"session.new.worktree.main": "สาขาหลัก", "session.new.worktree.main": "สาขาหลัก",
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})", "session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
"session.new.worktree.create": "สร้าง worktree ใหม่", "session.new.worktree.create": "สร้าง worktree ใหม่",
"session.new.workspace.runIn": "เรียกใช้เซสชันใน",
"session.new.workspace.triggerLocal": "ในเครื่อง",
"session.new.workspace.local": "รีโพซิทอรีในเครื่อง",
"session.new.workspace.existing": "พื้นที่ทำงาน…",
"session.new.lastModified": "แก้ไขล่าสุด", "session.new.lastModified": "แก้ไขล่าสุด",
"session.header.search.placeholder": "ค้นหา {{project}}", "session.header.search.placeholder": "ค้นหา {{project}}",
@ -612,6 +729,8 @@ export const dict = {
"sidebar.project.recentSessions": "เซสชันล่าสุด", "sidebar.project.recentSessions": "เซสชันล่าสุด",
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
"sidebar.project.clearNotifications": "ล้างการแจ้งเตือน", "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
"sidebar.empty.title": "ไม่มีโปรเจกต์ที่เปิดอยู่",
"sidebar.empty.description": "เปิดโปรเจกต์เพื่อเริ่มต้น",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@ -624,6 +743,7 @@ export const dict = {
"settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows", "settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows",
"settings.general.section.appearance": "รูปลักษณ์", "settings.general.section.appearance": "รูปลักษณ์",
"settings.general.section.advanced": "ขั้นสูง",
"settings.general.section.notifications": "การแจ้งเตือนระบบ", "settings.general.section.notifications": "การแจ้งเตือนระบบ",
"settings.general.section.updates": "การอัปเดต", "settings.general.section.updates": "การอัปเดต",
"settings.general.section.sounds": "เสียงเอฟเฟกต์", "settings.general.section.sounds": "เสียงเอฟเฟกต์",
@ -632,6 +752,11 @@ export const dict = {
"settings.general.row.language.title": "ภาษา", "settings.general.row.language.title": "ภาษา",
"settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
"settings.general.row.shell.title": "เชลล์เทอร์มินัล",
"settings.general.row.shell.description":
"เลือกเชลล์ที่จะใช้กับเทอร์มินัล เชลล์ที่เข้ากันได้จะใช้สำหรับการเรียกเครื่องมือของเอเจนต์ด้วย",
"settings.general.row.shell.autoDefault": "อัตโนมัติ (ค่าเริ่มต้น)",
"settings.general.row.shell.terminalOnly": "เทอร์มินัลเท่านั้น",
"settings.general.row.appearance.title": "รูปลักษณ์", "settings.general.row.appearance.title": "รูปลักษณ์",
"settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ", "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
"settings.general.row.colorScheme.title": "โทนสี", "settings.general.row.colorScheme.title": "โทนสี",
@ -640,14 +765,29 @@ export const dict = {
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม", "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
"settings.general.row.font.title": "ฟอนต์โค้ด", "settings.general.row.font.title": "ฟอนต์โค้ด",
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด", "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "ฟอนต์เทอร์มินัล",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "ปรับแต่งฟอนต์ที่ใช้ในเทอร์มินัล",
"settings.general.row.uiFont.title": "ฟอนต์ UI", "settings.general.row.uiFont.title": "ฟอนต์ UI",
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ", "settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล", "settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว", "settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
"settings.general.row.followup.option.queue": "คิว", "settings.general.row.followup.option.queue": "คิว",
"settings.general.row.followup.option.steer": "นำทาง", "settings.general.row.followup.option.steer": "นำทาง",
"settings.general.row.showFileTree.title": "ต้นไม้ไฟล์",
"settings.general.row.showFileTree.description": "แสดงแผงต้นไม้ไฟล์ในเซสชัน",
"settings.general.row.showNavigation.title": "ตัวควบคุมการนำทาง",
"settings.general.row.showNavigation.description": "แสดงปุ่มย้อนกลับและไปข้างหน้าในแถบชื่อเรื่องเดสก์ท็อป",
"settings.general.row.showSearch.title": "ชุดคำสั่ง",
"settings.general.row.showSearch.description": "แสดงปุ่มค้นหาและชุดคำสั่งในแถบชื่อเรื่อง",
"settings.general.row.showTerminal.title": "เทอร์มินัล",
"settings.general.row.showTerminal.description": "แสดงปุ่มเทอร์มินัลในแถบชื่อเรื่องเดสก์ท็อป",
"settings.general.row.showStatus.title": "สถานะเซิร์ฟเวอร์",
"settings.general.row.showStatus.description": "แสดงปุ่มสถานะเซิร์ฟเวอร์ในแถบชื่อเรื่อง",
"settings.general.row.mobileTitlebarBottom.title": "การนำทางด้านล่าง",
"settings.general.row.mobileTitlebarBottom.description":
"วางแถบชื่อเรื่องและแท็บเซสชันไว้ด้านล่างของหน้าจอบนอุปกรณ์เคลื่อนที่",
"settings.general.row.showCustomAgents.title": "เอเจนต์ที่กำหนดเอง",
"settings.general.row.showCustomAgents.description": "แสดงตัวเลือกเอเจนต์ในช่องเขียนข้อความ",
"settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล", "settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล",
"settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์", "settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์",
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell", "settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
@ -655,6 +795,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit", "settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์", "แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
"settings.general.row.newLayoutDesigns.title": "เลย์เอาต์และดีไซน์ใหม่",
"settings.general.row.newLayoutDesigns.description":
"เปิดใช้เลย์เอาต์ หน้าแรก ช่องเขียนข้อความ และ UI เซสชันที่ออกแบบใหม่",
"settings.general.row.pinchZoom.title": "บีบนิ้วเพื่อซูม",
"settings.general.row.pinchZoom.description": "อนุญาตให้ใช้ท่าบีบนิ้วบนแทร็คแพดและ Ctrl-scroll เพื่อซูม",
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ", "settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท", "settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า", "settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
@ -668,6 +813,8 @@ export const dict = {
"settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี", "settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี",
"settings.updates.action.checkNow": "ตรวจสอบทันที", "settings.updates.action.checkNow": "ตรวจสอบทันที",
"settings.updates.action.checking": "กำลังตรวจสอบ...", "settings.updates.action.checking": "กำลังตรวจสอบ...",
"settings.updates.action.downloading": "กำลังดาวน์โหลด...",
"settings.updates.action.installing": "กำลังติดตั้ง...",
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว", "settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด", "settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
@ -857,10 +1004,10 @@ export const dict = {
"dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้", "dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
"dialog.server.add.passwordPlaceholder": "รหัสผ่าน", "dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
"server.row.noUsername": "ไม่มีชื่อผู้ใช้", "server.row.noUsername": "ไม่มีชื่อผู้ใช้",
"session.review.noVcs.createGit.title": "สร้าง Git repository", "session.review.noVcs.createGit.title": "สร้าง Git รีโพซิทอรี",
"session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้", "session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้",
"session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git repository...", "session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git รีโพซิทอรี...",
"session.review.noVcs.createGit.action": "สร้าง Git repository", "session.review.noVcs.createGit.action": "สร้าง Git รีโพซิทอรี",
"session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ", "session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ",
"session.question.progress": "{{current}} จาก {{total}} คำถาม", "session.question.progress": "{{current}} จาก {{total}} คำถาม",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",

View file

@ -27,6 +27,9 @@ export const dict = {
"command.sidebar.toggle": "Kenar çubuğunu aç/kapat", "command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
"command.project.open": "Proje aç", "command.project.open": "Proje aç",
"command.project.previous": "Önceki proje",
"command.project.next": "Sonraki proje",
"command.project.index": "{{index}} numaralı projeye geç",
"command.provider.connect": "Sağlayıcı bağla", "command.provider.connect": "Sağlayıcı bağla",
"command.server.switch": "Sunucu değiştir", "command.server.switch": "Sunucu değiştir",
"command.settings.open": "Ayarları aç", "command.settings.open": "Ayarları aç",
@ -49,6 +52,7 @@ export const dict = {
"command.session.new": "Yeni oturum", "command.session.new": "Yeni oturum",
"command.file.open": "Dosya aç", "command.file.open": "Dosya aç",
"command.tab.close": "Sekmeyi kapat", "command.tab.close": "Sekmeyi kapat",
"command.tab.reopenClosed": "Kapatılan sekmeyi yeniden aç",
"command.context.addSelection": "Seçimi bağlama ekle", "command.context.addSelection": "Seçimi bağlama ekle",
"command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle", "command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
"command.input.focus": "Girişi odakla", "command.input.focus": "Girişi odakla",
@ -132,7 +136,7 @@ export const dict = {
"provider.connect.status.waiting": "Yetkilendirme bekleniyor...", "provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
"provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}", "provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
"provider.connect.apiKey.description": "provider.connect.apiKey.description":
"{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.", "{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için API anahtarınızı girin.",
"provider.connect.apiKey.label": "{{provider}} API anahtarı", "provider.connect.apiKey.label": "{{provider}} API anahtarı",
"provider.connect.apiKey.placeholder": "API anahtarı", "provider.connect.apiKey.placeholder": "API anahtarı",
"provider.connect.apiKey.required": "API anahtarı gerekli", "provider.connect.apiKey.required": "API anahtarı gerekli",
@ -143,10 +147,10 @@ export const dict = {
"provider.connect.opencodeZen.visit.prefix": "", "provider.connect.opencodeZen.visit.prefix": "",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen", "provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.", "provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
"provider.connect.oauth.code.visit.prefix": "provider.connect.oauth.code.visit.prefix": "Yetkilendirme kodunuzu almak için ",
"Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ",
"provider.connect.oauth.code.visit.link": "bu bağlantıya", "provider.connect.oauth.code.visit.link": "bu bağlantıya",
"provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.", "provider.connect.oauth.code.visit.suffix":
" gidin; ardından hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.",
"provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu", "provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
"provider.connect.oauth.code.placeholder": "Yetkilendirme kodu", "provider.connect.oauth.code.placeholder": "Yetkilendirme kodu",
"provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli", "provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
@ -213,6 +217,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Kabul eder: {{inputs}}", "model.tooltip.allows": "Kabul eder: {{inputs}}",
"model.tooltip.context.label": "Bağlam",
"model.tooltip.inputs": "Girdiler",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Sağlayıcı",
"model.tooltip.reasoning": "Akıl yürütme",
"model.tooltip.reasoning.allowed": "Akıl yürütme destekler", "model.tooltip.reasoning.allowed": "Akıl yürütme destekler",
"model.tooltip.reasoning.none": "Akıl yürütme yok", "model.tooltip.reasoning.none": "Akıl yürütme yok",
"model.tooltip.context": "Bağlam limiti {{limit}}", "model.tooltip.context": "Bağlam limiti {{limit}}",
@ -223,6 +232,7 @@ export const dict = {
"common.loading": "Yükleniyor", "common.loading": "Yükleniyor",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "İptal", "common.cancel": "İptal",
"common.clear": "Temizle",
"common.connect": "Bağlan", "common.connect": "Bağlan",
"common.disconnect": "Bağlantı Kes", "common.disconnect": "Bağlantı Kes",
"common.continue": "Gönder", "common.continue": "Gönder",
@ -240,6 +250,8 @@ export const dict = {
"prompt.mode.shell": "Kabuk", "prompt.mode.shell": "Kabuk",
"prompt.mode.normal": "Komut", "prompt.mode.normal": "Komut",
"prompt.mode.shell.exit": ıkmak için esc", "prompt.mode.shell.exit": ıkmak için esc",
"session.child.promptDisabled": "Alt ajan oturumlarına istem gönderilemez.",
"session.child.backToParent": "Ana oturuma dön.",
"prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt", "prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt",
"prompt.example.2": "Bu projenin teknoloji yığını nedir?", "prompt.example.2": "Bu projenin teknoloji yığını nedir?",
@ -346,6 +358,75 @@ export const dict = {
"dialog.server.menu.delete": "Sil", "dialog.server.menu.delete": "Sil",
"dialog.server.current": "Mevcut Sunucu", "dialog.server.current": "Mevcut Sunucu",
"dialog.server.status.default": "Varsayılan", "dialog.server.status.default": "Varsayılan",
"wsl.server.add": "WSL sunucusu ekle",
"wsl.server.addShort": "WSL ekle",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL sunucusu",
"wsl.server.retryStart": "Başlatmayı yeniden dene",
"wsl.server.updating": "Güncelleniyor...",
"wsl.onboarding.step.distro": "Dağıtım seç",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL kontrol ediliyor...",
"wsl.onboarding.restartRequired": "WSL kurulumunu tamamlamak için Windows'un yeniden başlatılması gerekiyor.",
"wsl.onboarding.ready": "WSL hazır.",
"wsl.onboarding.required": "Devam etmek için WSL gerekli.",
"wsl.onboarding.checkingDistros": "Dağıtımlar kontrol ediliyor...",
"wsl.onboarding.installingDistro": "{{distro}} kuruluyor...",
"wsl.onboarding.checkingDistro": "{{distro}} kontrol ediliyor...",
"wsl.onboarding.listingDistros": "Dağıtımlar listeleniyor...",
"wsl.onboarding.distroReady": "{{distro}} hazır.",
"wsl.onboarding.distroNotInstalled": "{{distro}} henüz kurulu değil.",
"wsl.onboarding.openDistroOnce": "Kurulumu tamamlamak için {{distro}} dağıtımını bir kez açın.",
"wsl.onboarding.finishingDistro": "{{distro}} kurulumu tamamlanıyor.",
"wsl.onboarding.pickDistro": "Bir dağıtım seçin veya aşağıdan bir dağıtım kurun.",
"wsl.onboarding.checkingOpencode": "OpenCode kontrol ediliyor...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} içinde OpenCode kontrol ediliyor...",
"wsl.onboarding.updatingOpencode": "OpenCode güncelleniyor...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}} içinde OpenCode güncelleniyor...",
"wsl.onboarding.updateOpencodeIn": "{{distro}} içindeki OpenCode'u güncelleyin.",
"wsl.onboarding.updateOpencode": "OpenCode'u güncelle",
"wsl.onboarding.opencodeReadyIn": "{{distro}} içinde OpenCode hazır.",
"wsl.onboarding.opencodeReady": "OpenCode hazır.",
"wsl.onboarding.installOpencodeIn": "OpenCode'u {{distro}} içine kurun.",
"wsl.onboarding.installOpencode": "OpenCode'u kur",
"wsl.onboarding.chooseDistroFirst": "Önce bir dağıtım seçin.",
"wsl.onboarding.loadFailed": "WSL durumu yüklenemedi.",
"wsl.onboarding.loading": "Yükleniyor...",
"wsl.onboarding.installedDistros": "Kurulu dağıtımlar",
"wsl.onboarding.checkAgain": "Tekrar kontrol et",
"wsl.onboarding.distroStatus.ready": "Hazır",
"wsl.onboarding.distroStatus.checking": "Kontrol ediliyor...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode kurulu değil",
"wsl.onboarding.distroStatus.missingTools": "bash ve curl eksik",
"wsl.onboarding.distroStatus.unsupported": "Desteklenmiyor · WSL 2 kullanın",
"wsl.onboarding.needAnotherDistro": "Başka bir dağıtım mı gerekiyor?",
"wsl.onboarding.needAnotherDistroHint": "WSL kataloğundan bir Linux dağıtımı kurun",
"wsl.onboarding.wslNotInstalled.title": "WSL kurulu değil",
"wsl.onboarding.wslNotInstalled.description":
"OpenCode'un WSL sunucusu ekleyebilmesi için önce WSL (Linux için Windows Alt Sistemi) kurulmalıdır",
"wsl.onboarding.wslUnavailable.title": "WSL kullanılamıyor",
"wsl.onboarding.wslUnavailable.description": "OpenCode bu makinede WSL'yi doğrulayamadı.",
"wsl.onboarding.installWsl": "WSL'yi kur",
"wsl.onboarding.windowsRestartRequired":
"WSL kurulumunu tamamlamak için Windows'u yeniden başlatın, ardından OpenCode'u tekrar açın.",
"wsl.onboarding.next": "İleri",
"wsl.onboarding.refresh": "Yenile",
"wsl.onboarding.allDistrosAdded": "Kurulu tüm dağıtımlar zaten eklenmiş.",
"wsl.onboarding.noDistros": "Henüz dağıtım algılanmadı.",
"wsl.onboarding.install": "Kur",
"wsl.onboarding.installing": "Kuruluyor...",
"wsl.onboarding.installDistro": "Dağıtım kur",
"wsl.onboarding.searchDistros": "Dağıtım ara",
"wsl.onboarding.wsl2Required": "WSL 2 gerekli.",
"wsl.onboarding.toolsRequired": "Bu dağıtım için bash ve curl gerekli.",
"wsl.onboarding.openTerminal": "Terminali aç",
"wsl.onboarding.path": "Yol: {{path}}",
"wsl.onboarding.notFound": "bulunamadı",
"wsl.onboarding.version": "Sürüm: {{version}}",
"wsl.onboarding.unknown": "bilinmiyor",
"wsl.onboarding.desktopVersion": "masaüstü {{version}}",
"wsl.onboarding.versionMismatch": "Kurulu sürüm masaüstü uygulamasının sürümüyle eşleşmiyor.",
"wsl.onboarding.adding": "Ekleniyor...",
"dialog.project.edit.title": "Projeyi düzenle", "dialog.project.edit.title": "Projeyi düzenle",
"dialog.project.edit.name": "Ad", "dialog.project.edit.name": "Ad",
@ -408,6 +489,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -456,10 +538,12 @@ export const dict = {
"error.page.title": "Bir şeyler yanlış gitti", "error.page.title": "Bir şeyler yanlış gitti",
"error.page.description": "Uygulama yüklenirken bir hata oluştu.", "error.page.description": "Uygulama yüklenirken bir hata oluştu.",
"error.page.description.localServerStartup": "Yerel sunucu başlatılırken bir hata oluştu.",
"error.page.details.label": "Hata Detayları", "error.page.details.label": "Hata Detayları",
"error.page.action.restart": "Yeniden Başlat", "error.page.action.restart": "Yeniden Başlat",
"error.page.action.report": "Hatayı Bildir", "error.page.action.report": "Hatayı Bildir",
"error.page.action.reported": "Hata Bildirildi", "error.page.action.reported": "Hata Bildirildi",
"error.page.action.exportLogs": "Günlükleri Dışa Aktar",
"error.page.action.checking": "Kontrol ediliyor...", "error.page.action.checking": "Kontrol ediliyor...",
"error.page.action.checkUpdates": "Güncellemeleri kontrol et", "error.page.action.checkUpdates": "Güncellemeleri kontrol et",
"error.page.action.updateTo": "{{version}} sürümüne güncelle", "error.page.action.updateTo": "{{version}} sürümüne güncelle",
@ -509,11 +593,31 @@ export const dict = {
"home.recentProjects": "Son projeler", "home.recentProjects": "Son projeler",
"home.empty.title": "Son proje yok", "home.empty.title": "Son proje yok",
"home.empty.description": "Yerel bir proje açarak başlayın", "home.empty.description": "Yerel bir proje açarak başlayın",
"home.title": "Ana Sayfa",
"home.projects": "Projeler",
"home.project.add": "Proje ekle",
"home.recentlyClosed": "Yakın zamanda kapatılanlar",
"home.server.collapse": "Sunucu projelerini daralt",
"home.server.expand": "Sunucu projelerini genişlet",
"home.sessions.search.placeholder": "Oturum ara",
"home.sessions.search.placeholder.scoped": "{{scope}} kapsamında oturum ara",
"home.sessions.search.sessions": "Oturumlar",
"home.sessions.search.noResults": "{{query}} için oturum bulunamadı",
"home.sessions.empty": "Henüz burada bir şey yok",
"home.sessions.empty.description": "Başlamak için bir oturum oluşturun",
"home.sessions.group.today": "Bugün",
"home.sessions.group.yesterday": "Dün",
"home.sessions.group.older": "Daha eski",
"session.tab.session": "Oturum", "session.tab.session": "Oturum",
"session.tab.review": "İnceleme", "session.tab.review": "İnceleme",
"session.tab.context": "Bağlam", "session.tab.context": "Bağlam",
"session.tab.unknown": "Bilinmeyen Oturum",
"session.panel.reviewAndFiles": "İnceleme ve dosyalar", "session.panel.reviewAndFiles": "İnceleme ve dosyalar",
"session.error.notFound": "Bu oturum bulunamıyor",
"session.error.notFound.description": "Bu sekme, artık bu sunucuda bulunmayan bir oturuma yönlendiriyor.",
"session.error.notFound.closeTab": "Sekmeyi Kapat",
"session.error.serverConnection": "Bu sunucuya bağlanılamıyor",
"session.review.filesChanged": "{{count}} Dosya Değişti", "session.review.filesChanged": "{{count}} Dosya Değişti",
"session.review.change.one": "Değişiklik", "session.review.change.one": "Değişiklik",
"session.review.change.other": "Değişiklik", "session.review.change.other": "Değişiklik",
@ -523,6 +627,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor", "Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor",
"session.review.noChanges": "Değişiklik yok", "session.review.noChanges": "Değişiklik yok",
"session.review.noUncommittedChanges": "Henüz commit edilmemiş değişiklik yok",
"session.review.noBranchChanges": "Henüz dal değişikliği yok",
"session.files.selectToOpen": "Açmak için bir dosya seçin", "session.files.selectToOpen": "Açmak için bir dosya seçin",
"session.files.all": "Tüm dosyalar", "session.files.all": "Tüm dosyalar",
@ -539,6 +645,10 @@ export const dict = {
"session.todo.title": "Görevler", "session.todo.title": "Görevler",
"session.todo.collapse": "Daralt", "session.todo.collapse": "Daralt",
"session.todo.expand": "Genişlet", "session.todo.expand": "Genişlet",
"session.question.minimize": "Soruyu küçült",
"session.question.restore": "Soruyu geri yükle",
"session.question.pending.one": "{{count}} bekleyen soru",
"session.question.pending.other": "{{count}} bekleyen soru",
"session.followupDock.summary.one": "{{count}} sıradaki mesaj", "session.followupDock.summary.one": "{{count}} sıradaki mesaj",
"session.followupDock.summary.other": "{{count}} sıradaki mesaj", "session.followupDock.summary.other": "{{count}} sıradaki mesaj",
"session.followupDock.sendNow": "Şimdi gönder", "session.followupDock.sendNow": "Şimdi gönder",
@ -552,9 +662,16 @@ export const dict = {
"session.revertDock.restore": "Mesajı geri yükle", "session.revertDock.restore": "Mesajı geri yükle",
"session.new.title": "İstediğini yap", "session.new.title": "İstediğini yap",
"session.new.project.new": "Yeni proje",
"session.new.project.search": "Proje ara",
"session.new.project.add": "Proje ekle",
"session.new.worktree.main": "Ana dal", "session.new.worktree.main": "Ana dal",
"session.new.worktree.mainWithBranch": "Ana dal ({{branch}})", "session.new.worktree.mainWithBranch": "Ana dal ({{branch}})",
"session.new.worktree.create": "Yeni çalışma ağacı oluştur", "session.new.worktree.create": "Yeni çalışma ağacı oluştur",
"session.new.workspace.runIn": "Oturumu şurada çalıştır",
"session.new.workspace.triggerLocal": "Yerel",
"session.new.workspace.local": "Yerel depo",
"session.new.workspace.existing": "Çalışma alanı…",
"session.new.lastModified": "Son değişiklik", "session.new.lastModified": "Son değişiklik",
"session.header.search.placeholder": "{{project}} ara", "session.header.search.placeholder": "{{project}} ara",
@ -624,6 +741,8 @@ export const dict = {
"sidebar.project.recentSessions": "Son oturumlar", "sidebar.project.recentSessions": "Son oturumlar",
"sidebar.project.viewAllSessions": "Tüm oturumları görüntüle", "sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
"sidebar.project.clearNotifications": "Bildirimleri temizle", "sidebar.project.clearNotifications": "Bildirimleri temizle",
"sidebar.empty.title": "Açık proje yok",
"sidebar.empty.description": "Başlamak için bir proje açın",
"app.name.desktop": "OpenCode Masaüstü", "app.name.desktop": "OpenCode Masaüstü",
@ -636,6 +755,7 @@ export const dict = {
"settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.", "settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.",
"settings.general.section.appearance": "Görünüm", "settings.general.section.appearance": "Görünüm",
"settings.general.section.advanced": "Gelişmiş",
"settings.general.section.notifications": "Sistem bildirimleri", "settings.general.section.notifications": "Sistem bildirimleri",
"settings.general.section.updates": "Güncellemeler", "settings.general.section.updates": "Güncellemeler",
"settings.general.section.sounds": "Ses efektleri", "settings.general.section.sounds": "Ses efektleri",
@ -644,6 +764,11 @@ export const dict = {
"settings.general.row.language.title": "Dil", "settings.general.row.language.title": "Dil",
"settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin", "settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
"settings.general.row.shell.title": "Terminal Kabuğu",
"settings.general.row.shell.description":
"Terminalinizde kullanılacak kabuğu seçin. Uyumlu kabuklar, ajan araç çağrılarında da kullanılır.",
"settings.general.row.shell.autoDefault": "Otomatik (Varsayılan)",
"settings.general.row.shell.terminalOnly": "yalnızca terminal",
"settings.general.row.appearance.title": "Görünüm", "settings.general.row.appearance.title": "Görünüm",
"settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin", "settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
"settings.general.row.colorScheme.title": "Renk şeması", "settings.general.row.colorScheme.title": "Renk şeması",
@ -653,8 +778,8 @@ export const dict = {
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.", "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
"settings.general.row.font.title": "Kod Yazı Tipi", "settings.general.row.font.title": "Kod Yazı Tipi",
"settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin", "settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminal yazı tipi",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Terminalde kullanılan yazı tipini özelleştirin",
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi", "settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
"settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin", "settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
"settings.general.row.followup.title": "Takip davranışı", "settings.general.row.followup.title": "Takip davranışı",
@ -662,6 +787,21 @@ export const dict = {
"Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin", "Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
"settings.general.row.followup.option.queue": "Sıra", "settings.general.row.followup.option.queue": "Sıra",
"settings.general.row.followup.option.steer": "Yönlendir", "settings.general.row.followup.option.steer": "Yönlendir",
"settings.general.row.showFileTree.title": "Dosya ağacı",
"settings.general.row.showFileTree.description": "Oturumlarda dosya ağacı panelini göster",
"settings.general.row.showNavigation.title": "Gezinme denetimleri",
"settings.general.row.showNavigation.description": "Masaüstü başlık çubuğunda geri ve ileri düğmelerini göster",
"settings.general.row.showSearch.title": "Komut paleti",
"settings.general.row.showSearch.description": "Başlık çubuğunda arama ve komut paleti düğmesini göster",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Masaüstü başlık çubuğunda terminal düğmesini göster",
"settings.general.row.showStatus.title": "Sunucu durumu",
"settings.general.row.showStatus.description": "Başlık çubuğunda sunucu durumu düğmesini göster",
"settings.general.row.mobileTitlebarBottom.title": "Alt gezinme",
"settings.general.row.mobileTitlebarBottom.description":
"Mobil cihazlarda başlık çubuğunu ve oturum sekmelerini ekranın altına yerleştir",
"settings.general.row.showCustomAgents.title": "Özel ajanlar",
"settings.general.row.showCustomAgents.description": "Yazma alanında ajan seçiciyi göster",
"settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster", "settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
"settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle", "settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
"settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet", "settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",
@ -670,6 +810,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet", "settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster", "Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
"settings.general.row.newLayoutDesigns.title": "Yeni düzen ve tasarımlar",
"settings.general.row.newLayoutDesigns.description":
"Yeniden tasarlanan düzeni, ana sayfayı, yazma alanını ve oturum arayüzünü etkinleştir",
"settings.general.row.pinchZoom.title": "Sıkıştırarak yakınlaştır",
"settings.general.row.pinchZoom.description":
"İzleme dörtgeninde sıkıştırma ve Ctrl-kaydırma hareketleriyle yakınlaştırmaya izin ver",
"settings.general.row.wayland.title": "Yerel Wayland kullan", "settings.general.row.wayland.title": "Yerel Wayland kullan",
"settings.general.row.wayland.description": "settings.general.row.wayland.description":
@ -686,6 +832,8 @@ export const dict = {
"settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle", "settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
"settings.updates.action.checkNow": "Şimdi kontrol et", "settings.updates.action.checkNow": "Şimdi kontrol et",
"settings.updates.action.checking": "Kontrol ediliyor...", "settings.updates.action.checking": "Kontrol ediliyor...",
"settings.updates.action.downloading": "İndiriliyor...",
"settings.updates.action.installing": "Kuruluyor...",
"settings.updates.toast.latest.title": "Güncelsiniz", "settings.updates.toast.latest.title": "Güncelsiniz",
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.", "settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",

View file

@ -48,6 +48,7 @@ export const dict = {
"command.session.new": "Нова сесія", "command.session.new": "Нова сесія",
"command.file.open": "Відкрити файл", "command.file.open": "Відкрити файл",
"command.tab.close": "Закрити вкладку", "command.tab.close": "Закрити вкладку",
"command.tab.reopenClosed": "Повторно відкрити закриту вкладку",
"command.context.addSelection": "Додати виділення до контексту", "command.context.addSelection": "Додати виділення до контексту",
"command.context.addSelection.description": "Додати вибрані рядки з поточного файлу", "command.context.addSelection.description": "Додати вибрані рядки з поточного файлу",
"command.input.focus": "Фокус на полі введення", "command.input.focus": "Фокус на полі введення",
@ -212,6 +213,11 @@ export const dict = {
"model.input.video": "відео", "model.input.video": "відео",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Дозволяє: {{inputs}}", "model.tooltip.allows": "Дозволяє: {{inputs}}",
"model.tooltip.context.label": "Контекст",
"model.tooltip.inputs": "Вхідні дані",
"model.tooltip.model": "Модель",
"model.tooltip.provider": "Провайдер",
"model.tooltip.reasoning": "Мислення",
"model.tooltip.reasoning.allowed": "Підтримує мислення", "model.tooltip.reasoning.allowed": "Підтримує мислення",
"model.tooltip.reasoning.none": "Без мислення", "model.tooltip.reasoning.none": "Без мислення",
"model.tooltip.context": "Ліміт контексту {{limit}}", "model.tooltip.context": "Ліміт контексту {{limit}}",
@ -222,6 +228,7 @@ export const dict = {
"common.loading": "Завантаження", "common.loading": "Завантаження",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Скасувати", "common.cancel": "Скасувати",
"common.clear": "Очистити",
"common.open": "Відкрити", "common.open": "Відкрити",
"common.connect": "Підключити", "common.connect": "Підключити",
"common.disconnect": "Відключити", "common.disconnect": "Відключити",
@ -354,6 +361,75 @@ export const dict = {
"dialog.server.menu.delete": "Видалити", "dialog.server.menu.delete": "Видалити",
"dialog.server.current": "Поточний сервер", "dialog.server.current": "Поточний сервер",
"dialog.server.status.default": "За замовчуванням", "dialog.server.status.default": "За замовчуванням",
"wsl.server.add": "Додати сервер WSL",
"wsl.server.addShort": "Додати WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Сервер WSL",
"wsl.server.retryStart": "Повторити запуск",
"wsl.server.updating": "Оновлення...",
"wsl.onboarding.step.distro": "Вибрати дистрибутив",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Перевірка WSL...",
"wsl.onboarding.restartRequired": "Щоб завершити встановлення WSL, потрібно перезапустити Windows.",
"wsl.onboarding.ready": "WSL готова до роботи.",
"wsl.onboarding.required": "Для продовження потрібна WSL.",
"wsl.onboarding.checkingDistros": "Перевірка дистрибутивів...",
"wsl.onboarding.installingDistro": "Встановлення {{distro}}...",
"wsl.onboarding.checkingDistro": "Перевірка {{distro}}...",
"wsl.onboarding.listingDistros": "Отримання списку дистрибутивів...",
"wsl.onboarding.distroReady": "{{distro}} готовий до роботи.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ще не встановлено.",
"wsl.onboarding.openDistroOnce": "Відкрийте {{distro}} один раз, щоб завершити налаштування.",
"wsl.onboarding.finishingDistro": "Завершення налаштування {{distro}}.",
"wsl.onboarding.pickDistro": "Виберіть дистрибутив або встановіть один із наведених нижче.",
"wsl.onboarding.checkingOpencode": "Перевірка OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Перевірка OpenCode у {{distro}}...",
"wsl.onboarding.updatingOpencode": "Оновлення OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Оновлення OpenCode у {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Оновіть OpenCode у {{distro}}.",
"wsl.onboarding.updateOpencode": "Оновити OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode готовий до роботи у {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode готовий до роботи.",
"wsl.onboarding.installOpencodeIn": "Встановіть OpenCode у {{distro}}.",
"wsl.onboarding.installOpencode": "Встановити OpenCode",
"wsl.onboarding.chooseDistroFirst": "Спочатку виберіть дистрибутив.",
"wsl.onboarding.loadFailed": "Не вдалося завантажити стан WSL.",
"wsl.onboarding.loading": "Завантаження...",
"wsl.onboarding.installedDistros": "Встановлені дистрибутиви",
"wsl.onboarding.checkAgain": "Перевірити ще раз",
"wsl.onboarding.distroStatus.ready": "Готовий",
"wsl.onboarding.distroStatus.checking": "Перевірка...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не встановлено",
"wsl.onboarding.distroStatus.missingTools": "Немає bash і curl",
"wsl.onboarding.distroStatus.unsupported": "Не підтримується · Використовуйте WSL 2",
"wsl.onboarding.needAnotherDistro": "Потрібен інший дистрибутив?",
"wsl.onboarding.needAnotherDistroHint": "Встановіть дистрибутив Linux із каталогу WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL не встановлено",
"wsl.onboarding.wslNotInstalled.description":
"Для додавання сервера WSL в OpenCode потрібна WSL (Підсистема Windows для Linux)",
"wsl.onboarding.wslUnavailable.title": "WSL недоступна",
"wsl.onboarding.wslUnavailable.description": "OpenCode не вдалося перевірити WSL на цьому комп'ютері.",
"wsl.onboarding.installWsl": "Встановити WSL",
"wsl.onboarding.windowsRestartRequired":
"Перезапустіть Windows, щоб завершити встановлення WSL, а потім знову відкрийте OpenCode.",
"wsl.onboarding.next": "Далі",
"wsl.onboarding.refresh": "Оновити",
"wsl.onboarding.allDistrosAdded": "Усі встановлені дистрибутиви вже додано.",
"wsl.onboarding.noDistros": "Дистрибутивів поки не виявлено.",
"wsl.onboarding.install": "Встановити",
"wsl.onboarding.installing": "Встановлення...",
"wsl.onboarding.installDistro": "Встановити дистрибутив",
"wsl.onboarding.searchDistros": "Пошук дистрибутивів",
"wsl.onboarding.wsl2Required": "Для роботи потрібна WSL 2.",
"wsl.onboarding.toolsRequired": "Для цього дистрибутива потрібні bash і curl.",
"wsl.onboarding.openTerminal": "Відкрити термінал",
"wsl.onboarding.path": "Шлях: {{path}}",
"wsl.onboarding.notFound": "не знайдено",
"wsl.onboarding.version": "Версія: {{version}}",
"wsl.onboarding.unknown": "невідомо",
"wsl.onboarding.desktopVersion": "десктопний застосунок {{version}}",
"wsl.onboarding.versionMismatch": "Встановлена версія не відповідає версії десктопного застосунку.",
"wsl.onboarding.adding": "Додавання...",
"server.row.noUsername": "без імені користувача", "server.row.noUsername": "без імені користувача",
"dialog.project.edit.title": "Редагувати проєкт", "dialog.project.edit.title": "Редагувати проєкт",
@ -473,10 +549,12 @@ export const dict = {
"error.page.title": "Щось пішло не так", "error.page.title": "Щось пішло не так",
"error.page.description": "Під час завантаження програми сталася помилка.", "error.page.description": "Під час завантаження програми сталася помилка.",
"error.page.description.localServerStartup": "Під час запуску локального сервера сталася помилка.",
"error.page.details.label": "Деталі помилки", "error.page.details.label": "Деталі помилки",
"error.page.action.restart": "Перезапустити", "error.page.action.restart": "Перезапустити",
"error.page.action.report": "Повідомити про помилку", "error.page.action.report": "Повідомити про помилку",
"error.page.action.reported": "Помилку повідомлено", "error.page.action.reported": "Помилку повідомлено",
"error.page.action.exportLogs": "Експортувати журнали",
"error.page.action.checking": "Перевірка...", "error.page.action.checking": "Перевірка...",
"error.page.action.checkUpdates": "Перевірити оновлення", "error.page.action.checkUpdates": "Перевірити оновлення",
"error.page.action.updateTo": "Оновити до {{version}}", "error.page.action.updateTo": "Оновити до {{version}}",
@ -533,11 +611,31 @@ export const dict = {
"home.recentProjects": "Нещодавні проєкти", "home.recentProjects": "Нещодавні проєкти",
"home.empty.title": "Немає нещодавніх проєктів", "home.empty.title": "Немає нещодавніх проєктів",
"home.empty.description": "Почніть, відкривши локальний проєкт", "home.empty.description": "Почніть, відкривши локальний проєкт",
"home.title": "Головна",
"home.projects": "Проєкти",
"home.project.add": "Додати проєкт",
"home.recentlyClosed": "Нещодавно закриті",
"home.server.collapse": "Згорнути проєкти сервера",
"home.server.expand": "Розгорнути проєкти сервера",
"home.sessions.search.placeholder": "Пошук сесій",
"home.sessions.search.placeholder.scoped": "Пошук сесій у {{scope}}",
"home.sessions.search.sessions": "Сесії",
"home.sessions.search.noResults": "Сесій за запитом {{query}} не знайдено",
"home.sessions.empty": "Тут поки нічого немає",
"home.sessions.empty.description": "Створіть сесію, щоб почати",
"home.sessions.group.today": "Сьогодні",
"home.sessions.group.yesterday": "Учора",
"home.sessions.group.older": "Раніше",
"session.tab.session": "Сесія", "session.tab.session": "Сесія",
"session.tab.review": "Огляд", "session.tab.review": "Огляд",
"session.tab.context": "Контекст", "session.tab.context": "Контекст",
"session.tab.unknown": "Невідома сесія",
"session.panel.reviewAndFiles": "Огляд і файли", "session.panel.reviewAndFiles": "Огляд і файли",
"session.error.notFound": "Цю сесію не знайдено",
"session.error.notFound.description": "Ця вкладка посилається на сесію, якої більше немає на цьому сервері.",
"session.error.notFound.closeTab": "Закрити вкладку",
"session.error.serverConnection": "Не вдається підключитися до цього сервера",
"session.review.filesChanged": "Змінено файлів: {{count}}", "session.review.filesChanged": "Змінено файлів: {{count}}",
"session.review.change.one": "Зміна", "session.review.change.one": "Зміна",
"session.review.change.other": "Зміни", "session.review.change.other": "Зміни",
@ -570,6 +668,10 @@ export const dict = {
"session.todo.expand": "Розгорнути", "session.todo.expand": "Розгорнути",
"session.todo.progress": "Виконано {{done}} з {{total}} завдань", "session.todo.progress": "Виконано {{done}} з {{total}} завдань",
"session.question.progress": "{{current}} з {{total}} запитань", "session.question.progress": "{{current}} з {{total}} запитань",
"session.question.minimize": "Згорнути запитання",
"session.question.restore": "Відновити запитання",
"session.question.pending.one": "{{count}} запитання очікує відповіді",
"session.question.pending.other": "Запитання, що очікують відповіді: {{count}}",
"session.followupDock.summary.one": "{{count}} повідомлення в черзі", "session.followupDock.summary.one": "{{count}} повідомлення в черзі",
"session.followupDock.summary.other": "{{count}} повідомлень у черзі", "session.followupDock.summary.other": "{{count}} повідомлень у черзі",
"session.followupDock.sendNow": "Надіслати зараз", "session.followupDock.sendNow": "Надіслати зараз",
@ -583,9 +685,16 @@ export const dict = {
"session.revertDock.restore": "Відновити повідомлення", "session.revertDock.restore": "Відновити повідомлення",
"session.new.title": "Створити що завгодно", "session.new.title": "Створити що завгодно",
"session.new.project.new": "Новий проєкт",
"session.new.project.search": "Пошук проєктів",
"session.new.project.add": "Додати проєкт",
"session.new.worktree.main": "Основна гілка", "session.new.worktree.main": "Основна гілка",
"session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})", "session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})",
"session.new.worktree.create": "Створити нове робоче дерево", "session.new.worktree.create": "Створити нове робоче дерево",
"session.new.workspace.runIn": "Запустити сесію в",
"session.new.workspace.triggerLocal": "Локально",
"session.new.workspace.local": "Локальний репозиторій",
"session.new.workspace.existing": "Робоча область…",
"session.new.lastModified": "Востаннє змінено", "session.new.lastModified": "Востаннє змінено",
"session.header.search.placeholder": "Пошук {{project}}", "session.header.search.placeholder": "Пошук {{project}}",
@ -773,6 +882,11 @@ export const dict = {
"settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу", "settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу",
"settings.general.row.showStatus.title": "Статус сервера", "settings.general.row.showStatus.title": "Статус сервера",
"settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку", "settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку",
"settings.general.row.mobileTitlebarBottom.title": "Нижня навігація",
"settings.general.row.mobileTitlebarBottom.description":
"Розмістити заголовок і вкладки сесії внизу екрана на мобільних пристроях",
"settings.general.row.showCustomAgents.title": "Користувацькі агенти",
"settings.general.row.showCustomAgents.description": "Показувати вибір агента в редакторі запиту",
"settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення", "settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення",
"settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі", "settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі",
"settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки", "settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки",
@ -781,6 +895,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування", "settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі", "Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі",
"settings.general.row.newLayoutDesigns.title": "Новий макет і дизайн",
"settings.general.row.newLayoutDesigns.description":
"Увімкнути оновлений макет та інтерфейси головної сторінки, редактора запитів і сесії",
"settings.general.row.pinchZoom.title": "Масштабування щипком",
"settings.general.row.pinchZoom.description":
"Дозволити масштабування жестом щипка на трекпаді та прокручуванням із Ctrl",
"settings.general.row.wayland.title": "Використовувати нативний Wayland", "settings.general.row.wayland.title": "Використовувати нативний Wayland",
"settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.", "settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.",
@ -796,6 +916,8 @@ export const dict = {
"settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні", "settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні",
"settings.updates.action.checkNow": "Перевірити зараз", "settings.updates.action.checkNow": "Перевірити зараз",
"settings.updates.action.checking": "Перевірка...", "settings.updates.action.checking": "Перевірка...",
"settings.updates.action.downloading": "Завантаження...",
"settings.updates.action.installing": "Встановлення...",
"settings.updates.toast.latest.title": "У вас актуальна версія", "settings.updates.toast.latest.title": "У вас актуальна версія",
"settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.", "settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.",
"sound.option.none": "Немає", "sound.option.none": "Немає",

View file

@ -28,6 +28,9 @@ export const dict = {
"command.sidebar.toggle": "切换侧边栏", "command.sidebar.toggle": "切换侧边栏",
"command.project.open": "打开项目", "command.project.open": "打开项目",
"command.project.previous": "上一个项目",
"command.project.next": "下一个项目",
"command.project.index": "切换到项目 {{index}}",
"command.provider.connect": "连接提供商", "command.provider.connect": "连接提供商",
@ -56,6 +59,7 @@ export const dict = {
"command.file.open": "打开文件", "command.file.open": "打开文件",
"command.tab.close": "关闭标签页", "command.tab.close": "关闭标签页",
"command.tab.reopenClosed": "重新打开已关闭的标签页",
"command.context.addSelection": "将所选内容添加到上下文", "command.context.addSelection": "将所选内容添加到上下文",
"command.context.addSelection.description": "添加当前文件中选中的行", "command.context.addSelection.description": "添加当前文件中选中的行",
@ -230,6 +234,11 @@ export const dict = {
"model.input.video": "视频", "model.input.video": "视频",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "支持:{{inputs}}", "model.tooltip.allows": "支持:{{inputs}}",
"model.tooltip.context.label": "上下文",
"model.tooltip.inputs": "输入",
"model.tooltip.model": "模型",
"model.tooltip.provider": "提供商",
"model.tooltip.reasoning": "推理",
"model.tooltip.reasoning.allowed": "支持推理", "model.tooltip.reasoning.allowed": "支持推理",
"model.tooltip.reasoning.none": "不支持推理", "model.tooltip.reasoning.none": "不支持推理",
"model.tooltip.context": "上下文上限 {{limit}}", "model.tooltip.context": "上下文上限 {{limit}}",
@ -240,6 +249,7 @@ export const dict = {
"common.loading": "加载中", "common.loading": "加载中",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "取消", "common.cancel": "取消",
"common.clear": "清除",
"common.connect": "连接", "common.connect": "连接",
"common.disconnect": "断开连接", "common.disconnect": "断开连接",
"common.continue": "提交", "common.continue": "提交",
@ -257,6 +267,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "按 esc 退出", "prompt.mode.shell.exit": "按 esc 退出",
"session.child.promptDisabled": "无法向子智能体会话发送提示词。",
"session.child.backToParent": "返回主会话。",
"prompt.example.1": "修复代码库中的一个 TODO", "prompt.example.1": "修复代码库中的一个 TODO",
"prompt.example.2": "这个项目的技术栈是什么?", "prompt.example.2": "这个项目的技术栈是什么?",
"prompt.example.3": "修复失败的测试", "prompt.example.3": "修复失败的测试",
@ -359,6 +371,73 @@ export const dict = {
"dialog.server.menu.delete": "删除", "dialog.server.menu.delete": "删除",
"dialog.server.current": "当前服务器", "dialog.server.current": "当前服务器",
"dialog.server.status.default": "默认", "dialog.server.status.default": "默认",
"wsl.server.add": "添加 WSL 服务器",
"wsl.server.addShort": "添加 WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL 服务器",
"wsl.server.retryStart": "重试启动",
"wsl.server.updating": "正在更新...",
"wsl.onboarding.step.distro": "选择发行版",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "正在检查 WSL...",
"wsl.onboarding.restartRequired": "Windows 需要重启才能完成 WSL 安装。",
"wsl.onboarding.ready": "WSL 已就绪。",
"wsl.onboarding.required": "需要 WSL 才能继续。",
"wsl.onboarding.checkingDistros": "正在检查发行版...",
"wsl.onboarding.installingDistro": "正在安装 {{distro}}...",
"wsl.onboarding.checkingDistro": "正在检查 {{distro}}...",
"wsl.onboarding.listingDistros": "正在列出发行版...",
"wsl.onboarding.distroReady": "{{distro}} 已就绪。",
"wsl.onboarding.distroNotInstalled": "{{distro}} 尚未安装。",
"wsl.onboarding.openDistroOnce": "打开一次 {{distro}} 以完成设置。",
"wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的设置。",
"wsl.onboarding.pickDistro": "选择一个发行版,或在下方安装。",
"wsl.onboarding.checkingOpencode": "正在检查 OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "正在检查 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updatingOpencode": "正在更新 OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。",
"wsl.onboarding.updateOpencode": "更新 OpenCode",
"wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就绪。",
"wsl.onboarding.opencodeReady": "OpenCode 已就绪。",
"wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安装 OpenCode。",
"wsl.onboarding.installOpencode": "安装 OpenCode",
"wsl.onboarding.chooseDistroFirst": "请先选择发行版。",
"wsl.onboarding.loadFailed": "加载 WSL 状态失败。",
"wsl.onboarding.loading": "加载中...",
"wsl.onboarding.installedDistros": "已安装的发行版",
"wsl.onboarding.checkAgain": "重新检查",
"wsl.onboarding.distroStatus.ready": "已就绪",
"wsl.onboarding.distroStatus.checking": "检查中...",
"wsl.onboarding.distroStatus.opencodeMissing": "未安装 OpenCode",
"wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl",
"wsl.onboarding.distroStatus.unsupported": "不支持 · 请使用 WSL 2",
"wsl.onboarding.needAnotherDistro": "需要其他发行版?",
"wsl.onboarding.needAnotherDistroHint": "从 WSL 目录安装 Linux 发行版",
"wsl.onboarding.wslNotInstalled.title": "未安装 WSL",
"wsl.onboarding.wslNotInstalled.description": "OpenCode 需要 WSLWindows Subsystem for Linux才能添加 WSL 服务器",
"wsl.onboarding.wslUnavailable.title": "WSL 不可用",
"wsl.onboarding.wslUnavailable.description": "OpenCode 无法验证此设备上的 WSL。",
"wsl.onboarding.installWsl": "安装 WSL",
"wsl.onboarding.windowsRestartRequired": "重启 Windows 以完成 WSL 安装,然后重新打开 OpenCode。",
"wsl.onboarding.next": "下一步",
"wsl.onboarding.refresh": "刷新",
"wsl.onboarding.allDistrosAdded": "所有已安装的发行版均已添加。",
"wsl.onboarding.noDistros": "尚未检测到发行版。",
"wsl.onboarding.install": "安装",
"wsl.onboarding.installing": "正在安装...",
"wsl.onboarding.installDistro": "安装发行版",
"wsl.onboarding.searchDistros": "搜索发行版",
"wsl.onboarding.wsl2Required": "需要 WSL 2。",
"wsl.onboarding.toolsRequired": "此发行版需要 bash 和 curl。",
"wsl.onboarding.openTerminal": "打开终端",
"wsl.onboarding.path": "路径:{{path}}",
"wsl.onboarding.notFound": "未找到",
"wsl.onboarding.version": "版本:{{version}}",
"wsl.onboarding.unknown": "未知",
"wsl.onboarding.desktopVersion": "桌面版 {{version}}",
"wsl.onboarding.versionMismatch": "已安装版本与桌面应用版本不一致。",
"wsl.onboarding.adding": "正在添加...",
"dialog.project.edit.title": "编辑项目", "dialog.project.edit.title": "编辑项目",
"dialog.project.edit.name": "名称", "dialog.project.edit.name": "名称",
@ -418,6 +497,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -456,10 +536,12 @@ export const dict = {
"error.page.title": "出了点问题", "error.page.title": "出了点问题",
"error.page.description": "加载应用程序时发生错误。", "error.page.description": "加载应用程序时发生错误。",
"error.page.description.localServerStartup": "启动本地服务器时发生错误。",
"error.page.details.label": "错误详情", "error.page.details.label": "错误详情",
"error.page.action.restart": "重启", "error.page.action.restart": "重启",
"error.page.action.report": "上报错误", "error.page.action.report": "上报错误",
"error.page.action.reported": "错误已上报", "error.page.action.reported": "错误已上报",
"error.page.action.exportLogs": "导出日志",
"error.page.action.checking": "检查中...", "error.page.action.checking": "检查中...",
"error.page.action.checkUpdates": "检查更新", "error.page.action.checkUpdates": "检查更新",
"error.page.action.updateTo": "更新到 {{version}}", "error.page.action.updateTo": "更新到 {{version}}",
@ -506,6 +588,9 @@ export const dict = {
"home.title": "主页", "home.title": "主页",
"home.projects": "项目", "home.projects": "项目",
"home.project.add": "添加项目", "home.project.add": "添加项目",
"home.recentlyClosed": "最近关闭",
"home.server.collapse": "折叠服务器项目",
"home.server.expand": "展开服务器项目",
"home.sessions.search.placeholder": "搜索会话", "home.sessions.search.placeholder": "搜索会话",
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话", "home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话",
"home.sessions.search.sessions": "会话", "home.sessions.search.sessions": "会话",
@ -519,7 +604,12 @@ export const dict = {
"session.tab.session": "会话", "session.tab.session": "会话",
"session.tab.review": "审查", "session.tab.review": "审查",
"session.tab.context": "上下文", "session.tab.context": "上下文",
"session.tab.unknown": "未知会话",
"session.panel.reviewAndFiles": "审查和文件", "session.panel.reviewAndFiles": "审查和文件",
"session.error.notFound": "找不到此会话",
"session.error.notFound.description": "此标签页指向的会话已不存在于此服务器上。",
"session.error.notFound.closeTab": "关闭标签页",
"session.error.serverConnection": "无法连接到此服务器",
"session.review.filesChanged": "{{count}} 个文件变更", "session.review.filesChanged": "{{count}} 个文件变更",
"session.review.change.one": "更改", "session.review.change.one": "更改",
"session.review.change.other": "更改", "session.review.change.other": "更改",
@ -528,6 +618,8 @@ export const dict = {
"session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改", "session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改",
"session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用", "session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用",
"session.review.noChanges": "无更改", "session.review.noChanges": "无更改",
"session.review.noUncommittedChanges": "尚无未提交的更改",
"session.review.noBranchChanges": "尚无分支更改",
"session.files.selectToOpen": "选择要打开的文件", "session.files.selectToOpen": "选择要打开的文件",
"session.files.all": "所有文件", "session.files.all": "所有文件",
"session.files.empty": "无文件", "session.files.empty": "无文件",
@ -541,6 +633,10 @@ export const dict = {
"session.todo.title": "待办事项", "session.todo.title": "待办事项",
"session.todo.collapse": "折叠", "session.todo.collapse": "折叠",
"session.todo.expand": "展开", "session.todo.expand": "展开",
"session.question.minimize": "最小化问题",
"session.question.restore": "恢复问题",
"session.question.pending.one": "{{count}} 个待处理问题",
"session.question.pending.other": "{{count}} 个待处理问题",
"session.followupDock.summary.one": "{{count}} 条排队消息", "session.followupDock.summary.one": "{{count}} 条排队消息",
"session.followupDock.summary.other": "{{count}} 条排队消息", "session.followupDock.summary.other": "{{count}} 条排队消息",
"session.followupDock.sendNow": "立即发送", "session.followupDock.sendNow": "立即发送",
@ -553,9 +649,16 @@ export const dict = {
"session.revertDock.expand": "展开已回滚消息", "session.revertDock.expand": "展开已回滚消息",
"session.revertDock.restore": "恢复消息", "session.revertDock.restore": "恢复消息",
"session.new.title": "构建任何东西", "session.new.title": "构建任何东西",
"session.new.project.new": "新建项目",
"session.new.project.search": "搜索项目",
"session.new.project.add": "添加项目",
"session.new.worktree.main": "主分支", "session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支({{branch}}", "session.new.worktree.mainWithBranch": "主分支({{branch}}",
"session.new.worktree.create": "创建新的 worktree", "session.new.worktree.create": "创建新的 worktree",
"session.new.workspace.runIn": "运行会话的位置",
"session.new.workspace.triggerLocal": "本地",
"session.new.workspace.local": "本地仓库",
"session.new.workspace.existing": "工作区…",
"session.new.lastModified": "最后修改", "session.new.lastModified": "最后修改",
"session.header.search.placeholder": "搜索 {{project}}", "session.header.search.placeholder": "搜索 {{project}}",
"session.header.searchFiles": "搜索文件", "session.header.searchFiles": "搜索文件",
@ -623,6 +726,8 @@ export const dict = {
"sidebar.project.recentSessions": "最近会话", "sidebar.project.recentSessions": "最近会话",
"sidebar.project.viewAllSessions": "查看全部会话", "sidebar.project.viewAllSessions": "查看全部会话",
"sidebar.project.clearNotifications": "清除通知", "sidebar.project.clearNotifications": "清除通知",
"sidebar.empty.title": "没有打开的项目",
"sidebar.empty.description": "打开一个项目以开始使用",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@ -637,6 +742,7 @@ export const dict = {
"settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。", "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。",
"settings.general.section.appearance": "外观", "settings.general.section.appearance": "外观",
"settings.general.section.advanced": "高级",
"settings.general.section.notifications": "系统通知", "settings.general.section.notifications": "系统通知",
"settings.general.section.updates": "更新", "settings.general.section.updates": "更新",
"settings.general.section.sounds": "音效", "settings.general.section.sounds": "音效",
@ -644,6 +750,10 @@ export const dict = {
"settings.general.section.display": "显示", "settings.general.section.display": "显示",
"settings.general.row.language.title": "语言", "settings.general.row.language.title": "语言",
"settings.general.row.language.description": "更改 OpenCode 的显示语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言",
"settings.general.row.shell.title": "终端 Shell",
"settings.general.row.shell.description": "选择终端使用的 shell。兼容的 shell 也会用于智能体工具调用。",
"settings.general.row.shell.autoDefault": "自动(默认)",
"settings.general.row.shell.terminalOnly": "仅终端",
"settings.general.row.appearance.title": "外观", "settings.general.row.appearance.title": "外观",
"settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观", "settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观",
"settings.general.row.colorScheme.title": "配色方案", "settings.general.row.colorScheme.title": "配色方案",
@ -652,20 +762,38 @@ export const dict = {
"settings.general.row.theme.description": "自定义 OpenCode 的主题。", "settings.general.row.theme.description": "自定义 OpenCode 的主题。",
"settings.general.row.font.title": "代码字体", "settings.general.row.font.title": "代码字体",
"settings.general.row.font.description": "自定义代码块使用的字体", "settings.general.row.font.description": "自定义代码块使用的字体",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "终端字体",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "自定义终端使用的字体",
"settings.general.row.uiFont.title": "界面字体", "settings.general.row.uiFont.title": "界面字体",
"settings.general.row.uiFont.description": "自定义整个界面使用的字体", "settings.general.row.uiFont.description": "自定义整个界面使用的字体",
"settings.general.row.followup.title": "跟进消息行为", "settings.general.row.followup.title": "跟进消息行为",
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待", "settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
"settings.general.row.followup.option.queue": "排队", "settings.general.row.followup.option.queue": "排队",
"settings.general.row.followup.option.steer": "引导", "settings.general.row.followup.option.steer": "引导",
"settings.general.row.showFileTree.title": "文件树",
"settings.general.row.showFileTree.description": "在会话中显示文件树面板",
"settings.general.row.showNavigation.title": "导航控件",
"settings.general.row.showNavigation.description": "在桌面版标题栏中显示前进和后退按钮",
"settings.general.row.showSearch.title": "命令面板",
"settings.general.row.showSearch.description": "在标题栏中显示搜索和命令面板按钮",
"settings.general.row.showTerminal.title": "终端",
"settings.general.row.showTerminal.description": "在桌面版标题栏中显示终端按钮",
"settings.general.row.showStatus.title": "服务器状态",
"settings.general.row.showStatus.description": "在标题栏中显示服务器状态按钮",
"settings.general.row.mobileTitlebarBottom.title": "底部导航",
"settings.general.row.mobileTitlebarBottom.description": "在移动设备上将标题栏和会话标签页置于屏幕底部",
"settings.general.row.showCustomAgents.title": "自定义智能体",
"settings.general.row.showCustomAgents.description": "在输入框中显示智能体选择器",
"settings.general.row.reasoningSummaries.title": "显示推理摘要", "settings.general.row.reasoningSummaries.title": "显示推理摘要",
"settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要", "settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要",
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分", "settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分", "settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分", "settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分", "settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
"settings.general.row.newLayoutDesigns.title": "新版布局和设计",
"settings.general.row.newLayoutDesigns.description": "启用重新设计的布局、主页、输入框和会话界面",
"settings.general.row.pinchZoom.title": "双指缩放",
"settings.general.row.pinchZoom.description": "允许使用触控板双指捏合和 Ctrl-scroll 手势缩放",
"settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.title": "使用原生 Wayland",
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。", "settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。", "settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
@ -678,6 +806,8 @@ export const dict = {
"settings.updates.row.check.description": "手动检查更新并在有更新时安装", "settings.updates.row.check.description": "手动检查更新并在有更新时安装",
"settings.updates.action.checkNow": "立即检查", "settings.updates.action.checkNow": "立即检查",
"settings.updates.action.checking": "正在检查...", "settings.updates.action.checking": "正在检查...",
"settings.updates.action.downloading": "正在下载...",
"settings.updates.action.installing": "正在安装...",
"settings.updates.toast.latest.title": "已是最新版本", "settings.updates.toast.latest.title": "已是最新版本",
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",

View file

@ -27,6 +27,9 @@ export const dict = {
"command.sidebar.toggle": "切換側邊欄", "command.sidebar.toggle": "切換側邊欄",
"command.project.open": "開啟專案", "command.project.open": "開啟專案",
"command.project.previous": "上一個專案",
"command.project.next": "下一個專案",
"command.project.index": "切換至專案 {{index}}",
"command.provider.connect": "連接提供者", "command.provider.connect": "連接提供者",
"command.server.switch": "切換伺服器", "command.server.switch": "切換伺服器",
"command.settings.open": "開啟設定", "command.settings.open": "開啟設定",
@ -49,6 +52,7 @@ export const dict = {
"command.session.new": "新增工作階段", "command.session.new": "新增工作階段",
"command.file.open": "開啟檔案", "command.file.open": "開啟檔案",
"command.tab.close": "關閉分頁", "command.tab.close": "關閉分頁",
"command.tab.reopenClosed": "重新開啟已關閉的分頁",
"command.context.addSelection": "將選取內容加入上下文", "command.context.addSelection": "將選取內容加入上下文",
"command.context.addSelection.description": "加入目前檔案中選取的行", "command.context.addSelection.description": "加入目前檔案中選取的行",
"command.input.focus": "聚焦輸入框", "command.input.focus": "聚焦輸入框",
@ -209,6 +213,11 @@ export const dict = {
"model.input.video": "影片", "model.input.video": "影片",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "支援: {{inputs}}", "model.tooltip.allows": "支援: {{inputs}}",
"model.tooltip.context.label": "上下文",
"model.tooltip.inputs": "輸入",
"model.tooltip.model": "模型",
"model.tooltip.provider": "供應商",
"model.tooltip.reasoning": "推理",
"model.tooltip.reasoning.allowed": "支援推理", "model.tooltip.reasoning.allowed": "支援推理",
"model.tooltip.reasoning.none": "不支援推理", "model.tooltip.reasoning.none": "不支援推理",
"model.tooltip.context": "上下文上限 {{limit}}", "model.tooltip.context": "上下文上限 {{limit}}",
@ -218,6 +227,7 @@ export const dict = {
"common.loading": "載入中", "common.loading": "載入中",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "取消", "common.cancel": "取消",
"common.clear": "清除",
"common.connect": "連線", "common.connect": "連線",
"common.disconnect": "中斷連線", "common.disconnect": "中斷連線",
"common.continue": "提交", "common.continue": "提交",
@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "按 esc 退出", "prompt.mode.shell.exit": "按 esc 退出",
"session.child.promptDisabled": "無法在子代理程式工作階段中輸入提示詞。",
"session.child.backToParent": "返回主要工作階段。",
"prompt.example.1": "修復程式碼庫中的一個 TODO", "prompt.example.1": "修復程式碼庫中的一個 TODO",
"prompt.example.2": "這個專案的技術堆疊是什麼?", "prompt.example.2": "這個專案的技術堆疊是什麼?",
@ -340,6 +352,73 @@ export const dict = {
"dialog.server.menu.delete": "刪除", "dialog.server.menu.delete": "刪除",
"dialog.server.current": "目前伺服器", "dialog.server.current": "目前伺服器",
"dialog.server.status.default": "預設", "dialog.server.status.default": "預設",
"wsl.server.add": "新增 WSL 伺服器",
"wsl.server.addShort": "新增 WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL 伺服器",
"wsl.server.retryStart": "重試啟動",
"wsl.server.updating": "更新中...",
"wsl.onboarding.step.distro": "選擇發行版",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "正在檢查 WSL...",
"wsl.onboarding.restartRequired": "Windows 需要重新啟動,才能完成 WSL 安裝。",
"wsl.onboarding.ready": "WSL 已就緒。",
"wsl.onboarding.required": "需要 WSL 才能繼續。",
"wsl.onboarding.checkingDistros": "正在檢查發行版...",
"wsl.onboarding.installingDistro": "正在安裝 {{distro}}...",
"wsl.onboarding.checkingDistro": "正在檢查 {{distro}}...",
"wsl.onboarding.listingDistros": "正在列出發行版...",
"wsl.onboarding.distroReady": "{{distro}} 已就緒。",
"wsl.onboarding.distroNotInstalled": "尚未安裝 {{distro}}。",
"wsl.onboarding.openDistroOnce": "開啟 {{distro}} 一次以完成設定。",
"wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的設定。",
"wsl.onboarding.pickDistro": "選擇發行版,或在下方安裝。",
"wsl.onboarding.checkingOpencode": "正在檢查 OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "正在檢查 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updatingOpencode": "正在更新 OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。",
"wsl.onboarding.updateOpencode": "更新 OpenCode",
"wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就緒。",
"wsl.onboarding.opencodeReady": "OpenCode 已就緒。",
"wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安裝 OpenCode。",
"wsl.onboarding.installOpencode": "安裝 OpenCode",
"wsl.onboarding.chooseDistroFirst": "請先選擇發行版。",
"wsl.onboarding.loadFailed": "無法載入 WSL 狀態。",
"wsl.onboarding.loading": "載入中...",
"wsl.onboarding.installedDistros": "已安裝的發行版",
"wsl.onboarding.checkAgain": "再次檢查",
"wsl.onboarding.distroStatus.ready": "就緒",
"wsl.onboarding.distroStatus.checking": "檢查中...",
"wsl.onboarding.distroStatus.opencodeMissing": "未安裝 OpenCode",
"wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl",
"wsl.onboarding.distroStatus.unsupported": "不支援 · 請使用 WSL 2",
"wsl.onboarding.needAnotherDistro": "需要其他發行版?",
"wsl.onboarding.needAnotherDistroHint": "從 WSL 目錄安裝 Linux 發行版",
"wsl.onboarding.wslNotInstalled.title": "未安裝 WSL",
"wsl.onboarding.wslNotInstalled.description": "新增 WSL 伺服器前,必須先安裝 WSLWindows Subsystem for Linux",
"wsl.onboarding.wslUnavailable.title": "WSL 無法使用",
"wsl.onboarding.wslUnavailable.description": "OpenCode 無法確認此電腦上的 WSL。",
"wsl.onboarding.installWsl": "安裝 WSL",
"wsl.onboarding.windowsRestartRequired": "重新啟動 Windows 以完成 WSL 安裝,然後重新開啟 OpenCode。",
"wsl.onboarding.next": "下一步",
"wsl.onboarding.refresh": "重新整理",
"wsl.onboarding.allDistrosAdded": "所有已安裝的發行版都已新增。",
"wsl.onboarding.noDistros": "尚未偵測到發行版。",
"wsl.onboarding.install": "安裝",
"wsl.onboarding.installing": "安裝中...",
"wsl.onboarding.installDistro": "安裝發行版",
"wsl.onboarding.searchDistros": "搜尋發行版",
"wsl.onboarding.wsl2Required": "需要 WSL 2。",
"wsl.onboarding.toolsRequired": "此發行版需要 bash 和 curl。",
"wsl.onboarding.openTerminal": "開啟終端機",
"wsl.onboarding.path": "路徑: {{path}}",
"wsl.onboarding.notFound": "找不到",
"wsl.onboarding.version": "版本: {{version}}",
"wsl.onboarding.unknown": "未知",
"wsl.onboarding.desktopVersion": "桌面版 {{version}}",
"wsl.onboarding.versionMismatch": "已安裝版本與桌面應用程式版本不符。",
"wsl.onboarding.adding": "新增中...",
"dialog.project.edit.title": "編輯專案", "dialog.project.edit.title": "編輯專案",
"dialog.project.edit.name": "名稱", "dialog.project.edit.name": "名稱",
@ -402,6 +481,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@ -449,10 +529,12 @@ export const dict = {
"error.page.title": "出了點問題", "error.page.title": "出了點問題",
"error.page.description": "載入應用程式時發生錯誤。", "error.page.description": "載入應用程式時發生錯誤。",
"error.page.description.localServerStartup": "啟動本機伺服器時發生錯誤。",
"error.page.details.label": "錯誤詳情", "error.page.details.label": "錯誤詳情",
"error.page.action.restart": "重新啟動", "error.page.action.restart": "重新啟動",
"error.page.action.report": "回報錯誤", "error.page.action.report": "回報錯誤",
"error.page.action.reported": "已回報錯誤", "error.page.action.reported": "已回報錯誤",
"error.page.action.exportLogs": "匯出日誌",
"error.page.action.checking": "檢查中...", "error.page.action.checking": "檢查中...",
"error.page.action.checkUpdates": "檢查更新", "error.page.action.checkUpdates": "檢查更新",
"error.page.action.updateTo": "更新到 {{version}}", "error.page.action.updateTo": "更新到 {{version}}",
@ -498,17 +580,39 @@ export const dict = {
"home.recentProjects": "最近專案", "home.recentProjects": "最近專案",
"home.empty.title": "沒有最近專案", "home.empty.title": "沒有最近專案",
"home.empty.description": "透過開啟本地專案開始使用", "home.empty.description": "透過開啟本地專案開始使用",
"home.title": "首頁",
"home.projects": "專案",
"home.project.add": "新增專案",
"home.recentlyClosed": "最近關閉",
"home.server.collapse": "收合伺服器專案",
"home.server.expand": "展開伺服器專案",
"home.sessions.search.placeholder": "搜尋工作階段",
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜尋工作階段",
"home.sessions.search.sessions": "工作階段",
"home.sessions.search.noResults": "找不到符合 {{query}} 的工作階段",
"home.sessions.empty": "這裡還沒有內容",
"home.sessions.empty.description": "建立工作階段以開始使用",
"home.sessions.group.today": "今天",
"home.sessions.group.yesterday": "昨天",
"home.sessions.group.older": "更早",
"session.tab.session": "工作階段", "session.tab.session": "工作階段",
"session.tab.review": "審查", "session.tab.review": "審查",
"session.tab.context": "上下文", "session.tab.context": "上下文",
"session.tab.unknown": "未知的工作階段",
"session.panel.reviewAndFiles": "審查與檔案", "session.panel.reviewAndFiles": "審查與檔案",
"session.error.notFound": "找不到此工作階段",
"session.error.notFound.description": "此分頁指向的工作階段已不存在於此伺服器。",
"session.error.notFound.closeTab": "關閉分頁",
"session.error.serverConnection": "無法連線至此伺服器",
"session.review.filesChanged": "{{count}} 個檔案變更", "session.review.filesChanged": "{{count}} 個檔案變更",
"session.review.change.one": "變更", "session.review.change.one": "變更",
"session.review.change.other": "變更", "session.review.change.other": "變更",
"session.review.loadingChanges": "正在載入變更...", "session.review.loadingChanges": "正在載入變更...",
"session.review.empty": "此工作階段暫無變更", "session.review.empty": "此工作階段暫無變更",
"session.review.noChanges": "沒有變更", "session.review.noChanges": "沒有變更",
"session.review.noUncommittedChanges": "尚無未提交的變更",
"session.review.noBranchChanges": "尚無分支變更",
"session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更", "session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更",
"session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更", "session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更",
"session.files.selectToOpen": "選取要開啟的檔案", "session.files.selectToOpen": "選取要開啟的檔案",
@ -525,6 +629,10 @@ export const dict = {
"session.todo.title": "待辦事項", "session.todo.title": "待辦事項",
"session.todo.collapse": "折疊", "session.todo.collapse": "折疊",
"session.todo.expand": "展開", "session.todo.expand": "展開",
"session.question.minimize": "最小化問題",
"session.question.restore": "還原問題",
"session.question.pending.one": "{{count}} 個待處理問題",
"session.question.pending.other": "{{count}} 個待處理問題",
"session.followupDock.summary.one": "{{count}} 則佇列訊息", "session.followupDock.summary.one": "{{count}} 則佇列訊息",
"session.followupDock.summary.other": "{{count}} 則佇列訊息", "session.followupDock.summary.other": "{{count}} 則佇列訊息",
"session.followupDock.sendNow": "立即傳送", "session.followupDock.sendNow": "立即傳送",
@ -538,9 +646,16 @@ export const dict = {
"session.revertDock.restore": "還原訊息", "session.revertDock.restore": "還原訊息",
"session.new.title": "建構任何東西", "session.new.title": "建構任何東西",
"session.new.project.new": "新專案",
"session.new.project.search": "搜尋專案",
"session.new.project.add": "新增專案",
"session.new.worktree.main": "主分支", "session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})", "session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
"session.new.worktree.create": "建立新的 worktree", "session.new.worktree.create": "建立新的 worktree",
"session.new.workspace.runIn": "工作階段執行位置",
"session.new.workspace.triggerLocal": "本機",
"session.new.workspace.local": "本機儲存庫",
"session.new.workspace.existing": "工作區…",
"session.new.lastModified": "最後修改", "session.new.lastModified": "最後修改",
"session.header.search.placeholder": "搜尋 {{project}}", "session.header.search.placeholder": "搜尋 {{project}}",
@ -608,6 +723,8 @@ export const dict = {
"sidebar.project.recentSessions": "最近工作階段", "sidebar.project.recentSessions": "最近工作階段",
"sidebar.project.viewAllSessions": "查看全部工作階段", "sidebar.project.viewAllSessions": "查看全部工作階段",
"sidebar.project.clearNotifications": "清除通知", "sidebar.project.clearNotifications": "清除通知",
"sidebar.empty.title": "未開啟任何專案",
"sidebar.empty.description": "開啟專案以開始使用",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "桌面", "settings.section.desktop": "桌面",
@ -619,6 +736,7 @@ export const dict = {
"settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。", "settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。",
"settings.general.section.appearance": "外觀", "settings.general.section.appearance": "外觀",
"settings.general.section.advanced": "進階",
"settings.general.section.notifications": "系統通知", "settings.general.section.notifications": "系統通知",
"settings.general.section.updates": "更新", "settings.general.section.updates": "更新",
"settings.general.section.sounds": "音效", "settings.general.section.sounds": "音效",
@ -627,6 +745,10 @@ export const dict = {
"settings.general.row.language.title": "語言", "settings.general.row.language.title": "語言",
"settings.general.row.language.description": "變更 OpenCode 的顯示語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言",
"settings.general.row.shell.title": "終端機 Shell",
"settings.general.row.shell.description": "選擇終端機使用的 Shell。相容的 Shell 也會用於代理程式工具呼叫。",
"settings.general.row.shell.autoDefault": "自動(預設)",
"settings.general.row.shell.terminalOnly": "僅限終端機",
"settings.general.row.appearance.title": "外觀", "settings.general.row.appearance.title": "外觀",
"settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀", "settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀",
"settings.general.row.colorScheme.title": "配色方案", "settings.general.row.colorScheme.title": "配色方案",
@ -635,14 +757,28 @@ export const dict = {
"settings.general.row.theme.description": "自訂 OpenCode 的主題。", "settings.general.row.theme.description": "自訂 OpenCode 的主題。",
"settings.general.row.font.title": "程式碼字型", "settings.general.row.font.title": "程式碼字型",
"settings.general.row.font.description": "自訂程式碼區塊使用的字型", "settings.general.row.font.description": "自訂程式碼區塊使用的字型",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "終端機字型",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "自訂終端機使用的字型",
"settings.general.row.uiFont.title": "介面字型", "settings.general.row.uiFont.title": "介面字型",
"settings.general.row.uiFont.description": "自訂整個介面使用的字型", "settings.general.row.uiFont.description": "自訂整個介面使用的字型",
"settings.general.row.followup.title": "後續追問行為", "settings.general.row.followup.title": "後續追問行為",
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待", "settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
"settings.general.row.followup.option.queue": "佇列", "settings.general.row.followup.option.queue": "佇列",
"settings.general.row.followup.option.steer": "引導", "settings.general.row.followup.option.steer": "引導",
"settings.general.row.showFileTree.title": "檔案樹",
"settings.general.row.showFileTree.description": "在工作階段中顯示檔案樹面板",
"settings.general.row.showNavigation.title": "導覽控制項",
"settings.general.row.showNavigation.description": "在桌面標題列中顯示上一頁和下一頁按鈕",
"settings.general.row.showSearch.title": "命令面板",
"settings.general.row.showSearch.description": "在標題列中顯示搜尋和命令面板按鈕",
"settings.general.row.showTerminal.title": "終端機",
"settings.general.row.showTerminal.description": "在桌面標題列中顯示終端機按鈕",
"settings.general.row.showStatus.title": "伺服器狀態",
"settings.general.row.showStatus.description": "在標題列中顯示伺服器狀態按鈕",
"settings.general.row.mobileTitlebarBottom.title": "底部導覽",
"settings.general.row.mobileTitlebarBottom.description": "在行動裝置上將標題列和工作階段分頁置於畫面底部",
"settings.general.row.showCustomAgents.title": "自訂代理程式",
"settings.general.row.showCustomAgents.description": "在輸入區顯示代理程式選擇器",
"settings.general.row.reasoningSummaries.title": "顯示推理摘要", "settings.general.row.reasoningSummaries.title": "顯示推理摘要",
"settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要", "settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要",
@ -650,6 +786,10 @@ export const dict = {
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊", "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊", "settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊", "settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
"settings.general.row.newLayoutDesigns.title": "新版面與設計",
"settings.general.row.newLayoutDesigns.description": "啟用重新設計的版面、首頁、輸入區和工作階段介面",
"settings.general.row.pinchZoom.title": "雙指縮放",
"settings.general.row.pinchZoom.description": "允許使用觸控板雙指開合和 Ctrl-捲動手勢縮放",
"settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.title": "使用原生 Wayland",
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。", "settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。", "settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
@ -663,6 +803,8 @@ export const dict = {
"settings.updates.row.check.description": "手動檢查更新並在有更新時安裝", "settings.updates.row.check.description": "手動檢查更新並在有更新時安裝",
"settings.updates.action.checkNow": "立即檢查", "settings.updates.action.checkNow": "立即檢查",
"settings.updates.action.checking": "檢查中...", "settings.updates.action.checking": "檢查中...",
"settings.updates.action.downloading": "下載中...",
"settings.updates.action.installing": "安裝中...",
"settings.updates.toast.latest.title": "已是最新版本", "settings.updates.toast.latest.title": "已是最新版本",
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",

View file

@ -224,7 +224,26 @@
[data-slot="model-selector-scroll"]::before { [data-slot="model-selector-scroll"]::before {
top: 0; top: 0;
background: linear-gradient(to bottom, var(--v2-background-bg-layer-01), transparent); height: 12px;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
);
} }
[data-slot="manage-models-scroll"]::before { [data-slot="manage-models-scroll"]::before {
@ -239,7 +258,26 @@
[data-slot="model-selector-scroll"]::after { [data-slot="model-selector-scroll"]::after {
bottom: 0; bottom: 0;
background: linear-gradient(to top, var(--v2-background-bg-layer-01), transparent); height: 12px;
background: linear-gradient(
0deg,
color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
);
} }
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) { @supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {

View file

@ -35,7 +35,7 @@ import { usePlatform } from "@/context/platform"
import { DateTime } from "luxon" import { DateTime } from "luxon"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useDirectoryPicker } from "@/components/directory-picker" import { useDirectoryPicker } from "@/components/directory-picker"
import { useSettingsCommand } from "@/components/settings-dialog" import { useSettingsDialog } from "@/components/settings-dialog"
import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server" import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server"
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2" import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
import { ServerConnection, serverName, useServer } from "@/context/server" import { ServerConnection, serverName, useServer } from "@/context/server"
@ -267,7 +267,7 @@ export function NewHome() {
const command = useCommand() const command = useCommand()
const notification = useNotification() const notification = useNotification()
const marked = useMarked() const marked = useMarked()
const openSettings = useSettingsCommand() const openSettings = useSettingsDialog()
let focusSessionSearch: (() => void) | undefined let focusSessionSearch: (() => void) | undefined
const [state, setState] = createStore({ const [state, setState] = createStore({
search: "", search: "",
@ -1278,7 +1278,7 @@ function HomeSessionSearch(props: {
</div> </div>
</div> </div>
</Show> </Show>
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02"> <label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02 focus-within:bg-v2-background-bg-layer-02">
<IconV2 name="magnifying-glass" /> <IconV2 name="magnifying-glass" />
<input <input
ref={input} ref={input}

View file

@ -6,11 +6,13 @@ import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { setNavigate } from "@/utils/notification-click" import { setNavigate } from "@/utils/notification-click"
import { setV2Toast, ToastRegion } from "@/utils/toast" import { setV2Toast, ToastRegion } from "@/utils/toast"
import { useSettingsCommand } from "@/components/settings-dialog"
export default function NewLayout(props: ParentProps) { export default function NewLayout(props: ParentProps) {
const platform = usePlatform() const platform = usePlatform()
const navigate = useNavigate() const navigate = useNavigate()
setNavigate(navigate) setNavigate(navigate)
useSettingsCommand()
createEffect(() => setV2Toast(true)) createEffect(() => setV2Toast(true))

View file

@ -2,8 +2,8 @@ import { createMemo, type Accessor } from "solid-js"
import { useGlobal } from "@/context/global" import { useGlobal } from "@/context/global"
import { useNotification } from "@/context/notification" import { useNotification } from "@/context/notification"
import { usePermission } from "@/context/permission" import { usePermission } from "@/context/permission"
import { sessionPermissionRequest, sessionQuestionRequest } from "@/pages/session/composer/session-request-tree"
import { ServerConnection } from "@/context/server" import { ServerConnection } from "@/context/server"
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
export function useSessionTabAvatarState( export function useSessionTabAvatarState(
server: Accessor<ServerConnection.Key>, server: Accessor<ServerConnection.Key>,
@ -26,15 +26,20 @@ export function useSessionTabAvatarState(
return !permission.autoResponds(item, directory()) return !permission.autoResponds(item, directory())
}) })
}) })
const unread = createMemo(() => { const hasQuestions = createMemo(() => {
if (hasPermissions()) return true const serverSync = sync()
if (!connection()) return false if (!serverSync) return false
return notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0 const [store] = serverSync.child(directory(), { bootstrap: false })
return !!sessionQuestionRequest(store.session, serverSync.session.data.question, sessionId())
}) })
const needsAttention = createMemo(() => hasPermissions() || hasQuestions())
const unread = createMemo(
() => needsAttention() || notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0,
)
const loading = createMemo(() => { const loading = createMemo(() => {
const serverSync = sync() const serverSync = sync()
if (!serverSync) return false if (!serverSync) return false
if (hasPermissions()) return false if (needsAttention()) return false
return serverSync.session.data.session_working(sessionId()) return serverSync.session.data.session_working(sessionId())
}) })
return { unread, loading } return { unread, loading }

View file

@ -6,7 +6,6 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { NewSessionDesignView } from "@/components/session" import { NewSessionDesignView } from "@/components/session"
import { PromptInput } from "@/components/prompt-input" import { PromptInput } from "@/components/prompt-input"
import { StatusPopoverV2 } from "@/components/status-popover" import { StatusPopoverV2 } from "@/components/status-popover"
import { useSettingsCommand } from "@/components/settings-dialog"
import { import {
PromptProjectAddButton, PromptProjectAddButton,
PromptProjectSelector, PromptProjectSelector,
@ -45,7 +44,6 @@ export default function NewSessionPage() {
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>() const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
useComposerCommands() useComposerCommands()
useSettingsCommand()
let inputRef: HTMLDivElement | undefined let inputRef: HTMLDivElement | undefined

View file

@ -35,7 +35,7 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { showToast } from "@/utils/toast" import { showToast } from "@/utils/toast"
import { base64Encode, checksum } from "@opencode-ai/core/util/encode" import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router" import { useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
import { NewSessionView, SessionHeader } from "@/components/session" import { NewSessionView, SessionHeader } from "@/components/session"
import { ErrorPage } from "@/pages/error" import { ErrorPage } from "@/pages/error"
import { CommentsProvider, useComments } from "@/context/comments" import { CommentsProvider, useComments } from "@/context/comments"
@ -56,7 +56,6 @@ import { useSync } from "@/context/sync"
import { useTabs } from "@/context/tabs" import { useTabs } from "@/context/tabs"
import { TerminalProvider, useTerminal } from "@/context/terminal" import { TerminalProvider, useTerminal } from "@/context/terminal"
import { PromptInput } from "@/components/prompt-input" import { PromptInput } from "@/components/prompt-input"
import { useSettingsCommand } from "@/components/settings-dialog"
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit" import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
import { import {
createPromptInputController, createPromptInputController,
@ -153,7 +152,7 @@ export function TargetSessionRouteContent() {
) )
} }
function SessionRouteErrorBoundary( export function SessionRouteErrorBoundary(
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>, props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
) { ) {
const settings = useSettings() const settings = useSettings()
@ -221,7 +220,6 @@ function SessionErrorFallback(props: { error: unknown; sessionID?: string; serve
function ResolvedTargetSessionRoute() { function ResolvedTargetSessionRoute() {
const params = useParams<{ serverKey: string; id: string }>() const params = useParams<{ serverKey: string; id: string }>()
const settings = useSettings()
const tabs = useTabs() const tabs = useTabs()
const sync = useServerSync() const sync = useServerSync()
const serverKey = createMemo(() => requireServerKey(params.serverKey)) const serverKey = createMemo(() => requireServerKey(params.serverKey))
@ -248,16 +246,11 @@ function ResolvedTargetSessionRoute() {
the terminal. Same-workspace tab switches keep it open because warm the terminal. Same-workspace tab switches keep it open because warm
targets resolve synchronously from the sync cache. */} targets resolve synchronously from the sync cache. */}
<Show when={directory()}> <Show when={directory()}>
<Show <SDKProvider directory={targetDirectory}>
when={settings.general.newLayoutDesigns()} <DirectoryDataProvider directory={targetDirectory} server={serverKey}>
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />} <TargetSessionPage />
> </DirectoryDataProvider>
<SDKProvider directory={targetDirectory}> </SDKProvider>
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
<TargetSessionPage />
</DirectoryDataProvider>
</SDKProvider>
</Show>
</Show> </Show>
</TargetServerScopedProviders> </TargetServerScopedProviders>
) )
@ -1021,7 +1014,6 @@ export default function Page() {
} }
useComposerCommands() useComposerCommands()
useSettingsCommand()
useSessionCommands({ useSessionCommands({
navigateMessageByOffset, navigateMessageByOffset,
setActiveMessage, setActiveMessage,

View file

@ -160,8 +160,10 @@ function TimelineDiffSummaryRow(props: { diffs: SummaryDiff[] }) {
> >
<div data-slot="session-turn-diffs-header"> <div data-slot="session-turn-diffs-header">
<span data-slot="session-turn-diffs-label"> <span data-slot="session-turn-diffs-label">
{props.diffs.length} {language.t("ui.sessionTurn.diffs.changed")}{" "} {language.t(
{language.t(props.diffs.length === 1 ? "ui.common.file.one" : "ui.common.file.other")} props.diffs.length === 1 ? "ui.sessionTurn.diffs.changed.one" : "ui.sessionTurn.diffs.changed.other",
{ count: String(props.diffs.length) },
)}
</span> </span>
<DiffChanges changes={props.diffs} /> <DiffChanges changes={props.diffs} />
<Show when={overflow() > 0}> <Show when={overflow() > 0}>

View file

@ -18,8 +18,6 @@ import { createSessionTabs } from "@/pages/session/helpers"
import { extractPromptFromParts } from "@/utils/prompt" import { extractPromptFromParts } from "@/utils/prompt"
import { UserMessage } from "@opencode-ai/sdk/v2" import { UserMessage } from "@opencode-ai/sdk/v2"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
import { useTabs } from "@/context/tabs"
import { requireServerKey } from "@/utils/session-route"
import { createSessionOwnership } from "./session-ownership" import { createSessionOwnership } from "./session-ownership"
export type SessionCommandContext = { export type SessionCommandContext = {
@ -48,7 +46,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const settings = useSettings() const settings = useSettings()
const sync = useSync() const sync = useSync()
const terminal = useTerminal() const terminal = useTerminal()
const sessionTabs = useTabs()
const layout = useLayout() const layout = useLayout()
const navigate = useNavigate() const navigate = useNavigate()
const { params, sessionKey, tabs, view } = useSessionLayout() const { params, sessionKey, tabs, view } = useSessionLayout()
@ -269,6 +266,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
view().terminal.open() view().terminal.open()
} }
const closeTerminal = () => {
const id = terminal.active()
if (!id) return
const last = terminal.all().length === 1
void terminal.close(id)
if (last) view().terminal.close()
}
const chooseMcp = () => { const chooseMcp = () => {
void openDialog( void openDialog(
() => import("@/components/dialog-select-mcp"), () => import("@/components/dialog-select-mcp"),
@ -411,9 +416,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
title: language.t("command.session.new"), title: language.t("command.session.new"),
keybind: "mod+shift+s", keybind: "mod+shift+s",
slash: "new", slash: "new",
onSelect: () => { onSelect: (source) => {
if (params.serverKey) { if (settings.general.newLayoutDesigns()) {
sessionTabs.newDraft({ server: requireServerKey(params.serverKey), directory: sdk().directory }) command.trigger("tab.new", source)
return return
} }
navigate(`/${params.dir}/session`) navigate(`/${params.dir}/session`)
@ -518,6 +523,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
] ]
const terminalCmds = () => [ const terminalCmds = () => [
terminalCommand({
id: "terminal.close",
title: language.t("terminal.close"),
keybind: "mod+w",
hidden: true,
when: (event) => event.target instanceof Element && !!event.target.closest('[data-component="terminal"]'),
onSelect: closeTerminal,
}),
terminalCommand({ terminalCommand({
id: "terminal.new", id: "terminal.new",
title: language.t("command.terminal.new"), title: language.t("command.terminal.new"),

View file

@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/cli", "name": "@opencode-ai/cli",
"version": "1.17.14", "version": "1.17.15",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -46,10 +46,12 @@
"effect": "catalog:", "effect": "catalog:",
"fuzzysort": "catalog:", "fuzzysort": "catalog:",
"jsonc-parser": "3.3.1", "jsonc-parser": "3.3.1",
"open": "10.1.2",
"opentui-spinner": "catalog:", "opentui-spinner": "catalog:",
"semver": "catalog:", "semver": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"strip-ansi": "7.1.2" "strip-ansi": "7.1.2",
"uqr": "0.1.3"
}, },
"devDependencies": { "devDependencies": {
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",

View file

@ -3,27 +3,13 @@ import { Spec } from "../framework/spec"
declare const OPENCODE_CLI_NAME: string | undefined declare const OPENCODE_CLI_NAME: string | undefined
const MiniParams = { const ServerParams = {
continue: Flag.boolean("continue").pipe( standalone: Flag.boolean("standalone").pipe(
Flag.withAlias("c"), Flag.withDescription("Run with a private server instead of the background service"),
Flag.withDescription("Continue the last session"),
Flag.withDefault(false), Flag.withDefault(false),
), ),
session: Flag.string("session").pipe( server: Flag.string("server").pipe(
Flag.withAlias("s"), Flag.withDescription("Connect to a server URL instead of the background service"),
Flag.withDescription("Session ID to continue"),
Flag.optional,
),
fork: Flag.boolean("fork").pipe(
Flag.withDescription("Fork the session when continuing"),
Flag.withDefault(false),
),
replay: Flag.boolean("replay").pipe(
Flag.withDescription("Replay session history on resume and after resize"),
Flag.withDefault(true),
),
replayLimit: Flag.integer("replay-limit").pipe(
Flag.withDescription("Cap visible replay to the newest N messages"),
Flag.optional, Flag.optional,
), ),
} }
@ -31,18 +17,11 @@ const MiniParams = {
export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", { export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", {
description: "OpenCode 2.0 preview command line interface", description: "OpenCode 2.0 preview command line interface",
params: { params: {
...ServerParams,
directory: Argument.string("directory").pipe( directory: Argument.string("directory").pipe(
Argument.withDescription("Directory to start OpenCode in"), Argument.withDescription("Directory to start OpenCode in"),
Argument.optional, Argument.optional,
), ),
standalone: Flag.boolean("standalone").pipe(
Flag.withDescription("Run with a private server instead of the background service"),
Flag.withDefault(false),
),
server: Flag.string("server").pipe(
Flag.withDescription("Connect to a server URL instead of the background service"),
Flag.optional,
),
continue: Flag.boolean("continue").pipe( continue: Flag.boolean("continue").pipe(
Flag.withAlias("c"), Flag.withAlias("c"),
Flag.withDescription("Continue the last session"), Flag.withDescription("Continue the last session"),
@ -75,6 +54,17 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
description: "Debugging and troubleshooting tools", description: "Debugging and troubleshooting tools",
commands: [Spec.make("agents", { description: "List all agents" })], commands: [Spec.make("agents", { description: "List all agents" })],
}), }),
Spec.make("console", {
description: "Manage OpenCode Console access",
commands: [
Spec.make("login", {
description: "Log in to OpenCode Console",
params: {
url: Argument.string("url").pipe(Argument.withDescription("Console server URL"), Argument.optional),
},
}),
],
}),
Spec.make("mcp", { Spec.make("mcp", {
description: "Manage MCP (Model Context Protocol) servers", description: "Manage MCP (Model Context Protocol) servers",
commands: [ commands: [
@ -116,10 +106,28 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
Spec.make("mini", { Spec.make("mini", {
description: "Start the minimal interactive interface", description: "Start the minimal interactive interface",
params: { params: {
...MiniParams, ...ServerParams,
project: Argument.string("project").pipe( continue: Flag.boolean("continue").pipe(
Argument.withDescription("Path to start OpenCode in"), Flag.withAlias("c"),
Argument.optional, Flag.withDescription("Continue the last session"),
Flag.withDefault(false),
),
session: Flag.string("session").pipe(
Flag.withAlias("s"),
Flag.withDescription("Session ID to continue"),
Flag.optional,
),
fork: Flag.boolean("fork").pipe(
Flag.withDescription("Fork the session when continuing"),
Flag.withDefault(false),
),
replay: Flag.boolean("replay").pipe(
Flag.withDescription("Replay session history on resume and after resize"),
Flag.withDefault(true),
),
replayLimit: Flag.integer("replay-limit").pipe(
Flag.withDescription("Cap visible replay to the newest N messages"),
Flag.optional,
), ),
model: Flag.string("model").pipe( model: Flag.string("model").pipe(
Flag.withAlias("m"), Flag.withAlias("m"),
@ -128,16 +136,13 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
), ),
agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional), agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional),
prompt: Flag.string("prompt").pipe(Flag.withDescription("Prompt to use"), Flag.optional), prompt: Flag.string("prompt").pipe(Flag.withDescription("Prompt to use"), Flag.optional),
server: Flag.string("server").pipe(
Flag.withDescription("Connect to a server URL instead of the background service"),
Flag.optional,
),
demo: Flag.boolean("demo").pipe(Flag.withDefault(false), Flag.withHidden), demo: Flag.boolean("demo").pipe(Flag.withDefault(false), Flag.withHidden),
}, },
}), }),
Spec.make("run", { Spec.make("run", {
description: "Run OpenCode with a message", description: "Run OpenCode with a message",
params: { params: {
...ServerParams,
message: Argument.string("message").pipe( message: Argument.string("message").pipe(
Argument.withDescription("Message to send"), Argument.withDescription("Message to send"),
Argument.variadic({ min: 0 }), Argument.variadic({ min: 0 }),
@ -158,7 +163,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
), ),
model: Flag.string("model").pipe( model: Flag.string("model").pipe(
Flag.withAlias("m"), Flag.withAlias("m"),
Flag.withDescription("Model to use in the format provider/model"), Flag.withDescription("Model to use in the format provider/model#variant"),
Flag.optional, Flag.optional,
), ),
agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional), agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional),
@ -172,12 +177,6 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
Flag.atMost(100), Flag.atMost(100),
), ),
title: Flag.string("title").pipe(Flag.withDescription("Session title"), Flag.optional), title: Flag.string("title").pipe(Flag.withDescription("Session title"), Flag.optional),
server: Flag.string("server").pipe(
Flag.withDescription("Connect to a server URL instead of the background service"),
Flag.optional,
),
dir: Flag.string("dir").pipe(Flag.withDescription("Directory to run in"), Flag.optional),
variant: Flag.string("variant").pipe(Flag.withDescription("Model variant"), Flag.optional),
thinking: Flag.boolean("thinking").pipe( thinking: Flag.boolean("thinking").pipe(
Flag.withDescription("Show thinking blocks"), Flag.withDescription("Show thinking blocks"),
Flag.withDefault(false), Flag.withDefault(false),
@ -187,10 +186,6 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
Flag.withDefault(false), Flag.withDefault(false),
), ),
yolo: Flag.boolean("yolo").pipe(Flag.withDefault(false), Flag.withHidden), yolo: Flag.boolean("yolo").pipe(Flag.withDefault(false), Flag.withHidden),
dangerouslySkipPermissions: Flag.boolean("dangerously-skip-permissions").pipe(
Flag.withDefault(false),
Flag.withHidden,
),
}, },
}), }),
Spec.make("service", { Spec.make("service", {
@ -214,6 +209,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
}), }),
], ],
}), }),
Spec.make("link", { description: "Show server connection information" }),
Spec.make("serve", { Spec.make("serve", {
description: "Start the v2 API server", description: "Start the v2 API server",
params: { params: {

View file

@ -20,10 +20,10 @@ export default Runtime.handler(
Effect.fn("cli.api")(function* (input) { Effect.fn("cli.api")(function* (input) {
const options = yield* ServiceConfig.options() const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options) const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options)) const endpoint = found ?? (yield* Service.start(options))
const params = Option.getOrElse(input.param, () => ({})) const params = Option.getOrElse(input.param, () => ({}))
const request = yield* resolveRequest(transport, input.request, params) const request = yield* resolveRequest(endpoint, input.request, params)
const headers = new Headers(transport.headers) const headers = new Headers(Service.headers(endpoint))
for (const header of input.header) { for (const header of input.header) {
const index = header.indexOf(":") const index = header.indexOf(":")
if (index < 1) return yield* Effect.fail(new Error(`Invalid header, expected name:value: ${header}`)) if (index < 1) return yield* Effect.fail(new Error(`Invalid header, expected name:value: ${header}`))
@ -33,7 +33,7 @@ export default Runtime.handler(
if (body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json") if (body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json")
const response = yield* Effect.tryPromise(() => const response = yield* Effect.tryPromise(() =>
fetch(new URL(request.path, transport.url), { fetch(new URL(request.path, endpoint.url), {
method: request.method, method: request.method,
headers, headers,
body, body,
@ -60,7 +60,7 @@ export function rawRequest(input: readonly string[]) {
} }
function resolveRequest( function resolveRequest(
transport: Service.Transport, endpoint: Service.Endpoint,
input: readonly string[], input: readonly string[],
params: Record<string, string>, params: Record<string, string>,
) { ) {
@ -68,7 +68,7 @@ function resolveRequest(
if (raw) return Effect.succeed(raw) if (raw) return Effect.succeed(raw)
if (input.length !== 1) return Effect.fail(new Error("Expected an operation name or an HTTP method and path")) if (input.length !== 1) return Effect.fail(new Error("Expected an operation name or an HTTP method and path"))
return Effect.tryPromise(async () => { return Effect.tryPromise(async () => {
const response = await fetch(new URL("/openapi.json", transport.url), { headers: transport.headers }) const response = await fetch(new URL("/openapi.json", endpoint.url), { headers: Service.headers(endpoint) })
if (!response.ok) throw new Error(`Failed to load OpenAPI document: HTTP ${response.status}`) if (!response.ok) throw new Error(`Failed to load OpenAPI document: HTTP ${response.status}`)
return resolveOperation((await response.json()) as OpenApi, input[0], params) return resolveOperation((await response.json()) as OpenApi, input[0], params)
}) })

View file

@ -0,0 +1,117 @@
import { Cause, Effect, Exit, Option } from "effect"
import { Service } from "@opencode-ai/client/effect"
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
import { AppProcess } from "@opencode-ai/core/process"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
import { createTimelineHost, type TimelineHost } from "../../../ui/timeline"
const integrationID = "opencode"
const location = { directory: process.cwd() }
export default Runtime.handler(
Commands.commands.console.commands.login,
Effect.fn("cli.console.login")(function* (input) {
const timeline = yield* Effect.acquireRelease(
Effect.promise(() => createTimelineHost()),
(value) => request(() => value.close()).pipe(Effect.ignore),
)
const exit = yield* login(timeline, Option.getOrUndefined(input.url)).pipe(
Effect.raceFirst(AppProcess.waitForAbort(timeline.signal)),
Effect.exit,
)
if (Exit.isSuccess(exit)) return
const cancelled = timeline.signal.aborted
yield* request(() => timeline.failure(cancelled ? "Authorization cancelled" : errorMessage(exit.cause))).pipe(
Effect.ignore,
)
process.exitCode = cancelled ? 130 : 1
}),
)
const login = Effect.fn("cli.console.login.run")(function* (timeline: TimelineHost, server?: string) {
yield* request(() => timeline.intro("Log in"))
yield* request(() => timeline.pending("Connecting to OpenCode..."))
const endpoint = yield* Service.start(yield* ServiceConfig.options())
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const found = yield* request((signal) => client.integration.get({ integrationID, location }, { signal }))
const integration = yield* required(found.data, "OpenCode Console integration is unavailable")
const method = yield* required(
integration.methods.find((candidate) => candidate.type === "oauth"),
"OpenCode Console login is unavailable",
)
yield* request(() => timeline.pending("Starting authorization..."))
const started = yield* request((signal) =>
client.integration.connect.oauth(
{
integrationID,
methodID: method.id,
inputs: server ? { server } : {},
location,
},
{ signal },
),
)
const attempt = started.data
yield* Effect.addFinalizer(() =>
request(() =>
client.integration.attempt.cancel(
{ attemptID: attempt.attemptID, location },
{ signal: AbortSignal.timeout(5_000) },
),
).pipe(Effect.ignore),
)
if (attempt.mode !== "auto") yield* Effect.fail(new Error("OpenCode Console requires a device login"))
yield* request(() => timeline.item(`Go to: ${attempt.url}`))
yield* request(() => timeline.item(attempt.instructions))
yield* request(async () => {
const { default: open } = await import("open")
await open(attempt.url)
}).pipe(Effect.ignore)
yield* request(() => timeline.pending("Waiting for authorization..."))
const status = yield* waitForConsoleLogin(client, attempt.attemptID)
if (status.status === "failed") yield* Effect.fail(new Error(status.message))
if (status.status === "expired") yield* Effect.fail(new Error("Device code expired"))
yield* request(() => timeline.success("Connected to OpenCode Console"))
yield* request(() => timeline.outro("Done"))
})
const waitForConsoleLogin = Effect.fn("cli.console.login.wait")(function* (
client: OpenCodeClient,
attemptID: string,
) {
while (true) {
const response = yield* request((signal) =>
client.integration.attempt.status({ attemptID, location }, { signal }),
)
if (response.data.status !== "pending") return response.data
yield* Effect.sleep(500)
}
})
function request<A>(task: (signal: AbortSignal) => Promise<A>) {
return Effect.tryPromise({
try: task,
catch: (cause) => cause,
})
}
function required<A>(value: A | null | undefined, message: string) {
return value === null || value === undefined ? Effect.fail(new Error(message)) : Effect.succeed(value)
}
function errorMessage(cause: Cause.Cause<unknown>) {
const error = Cause.squash(cause)
if (error instanceof Error) return error.message
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
return error.message
}
return String(error)
}

View file

@ -11,8 +11,8 @@ export default Runtime.handler(
Effect.fn("cli.debug.agents")(function* () { Effect.fn("cli.debug.agents")(function* () {
const options = yield* ServiceConfig.options() const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options) const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options)) const endpoint = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers }) const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const response = yield* Effect.promise(() => client.v2.agent.list({ location: { directory: process.cwd() } })) const response = yield* Effect.promise(() => client.v2.agent.list({ location: { directory: process.cwd() } }))
process.stdout.write( process.stdout.write(
JSON.stringify( JSON.stringify(

View file

@ -1,89 +1,50 @@
import { NodeFileSystem } from "@effect/platform-node" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Global } from "@opencode-ai/core/global"
import { run } from "@opencode-ai/tui"
import { loadBuiltinPlugins } from "@opencode-ai/tui/builtins"
import { TuiConfig } from "@opencode-ai/tui/config"
import { Commands } from "../commands" import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime" import { Runtime } from "../../framework/runtime"
import { Effect, Option, Redacted } from "effect" import { Effect, Option } from "effect"
import { Service } from "@opencode-ai/client/effect" import { Server } from "../../services/server"
import { Env } from "../../env"
import { ServiceConfig } from "../../services/service-config"
import { Standalone } from "../../services/standalone"
import { Updater } from "../../services/updater" import { Updater } from "../../services/updater"
export default Runtime.handler(Commands, (input) => export default Runtime.handler(Commands, (input) =>
Effect.gen(function* () { Effect.gen(function* () {
const directory = Option.getOrUndefined(input.directory) const requestedDirectory = Option.getOrUndefined(input.directory)
if (directory !== undefined) process.chdir(directory) if (requestedDirectory !== undefined) process.chdir(requestedDirectory)
const updater = yield* Updater.Service const updater = yield* Updater.Service
yield* updater.check().pipe(Effect.forkScoped) yield* updater.check().pipe(Effect.forkScoped)
const server = Option.getOrUndefined(input.server) const server = yield* Server.resolve({
if (server !== undefined && input.standalone) server: Option.getOrUndefined(input.server),
return yield* Effect.fail(new Error("--server and --standalone cannot be combined")) standalone: input.standalone,
const transport = yield* Effect.gen(function* () {
if (server !== undefined) {
const password = yield* Env.password
const explicit = {
url: server,
headers: password
? { authorization: "Basic " + btoa("opencode:" + Redacted.value(password)) }
: undefined,
} satisfies Service.Transport
// Fail loudly before entering the TUI: an explicit server that is
// unreachable or rejects auth should not present as reconnect churn.
const response = yield* Effect.tryPromise(() =>
fetch(new URL("/api/health", server), { headers: explicit.headers, signal: AbortSignal.timeout(5_000) }),
).pipe(Effect.mapError((cause) => new Error(`Could not reach server at ${server}`, { cause })))
if (response.status === 401)
return yield* Effect.fail(
new Error(
password
? `Server at ${server} rejected the password`
: `Server at ${server} requires a password; set OPENCODE_PASSWORD`,
),
)
if (!response.ok)
return yield* Effect.fail(new Error(`Server at ${server} responded with status ${response.status}`))
return explicit
}
if (input.standalone) return yield* Standalone.transport()
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
return found ?? (yield* Service.start(options))
}) })
const { runTui } = yield* Effect.promise(() => import("../../tui")) const config = TuiConfig.resolve({}, { terminalSuspend: false })
// The TUI re-runs discover whenever its event stream drops. For an explicit let disposeSlots: (() => void) | undefined
// --server or a standalone child the transport is fixed, so reconnects const runFork = Effect.runForkWith(yield* Effect.context())
// retry the same address; for the managed service discovery re-reads the yield* run({
// registration and may start a replacement. server,
const serviceOptions = server === undefined && !input.standalone ? yield* ServiceConfig.options() : undefined args: { continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
// Only startup enforces the CLI version. A reconnect must accept a server config,
// replaced by another client or the two clients will restart it forever. log: (level, message, tags) => {
const reconnectOptions = serviceOptions ? { ...serviceOptions, version: undefined } : undefined const effect =
const discover = reconnectOptions level === "debug"
? () => ? Effect.logDebug(message, tags)
Effect.runPromise( : level === "warn"
Effect.gen(function* () { ? Effect.logWarning(message, tags)
const found = yield* Service.discover(reconnectOptions) : level === "error"
return found ?? (yield* Service.start(reconnectOptions)) ? Effect.logError(message, tags)
}).pipe(Effect.provide(NodeFileSystem.layer)), : Effect.logInfo(message, tags)
) runFork(effect)
: () => Promise.resolve(transport) },
// Restart the managed service in place; start() resolves once the pluginHost: {
// replacement is healthy and the reconnect loop reattaches on its own. async start(pluginInput) {
// Only meaningful in service mode: --server is not ours to restart and a disposeSlots = await loadBuiltinPlugins(pluginInput.api, pluginInput.runtime)
// standalone child cannot be respawned. },
const reload = serviceOptions async dispose() {
? () => disposeSlots?.()
Effect.runPromise( },
Effect.gen(function* () { },
yield* Service.stop(serviceOptions) }).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
yield* Service.start(serviceOptions)
}).pipe(Effect.provide(NodeFileSystem.layer)),
)
: undefined
yield* runTui(
transport,
{ continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
discover,
reload,
)
}), }),
) )

View file

@ -0,0 +1,43 @@
import { EOL } from "os"
import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import { OpenCode } from "@opencode-ai/client/promise"
import { renderUnicodeCompact } from "uqr"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { ServiceConfig } from "../../services/service-config"
export default Runtime.handler(
Commands.commands.link,
Effect.fn("cli.link")(function* () {
const endpoint = yield* Service.start(yield* ServiceConfig.options())
const password = yield* ServiceConfig.password()
const server = yield* Effect.tryPromise(() =>
OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) }).server.get(),
)
const info = { urls: server.urls, username: "opencode", password }
process.stdout.write(
[
"",
` URLs ${info.urls[0] ?? "(none)"}`,
...info.urls.slice(1).map((url) => ` ${url}`),
` Username ${info.username}`,
` Password ${info.password}`,
"",
" Scan to connect",
"",
renderUnicodeCompact(JSON.stringify(info), { border: 2 })
.split(EOL)
.map((line) => " " + line)
.join(EOL),
"",
].join(EOL) + EOL,
)
const hostname = new URL(endpoint.url).hostname
if (!["localhost", "127.0.0.1", "[::1]"].includes(hostname)) return
process.stderr.write(
` Run \`opencode service set hostname 0.0.0.0\` to access the service remotely.${EOL}${EOL}`,
)
}),
)

View file

@ -19,8 +19,8 @@ export default Runtime.handler(
Effect.fn("cli.mcp.auth")(function* (input) { Effect.fn("cli.mcp.auth")(function* (input) {
const options = yield* ServiceConfig.options() const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options) const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options)) const endpoint = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers }) const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const integration = yield* resolveIntegration(client, input.name, location) const integration = yield* resolveIntegration(client, input.name, location)
if (!integration) if (!integration)

View file

@ -11,8 +11,8 @@ export default Runtime.handler(
Effect.fn("cli.mcp.list")(function* () { Effect.fn("cli.mcp.list")(function* () {
const options = yield* ServiceConfig.options() const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options) const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options)) const endpoint = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers }) const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const response = yield* Effect.promise(() => client.v2.mcp.list({ location: { directory: process.cwd() } })) const response = yield* Effect.promise(() => client.v2.mcp.list({ location: { directory: process.cwd() } }))
const servers = (response.data?.data ?? []).toSorted((a, b) => a.name.localeCompare(b.name)) const servers = (response.data?.data ?? []).toSorted((a, b) => a.name.localeCompare(b.name))
if (servers.length === 0) { if (servers.length === 0) {

View file

@ -14,8 +14,8 @@ export default Runtime.handler(
Effect.fn("cli.mcp.logout")(function* (input) { Effect.fn("cli.mcp.logout")(function* (input) {
const options = yield* ServiceConfig.options() const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options) const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options)) const endpoint = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers }) const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const integration = yield* resolveIntegration(client, input.name, location) const integration = yield* resolveIntegration(client, input.name, location)
if (!integration) { if (!integration) {

View file

@ -1,25 +1,17 @@
import { Effect, Option, Redacted } from "effect" import { Effect, Option } from "effect"
import path from "node:path"
import { Commands } from "../commands" import { Commands } from "../commands"
import { Env } from "../../env"
import { Runtime } from "../../framework/runtime" import { Runtime } from "../../framework/runtime"
import { Server } from "../../services/server"
export default Runtime.handler(Commands.commands.mini, (input) => export default Runtime.handler(Commands.commands.mini, (input) =>
Effect.gen(function* () { Effect.gen(function* () {
const { runMini } = yield* Effect.promise(() => import("../../mini")) const { runMini, validateMiniTerminal } = yield* Effect.promise(() => import("../../mini"))
const project = Option.getOrUndefined(input.project) yield* Effect.promise(async () => validateMiniTerminal())
const server = Option.getOrUndefined(input.server) const serverURL = Option.getOrUndefined(input.server)
const password = yield* Env.password const server = yield* Server.resolve({ server: serverURL, standalone: input.standalone })
yield* Effect.promise(() => yield* Effect.promise(() =>
runMini({ runMini({
attach: server, server,
password: password ? Redacted.value(password) : undefined,
directory:
server !== undefined
? project
: project === undefined
? process.cwd()
: path.resolve(process.env.PWD ?? process.cwd(), project),
continue: input.continue, continue: input.continue,
session: Option.getOrUndefined(input.session), session: Option.getOrUndefined(input.session),
fork: input.fork, fork: input.fork,

View file

@ -1,15 +1,19 @@
import { Effect, Option, Redacted } from "effect" import { Effect, Option } from "effect"
import { Commands } from "../commands" import { Commands } from "../commands"
import { Env } from "../../env"
import { Runtime } from "../../framework/runtime" import { Runtime } from "../../framework/runtime"
import { Server } from "../../services/server"
export default Runtime.handler(Commands.commands.run, (input) => export default Runtime.handler(Commands.commands.run, (input) =>
Effect.gen(function* () { Effect.gen(function* () {
const { runNonInteractive } = yield* Effect.promise(() => import("../../mini")) const { runNonInteractive } = yield* Effect.promise(() => import("../../mini"))
const password = yield* Env.password
const separator = process.argv.indexOf("--", 2) const separator = process.argv.indexOf("--", 2)
const server = yield* Server.resolve({
server: Option.getOrUndefined(input.server),
standalone: input.standalone,
})
yield* Effect.promise(() => yield* Effect.promise(() =>
runNonInteractive({ runNonInteractive({
server,
message: [...input.message, ...(separator === -1 ? [] : process.argv.slice(separator + 1))], message: [...input.message, ...(separator === -1 ? [] : process.argv.slice(separator + 1))],
continue: input.continue, continue: input.continue,
session: Option.getOrUndefined(input.session), session: Option.getOrUndefined(input.session),
@ -19,12 +23,8 @@ export default Runtime.handler(Commands.commands.run, (input) =>
format: input.format, format: input.format,
file: [...input.file], file: [...input.file],
title: Option.getOrUndefined(input.title), title: Option.getOrUndefined(input.title),
server: Option.getOrUndefined(input.server),
password: password ? Redacted.value(password) : undefined,
directory: Option.getOrUndefined(input.dir),
variant: Option.getOrUndefined(input.variant),
thinking: input.thinking, thinking: input.thinking,
dangerouslySkipPermissions: input.auto || input.yolo || input.dangerouslySkipPermissions, auto: input.auto || input.yolo,
}), }),
) )
}), }),

View file

@ -1,70 +0,0 @@
import { Service } from "@opencode-ai/client/effect"
import { ClientError, isUnauthorizedError, OpenCode } from "@opencode-ai/client/promise"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { ServerAuth } from "@opencode-ai/server/auth"
import { Effect } from "effect"
import { ServiceConfig } from "./services/service-config"
export type SharedOptions = {
readonly mode: "shared"
readonly command?: ReadonlyArray<string>
}
export type AttachOptions = {
readonly mode: "attach"
readonly url: string
readonly username?: string
readonly password?: string
}
export type Options = SharedOptions | AttachOptions
const attach = Effect.fn("cli.daemon.attach")(function* (options: AttachOptions) {
const transport = {
url: options.url,
headers:
options.password === undefined
? undefined
: ServerAuth.headers({ password: options.password, username: options.username }),
} satisfies Service.Transport
const client = OpenCode.make({ baseUrl: transport.url, headers: transport.headers })
const health = yield* Effect.tryPromise({
try: () => client.health.get({ signal: AbortSignal.timeout(5_000) }),
catch: (cause) => attachError(options, cause),
})
if (health.version !== InstallationVersion)
process.stderr.write(
`Warning: Server at ${options.url} has version ${health.version}; this client is ${InstallationVersion}. Continuing anyway.\n`,
)
return transport
})
const shared = Effect.fn("cli.daemon.shared")(function* (options: SharedOptions) {
const config = yield* ServiceConfig.options()
const service = options.command === undefined ? config : { ...config, command: options.command }
const found = yield* Service.discover(service)
if (found) return found
return yield* Service.start(service)
})
export function transport(options: AttachOptions): ReturnType<typeof attach>
export function transport(options: SharedOptions): ReturnType<typeof shared>
export function transport(options: Options): ReturnType<typeof attach> | ReturnType<typeof shared>
export function transport(options: Options) {
if (options.mode === "attach") return attach(options)
return shared(options)
}
function attachError(options: AttachOptions, cause: unknown) {
if (isUnauthorizedError(cause)) {
return new Error(
options.password === undefined
? `Server at ${options.url} requires authentication; provide a password`
: `Server at ${options.url} rejected the supplied credentials`,
{ cause },
)
}
if (cause instanceof ClientError && cause.reason === "Transport")
return new Error(`Could not reach server at ${options.url}`, { cause })
return new Error(`Server at ${options.url} did not provide a compatible V2 health response`, { cause })
}
export * as Daemon from "./daemon"

View file

@ -24,6 +24,9 @@ const Handlers = Runtime.handlers(Commands, {
debug: { debug: {
agents: () => import("./commands/handlers/debug/agents"), agents: () => import("./commands/handlers/debug/agents"),
}, },
console: {
login: () => import("./commands/handlers/console/login"),
},
mcp: { mcp: {
list: () => import("./commands/handlers/mcp/list"), list: () => import("./commands/handlers/mcp/list"),
add: () => import("./commands/handlers/mcp/add"), add: () => import("./commands/handlers/mcp/add"),
@ -33,6 +36,7 @@ const Handlers = Runtime.handlers(Commands, {
migrate: () => import("./commands/handlers/migrate"), migrate: () => import("./commands/handlers/migrate"),
mini: () => import("./commands/handlers/mini"), mini: () => import("./commands/handlers/mini"),
run: () => import("./commands/handlers/run"), run: () => import("./commands/handlers/run"),
link: () => import("./commands/handlers/link"),
service: { service: {
start: () => import("./commands/handlers/service/start"), start: () => import("./commands/handlers/service/start"),
restart: () => import("./commands/handlers/service/restart"), restart: () => import("./commands/handlers/service/restart"),

View file

@ -1,7 +1,8 @@
export { runMini, mergeInput as mergeInteractiveInput, type MiniCommandInput } from "./mini" export { runMini, validateMiniTerminal, mergeInput as mergeInteractiveInput, type MiniCommandInput } from "./mini"
export { export {
runNonInteractive, runNonInteractive,
mergeInput as mergeNonInteractiveInput, mergeInput as mergeNonInteractiveInput,
pickRunModel, pickRunModel,
parseRunModel,
type RunCommandInput, type RunCommandInput,
} from "./run" } from "./run"

View file

@ -1,18 +1,12 @@
import { NodeFileSystem } from "@effect/platform-node" import { Service } from "@opencode-ai/client/effect"
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise" import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
import { Global } from "@opencode-ai/core/global" import { Server } from "../services/server"
import { Effect } from "effect"
import path from "node:path"
import { Daemon } from "../daemon"
import { waitForCatalogReady } from "./catalog.shared" import { waitForCatalogReady } from "./catalog.shared"
import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./runtime.stdin" import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./runtime.stdin"
import type { RunInput, RunTuiConfig } from "./types" import type { RunInput, RunTuiConfig } from "./types"
export type MiniCommandInput = { export type MiniCommandInput = {
directory?: string server: Server.Resolved
attach?: string
password?: string
username?: string
continue?: boolean continue?: boolean
session?: string session?: string
fork?: boolean fork?: boolean
@ -22,58 +16,46 @@ export type MiniCommandInput = {
replay?: boolean replay?: boolean
replayLimit?: number replayLimit?: number
demo?: boolean demo?: boolean
serverCommand?: ReadonlyArray<string>
tuiConfig?: RunTuiConfig | Promise<RunTuiConfig> tuiConfig?: RunTuiConfig | Promise<RunTuiConfig>
} }
type Session = Awaited<ReturnType<OpenCodeClient["session"]["get"]>> type Session = Awaited<ReturnType<OpenCodeClient["session"]["get"]>>
type Transport = { readonly url: string; readonly headers?: HeadersInit }
export async function runMini(input: MiniCommandInput) { export async function runMini(input: MiniCommandInput) {
validate(input) validate(input)
const initialInput = mergeInput(process.stdin.isTTY ? undefined : await Bun.stdin.text(), input.prompt) const initialInput = mergeInput(process.stdin.isTTY ? undefined : await Bun.stdin.text(), input.prompt)
const runtimeTask = import("./runtime") const runtimeTask = import("./runtime")
const directory = input.attach ? input.directory : localDirectory(input.directory) const directory = localDirectory()
const transportTask = startTransport(input)
void transportTask.catch(() => {})
try { try {
if (input.attach) await transportTask
const sdk = OpenCode.make({ const sdk = OpenCode.make({
baseUrl: "http://opencode.pending", baseUrl: input.server.endpoint.url,
fetch: deferredFetch(transportTask), headers: Service.headers(input.server.endpoint),
}) })
const attachedSession =
input.attach && input.session && !input.directory
? await sdk.session.get({ sessionID: input.session }).catch(() => fail("Session not found"))
: undefined
const resolvedDirectory =
directory ?? attachedSession?.location.directory ?? (await remoteDirectory(await transportTask, sdk))
const model = parseModel(input.model) const model = parseModel(input.model)
let agentTask: Promise<string | undefined> | undefined let agentTask: Promise<string | undefined> | undefined
const resolveAgent = () => { const resolveAgent = () => {
agentTask ??= validateAgent(sdk, resolvedDirectory, input.agent, input.attach) agentTask ??= validateAgent(sdk, directory, input.agent)
return agentTask return agentTask
} }
const resolveSession = async () => { const resolveSession = async () => {
const [agent, selected] = await Promise.all([ const [agent, selected] = await Promise.all([
resolveAgent(), resolveAgent(),
selectSession(sdk, resolvedDirectory, input, attachedSession), selectSession(sdk, directory, input),
]) ])
const readyModel = const readyModel =
model ?? (selected?.model ? { providerID: selected.model.providerID, modelID: selected.model.id } : undefined) model ?? (selected?.model ? { providerID: selected.model.providerID, modelID: selected.model.id } : undefined)
if (readyModel) await waitForCatalogReady({ sdk, directory: resolvedDirectory, model: readyModel }) if (readyModel) await waitForCatalogReady({ sdk, directory, model: readyModel })
const session = selected ?? (await createSession(sdk, resolvedDirectory, agent, model)) const session = selected ?? (await createSession(sdk, directory, agent, model))
return { id: session.id, title: session.title, resume: selected !== undefined } return { id: session.id, title: session.title, resume: selected !== undefined }
} }
const create = ( const create = (
_sdk: OpenCodeClient, _sdk: OpenCodeClient,
next: { agent: string | undefined; model: RunInput["model"]; variant: string | undefined }, next: { agent: string | undefined; model: RunInput["model"]; variant: string | undefined },
) => createSession(sdk, resolvedDirectory, next.agent, next.model, next.variant) ) => createSession(sdk, directory, next.agent, next.model, next.variant)
const runtime = await runtimeTask const runtime = await runtimeTask
await runtime.runInteractiveDeferredMode({ await runtime.runInteractiveDeferredMode({
sdk, sdk,
directory: resolvedDirectory, directory,
resolveAgent, resolveAgent,
session: resolveSession, session: resolveSession,
createSession: create, createSession: create,
@ -94,6 +76,10 @@ export async function runMini(input: MiniCommandInput) {
} }
} }
export function validateMiniTerminal() {
if (!process.stdout.isTTY) fail("opencode mini requires a TTY stdout")
}
/** @internal Exported for testing. */ /** @internal Exported for testing. */
export function mergeInput(piped: string | undefined, prompt: string | undefined) { export function mergeInput(piped: string | undefined, prompt: string | undefined) {
if (!prompt) return piped || undefined if (!prompt) return piped || undefined
@ -102,7 +88,7 @@ export function mergeInput(piped: string | undefined, prompt: string | undefined
} }
function validate(input: MiniCommandInput) { function validate(input: MiniCommandInput) {
if (!process.stdout.isTTY) fail("opencode mini requires a TTY stdout") validateMiniTerminal()
if (input.replayLimit !== undefined && (!Number.isInteger(input.replayLimit) || input.replayLimit <= 0)) { if (input.replayLimit !== undefined && (!Number.isInteger(input.replayLimit) || input.replayLimit <= 0)) {
fail("--replay-limit must be a positive integer") fail("--replay-limit must be a positive integer")
} }
@ -110,56 +96,16 @@ function validate(input: MiniCommandInput) {
resolveInteractiveStdin().cleanup?.() resolveInteractiveStdin().cleanup?.()
} }
function localDirectory(directory?: string): string { function localDirectory(): string {
const root = process.env.PWD ?? process.cwd() const root = process.env.PWD ?? process.cwd()
try { try {
process.chdir(directory ? (path.isAbsolute(directory) ? directory : path.join(root, directory)) : root) process.chdir(root)
return process.cwd() return process.cwd()
} catch { } catch {
fail(`Failed to change directory to ${directory}`) fail(`Failed to change directory to ${root}`)
} }
} }
function startTransport(input: MiniCommandInput): Promise<Transport> {
if (input.attach) {
return Effect.runPromise(
Daemon.transport({
mode: "attach",
url: input.attach,
password: input.password ?? process.env.OPENCODE_SERVER_PASSWORD,
username: input.username ?? process.env.OPENCODE_SERVER_USERNAME,
}),
)
}
return Effect.runPromise(
Daemon.transport({ mode: "shared", command: input.serverCommand }).pipe(
Effect.provide(NodeFileSystem.layer),
Effect.provide(Global.layerWith({})),
),
)
}
function deferredFetch(transportTask: Promise<{ url: string; headers?: HeadersInit }>): typeof globalThis.fetch {
const fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const transport = await transportTask
const request = new Request(input, init)
const source = new URL(request.url)
const headers = new Headers(request.headers)
for (const [key, value] of new Headers(transport.headers)) headers.set(key, value)
return globalThis.fetch(new Request(new URL(source.pathname + source.search, transport.url), request), { headers })
}
return fetch as typeof globalThis.fetch
}
async function remoteDirectory(
transport: { url: string; headers?: HeadersInit },
sdk: OpenCodeClient,
): Promise<string> {
const location = await sdk.location.get()
if (!location.directory) throw new Error(`Failed to resolve remote directory from ${transport.url}`)
return location.directory
}
function parseModel(value?: string): RunInput["model"] { function parseModel(value?: string): RunInput["model"] {
if (!value) return if (!value) return
const [providerID, ...rest] = value.split("/") const [providerID, ...rest] = value.split("/")
@ -168,7 +114,7 @@ function parseModel(value?: string): RunInput["model"] {
return { providerID, modelID } return { providerID, modelID }
} }
async function validateAgent(sdk: OpenCodeClient, directory: string, name?: string, attach?: string) { async function validateAgent(sdk: OpenCodeClient, directory: string, name?: string) {
if (!name) return if (!name) return
const deadline = Date.now() + 5_000 const deadline = Date.now() + 5_000
let agents: Awaited<ReturnType<OpenCodeClient["agent"]["list"]>> | undefined let agents: Awaited<ReturnType<OpenCodeClient["agent"]["list"]>> | undefined
@ -183,7 +129,7 @@ async function validateAgent(sdk: OpenCodeClient, directory: string, name?: stri
await Bun.sleep(25) await Bun.sleep(25)
} }
if (!agents) { if (!agents) {
warning(`failed to list agents${attach ? ` from ${attach}` : ""}. Falling back to default agent`) warning("failed to list agents. Falling back to default agent")
return return
} }
warning(`agent "${name}" not found. Falling back to default agent`) warning(`agent "${name}" not found. Falling back to default agent`)

View file

@ -25,7 +25,7 @@ type Input = {
variant?: string variant?: string
thinking: boolean thinking: boolean
format: "default" | "json" format: "default" | "json"
dangerouslySkipPermissions: boolean auto: boolean
/** True when the client is attached to a shared server rather than an exclusive in-process one. */ /** True when the client is attached to a shared server rather than an exclusive in-process one. */
attached: boolean attached: boolean
renderTool: (part: ToolPart) => Promise<void> renderTool: (part: ToolPart) => Promise<void>
@ -91,7 +91,7 @@ export async function runNonInteractivePrompt(input: Input) {
} }
const replyPermission = async (request: { id: string; action: string; resources: ReadonlyArray<string> }) => { const replyPermission = async (request: { id: string; action: string; resources: ReadonlyArray<string> }) => {
if (!input.dangerouslySkipPermissions) { if (!input.auto) {
permissionRejected = true permissionRejected = true
UI.println( UI.println(
UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_WARNING_BOLD + "!",
@ -103,10 +103,10 @@ export async function runNonInteractivePrompt(input: Input) {
.reply({ .reply({
sessionID: input.sessionID, sessionID: input.sessionID,
requestID: request.id, requestID: request.id,
reply: input.dangerouslySkipPermissions ? "once" : "reject", reply: input.auto ? "once" : "reject",
}) })
.catch(() => {}) .catch(() => {})
if (!input.dangerouslySkipPermissions) { if (!input.auto) {
await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {}) await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
} }
} }
@ -153,7 +153,7 @@ export async function runNonInteractivePrompt(input: Input) {
if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue
const time = toMillis("created" in event ? event.created : undefined) const time = toMillis("created" in event ? event.created : undefined)
if (event.type === "session.prompt.promoted") { if (event.type === "session.input.promoted") {
if (event.data.inputID === messageID) { if (event.data.inputID === messageID) {
promoted = true promoted = true
continue continue
@ -409,10 +409,8 @@ export async function runNonInteractivePrompt(input: Input) {
{ {
sessionID: input.sessionID, sessionID: input.sessionID,
id: messageID, id: messageID,
prompt: { text: [input.message, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
text: [input.message, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"), files: prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])),
files: prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])),
},
delivery: "steer", delivery: "steer",
}, },
{ signal: admission.signal }, { signal: admission.signal },

View file

@ -1,19 +1,18 @@
import { NodeFileSystem } from "@effect/platform-node" import { Service } from "@opencode-ai/client/effect"
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise" import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
import { Global } from "@opencode-ai/core/global"
import { FSUtil } from "@opencode-ai/core/fs-util" import { FSUtil } from "@opencode-ai/core/fs-util"
import { Model } from "@opencode-ai/schema/model"
import type { ToolPart } from "@opencode-ai/sdk/v2" import type { ToolPart } from "@opencode-ai/sdk/v2"
import { Effect } from "effect"
import { open } from "node:fs/promises" import { open } from "node:fs/promises"
import path from "node:path" import path from "node:path"
import { Daemon } from "../daemon" import { Server } from "../services/server"
import { Standalone } from "../services/standalone"
import { loadRunAgents, waitForCatalogReady } from "./catalog.shared" import { loadRunAgents, waitForCatalogReady } from "./catalog.shared"
import { runNonInteractivePrompt } from "./noninteractive" import { runNonInteractivePrompt } from "./noninteractive"
import { toolInlineInfo } from "./tool" import { toolInlineInfo } from "./tool"
import { UI } from "./ui" import { UI } from "./ui"
export type RunCommandInput = { export type RunCommandInput = {
server: Server.Resolved
message: string[] message: string[]
continue?: boolean continue?: boolean
session?: string session?: string
@ -23,14 +22,8 @@ export type RunCommandInput = {
format: "default" | "json" format: "default" | "json"
file: string[] file: string[]
title?: string title?: string
server?: string
password?: string
username?: string
directory?: string
variant?: string
thinking?: boolean thinking?: boolean
dangerouslySkipPermissions?: boolean auto?: boolean
standaloneCommand?: ReadonlyArray<string>
} }
type FilePart = { type FilePart = {
@ -39,8 +32,6 @@ type FilePart = {
mime: string mime: string
} }
type Transport = { readonly url: string; readonly headers?: HeadersInit }
type Prepared = { type Prepared = {
directory?: string directory?: string
message: string message: string
@ -56,34 +47,24 @@ export function runNonInteractive(input: RunCommandInput) {
async function run(input: RunCommandInput) { async function run(input: RunCommandInput) {
if (input.fork && !input.continue && !input.session) fail("--fork requires --continue or --session") if (input.fork && !input.continue && !input.session) fail("--fork requires --continue or --session")
const root = process.env.PWD ?? process.cwd() const root = process.env.PWD ?? process.cwd()
const directory = input.server ? input.directory : localDirectory(input.directory, root) const directory = localDirectory(root)
const message = mergeInput(formatMessage(input.message), process.stdin.isTTY ? undefined : await Bun.stdin.text()) const message = mergeInput(formatMessage(input.message), process.stdin.isTTY ? undefined : await Bun.stdin.text())
if (!message?.trim()) fail("You must provide a message") if (!message?.trim()) fail("You must provide a message")
const files = await Promise.all( const files = await Promise.all(input.file.map((file) => prepareFile(file, root)))
input.file.map((file) => prepareFile(file, input.server ? root : (directory ?? root), input.server !== undefined)),
)
const prepared = { directory, message, files } const prepared = { directory, message, files }
if (input.standaloneCommand) return execute(input, prepared, input.server.endpoint)
return Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
const transport = yield* Standalone.transport({ command: input.standaloneCommand })
yield* Effect.promise(() => execute(input, prepared, transport))
}),
),
)
const transport = await startTransport(input)
return execute(input, prepared, transport)
} }
async function execute(input: RunCommandInput, prepared: Prepared, transport: Transport) { async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Service.Endpoint) {
const client = OpenCode.make({ baseUrl: transport.url, headers: transport.headers }) const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const requestedDirectory = prepared.directory ?? (await client.location.get()).directory const requestedDirectory = prepared.directory ?? (await client.location.get()).directory
if (!requestedDirectory) fail("Failed to resolve server directory") if (!requestedDirectory) fail("Failed to resolve server directory")
const session = await selectSession(client, requestedDirectory, input) const session = await selectSession(client, requestedDirectory, input)
const cwd = session?.location.directory ?? requestedDirectory const cwd = session?.location.directory ?? requestedDirectory
const workspace = session?.location.workspaceID const workspace = session?.location.workspaceID
const explicitModel = parseModel(input.model) const explicit = parseRunModel(input.model)
const explicitModel = explicit?.model
const variant = explicit?.variant
const sessionModel = session?.model ? { providerID: session.model.providerID, modelID: session.model.id } : undefined const sessionModel = session?.model ? { providerID: session.model.providerID, modelID: session.model.id } : undefined
const defaultModel = const defaultModel =
!explicitModel && !sessionModel !explicitModel && !sessionModel
@ -91,8 +72,8 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
.default({ location: { directory: cwd, workspace } }) .default({ location: { directory: cwd, workspace } })
.then((result) => (result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined)) .then((result) => (result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined))
: undefined : undefined
const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel) const model = pickRunModel(explicitModel, variant, sessionModel, defaultModel)
if (input.variant && !model) if (variant && !model)
return reportError(input, "Cannot select a variant before selecting a model", session?.id) return reportError(input, "Cannot select a variant before selecting a model", session?.id)
if (model) { if (model) {
await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model }) await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model })
@ -100,12 +81,12 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
if (!available.data.some((item) => item.providerID === model.providerID && item.id === model.modelID)) if (!available.data.some((item) => item.providerID === model.providerID && item.id === model.modelID))
return reportError(input, `Model unavailable: ${model.providerID}/${model.modelID}`, session?.id) return reportError(input, `Model unavailable: ${model.providerID}/${model.modelID}`, session?.id)
} }
const agent = await validateAgent(client, cwd, input.agent, input.server) const agent = await validateAgent(client, cwd, input.agent)
const selected = const selected =
session ?? session ??
(await client.session.create({ (await client.session.create({
agent, agent,
model: model ? { providerID: model.providerID, id: model.modelID, variant: input.variant } : undefined, model: model ? { providerID: model.providerID, id: model.modelID, variant } : undefined,
location: { directory: cwd }, location: { directory: cwd },
})) }))
if (!session && input.title !== undefined) { if (!session && input.title !== undefined) {
@ -122,11 +103,11 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
files: prepared.files, files: prepared.files,
agent, agent,
model, model,
variant: input.variant, variant,
thinking: input.thinking ?? false, thinking: input.thinking ?? false,
format: input.format, format: input.format,
dangerouslySkipPermissions: input.dangerouslySkipPermissions ?? false, auto: input.auto ?? false,
attached: !input.standaloneCommand, attached: true,
renderTool, renderTool,
renderToolError, renderToolError,
}).catch((error) => reportError(input, error instanceof Error ? error.message : String(error), selected.id)) }).catch((error) => reportError(input, error instanceof Error ? error.message : String(error), selected.id))
@ -154,47 +135,29 @@ function formatMessage(message: string[]) {
return value || undefined return value || undefined
} }
function localDirectory(directory: string | undefined, root: string) { function localDirectory(root: string) {
try { try {
process.chdir(directory ? (path.isAbsolute(directory) ? directory : path.join(root, directory)) : root) process.chdir(root)
return process.cwd() return process.cwd()
} catch { } catch {
fail(`Failed to change directory to ${directory}`) fail(`Failed to change directory to ${root}`)
} }
} }
function startTransport(input: RunCommandInput) { export function parseRunModel(value?: string) {
if (input.server) {
return Effect.runPromise(
Daemon.transport({
mode: "attach",
url: input.server,
password: input.password,
username: input.username,
}),
)
}
return Effect.runPromise(
Daemon.transport({ mode: "shared" }).pipe(
Effect.provide(NodeFileSystem.layer),
Effect.provide(Global.layerWith({})),
),
)
}
function parseModel(value?: string) {
if (!value) return if (!value) return
const [providerID, ...rest] = value.split("/") const ref = Model.Ref.parse(value)
const modelID = rest.join("/") return {
if (!providerID || !modelID) fail("--model must use the format provider/model") model: { providerID: ref.providerID, modelID: ref.id },
return { providerID, modelID } variant: ref.variant,
}
} }
async function validateAgent(client: OpenCodeClient, directory: string, name?: string, server?: string) { async function validateAgent(client: OpenCodeClient, directory: string, name?: string) {
if (!name) return if (!name) return
const agents = await loadRunAgents(client, directory).catch(() => undefined) const agents = await loadRunAgents(client, directory).catch(() => undefined)
if (!agents) { if (!agents) {
warning(`failed to list agents${server ? ` from ${server}` : ""}. Falling back to default agent`) warning("failed to list agents. Falling back to default agent")
return return
} }
const agent = agents.find((item) => item.id === name) const agent = agents.find((item) => item.id === name)
@ -222,12 +185,11 @@ async function selectSession(client: OpenCodeClient, directory: string, input: R
return client.session.fork({ sessionID: selected.id }) return client.session.fork({ sessionID: selected.id })
} }
async function prepareFile(input: string, directory: string, remote: boolean): Promise<FilePart> { async function prepareFile(input: string, directory: string): Promise<FilePart> {
const file = path.resolve(directory, input) const file = path.resolve(directory, input)
const handle = await open(file, "r").catch(() => fail(`File not found: ${input}`)) const handle = await open(file, "r").catch(() => fail(`File not found: ${input}`))
try { try {
const stat = await handle.stat() const stat = await handle.stat()
if (remote && stat.isDirectory()) fail(`Cannot attach local directory without a shared filesystem: ${input}`)
if (!stat.isFile() || stat.size > ATTACH_FILE_MAX_BYTES) if (!stat.isFile() || stat.size > ATTACH_FILE_MAX_BYTES)
fail(`Cannot attach a directory, special file, or file larger than 10 MiB: ${input}`) fail(`Cannot attach a directory, special file, or file larger than 10 MiB: ${input}`)
const content = Buffer.alloc(Number(stat.size)) const content = Buffer.alloc(Number(stat.size))

View file

@ -457,12 +457,13 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
} }
const reduce = (child: ChildState, event: V2Event) => { const reduce = (child: ChildState, event: V2Event) => {
if (event.type === "session.prompt.admitted") { if (event.type === "session.input.admitted") {
child.prompts.set(event.data.inputID, event.data.prompt.text) if (event.data.input.type === "user") child.prompts.set(event.data.inputID, event.data.input.data.text)
return return
} }
if (event.type === "session.prompt.promoted") { if (event.type === "session.input.promoted") {
const prompt = child.prompts.get(event.data.inputID) ?? "" const prompt = child.prompts.get(event.data.inputID)
if (prompt === undefined) return
child.prompts.delete(event.data.inputID) child.prompts.delete(event.data.inputID)
if (userFrame(child, event.data.inputID, prompt)) { if (userFrame(child, event.data.inputID, prompt)) {
touch(child, event.created) touch(child, event.created)

View file

@ -562,7 +562,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
} }
input.trace?.write("recv.event", event) input.trace?.write("recv.event", event)
subagents.main(event) subagents.main(event)
if (event.type === "session.prompt.promoted") { if (event.type === "session.input.promoted") {
if (state.wait?.messageID === event.data.inputID) state.wait.promoted = true if (state.wait?.messageID === event.data.inputID) state.wait.promoted = true
state.messageIDs.add(event.data.inputID) state.messageIDs.add(event.data.inputID)
write([], { phase: "running", status: "waiting for assistant" }) write([], { phase: "running", status: "waiting for assistant" })
@ -1052,11 +1052,9 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
{ {
sessionID: input.sessionID, sessionID: input.sessionID,
id: messageID, id: messageID,
prompt: { text: [next.prompt.text, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
text: [next.prompt.text, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"), files: attachments.length ? attachments : undefined,
files: attachments.length ? attachments : undefined, agents: agents.length ? agents : undefined,
agents: agents.length ? agents : undefined,
},
delivery: "steer", delivery: "steer",
}, },
{ signal: next.signal }, { signal: next.signal },

View file

@ -0,0 +1,77 @@
import { NodeFileSystem } from "@effect/platform-node"
import { Service } from "@opencode-ai/client/effect"
import { ClientError, isUnauthorizedError, OpenCode } from "@opencode-ai/client/promise"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { Effect, Redacted } from "effect"
import { Env } from "../env"
import { ServiceConfig } from "./service-config"
import { Standalone } from "./standalone"
export type Args = {
readonly server?: string
readonly standalone?: boolean
}
export type Resolved = {
readonly endpoint: Service.Endpoint
readonly discover?: () => Promise<Service.Endpoint>
readonly reload?: () => Promise<void>
}
export const resolve = Effect.fn("cli.server.resolve")(function* (args: Args) {
if (args.server !== undefined && args.standalone)
return yield* Effect.fail(new Error("--server and --standalone cannot be combined"))
if (args.server !== undefined) {
const password = yield* Env.password
const endpoint = {
url: args.server,
auth: password
? { type: "basic" as const, username: "opencode", password: Redacted.value(password) }
: undefined,
} satisfies Service.Endpoint
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
const health = yield* Effect.tryPromise({
try: () => client.health.get({ signal: AbortSignal.timeout(5_000) }),
catch: (cause) => connectError(endpoint, cause),
})
if (health.version !== InstallationVersion)
process.stderr.write(
`Warning: Server at ${endpoint.url} has version ${health.version}; this client is ${InstallationVersion}. Continuing anyway.\n`,
)
return { endpoint } satisfies Resolved
}
if (args.standalone) {
return { endpoint: yield* Standalone.start() } satisfies Resolved
}
const options = yield* ServiceConfig.options()
const endpoint = yield* Service.start(options)
const reconnectOptions = { ...options, version: undefined }
return {
endpoint,
discover: () => Effect.runPromise(Service.start(reconnectOptions).pipe(Effect.provide(NodeFileSystem.layer))),
reload: () =>
Effect.runPromise(
Effect.gen(function* () {
yield* Service.stop(options)
yield* Service.start(options)
}).pipe(Effect.provide(NodeFileSystem.layer)),
),
} satisfies Resolved
})
function connectError(endpoint: Service.Endpoint, cause: unknown) {
if (isUnauthorizedError(cause)) {
return new Error(
endpoint.auth === undefined
? `Server at ${endpoint.url} requires a password; set OPENCODE_PASSWORD`
: `Server at ${endpoint.url} rejected the password`,
{ cause },
)
}
if (cause instanceof ClientError && cause.reason === "Transport")
return new Error(`Could not reach server at ${endpoint.url}`, { cause })
return new Error(`Server at ${endpoint.url} did not provide a compatible V2 health response`, { cause })
}
export * as Server from "./server"

View file

@ -1,4 +1,4 @@
import { ServerAuth } from "@opencode-ai/server/auth" import { Service } from "@opencode-ai/client/effect"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Effect, Schema, Stream } from "effect" import { Effect, Schema, Stream } from "effect"
@ -34,7 +34,7 @@ function command(password: string, options: Options) {
}) })
} }
const makeTransport = Effect.fn("cli.standalone.transport")( const makeEndpoint = Effect.fn("cli.standalone.endpoint")(
function* (options: Options) { function* (options: Options) {
const password = randomBytes(32).toString("base64url") const password = randomBytes(32).toString("base64url")
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
@ -42,13 +42,17 @@ const makeTransport = Effect.fn("cli.standalone.transport")(
const output = yield* proc.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.take(1), Stream.mkString) const output = yield* proc.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.take(1), Stream.mkString)
if (!output) return yield* Effect.fail(new Error("Standalone server exited before reporting readiness")) if (!output) return yield* Effect.fail(new Error("Standalone server exited before reporting readiness"))
const ready = yield* Effect.tryPromise(() => decodeReady(output)) const ready = yield* Effect.tryPromise(() => decodeReady(output))
return { url: ready.url, headers: ServerAuth.headers({ password, username: "opencode" }), pid: proc.pid } return {
url: ready.url,
auth: { type: "basic" as const, username: "opencode", password },
pid: proc.pid,
} satisfies Service.Endpoint & { readonly pid: number }
}, },
Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)),
) )
export function transport(options: Options = {}) { export function start(options: Options = {}) {
return makeTransport(options) return makeEndpoint(options)
} }
export * as Standalone from "./standalone" export * as Standalone from "./standalone"

View file

@ -1,54 +0,0 @@
import { run } from "@opencode-ai/tui"
import { TuiConfig } from "@opencode-ai/tui/config"
import { Effect } from "effect"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Global } from "@opencode-ai/core/global"
import { loadBuiltinPlugins } from "@opencode-ai/tui/builtins"
import { OpenCode } from "@opencode-ai/client/promise"
import type { Service } from "@opencode-ai/client/effect"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import type { Args } from "@opencode-ai/tui/context/args"
export function runTui(
transport: Service.Transport,
args: Args,
discover?: () => Promise<Service.Transport>,
reload?: () => Promise<void>,
) {
const config = TuiConfig.resolve({}, { terminalSuspend: false })
let disposeSlots: (() => void) | undefined
return Effect.gen(function* () {
const options = { baseUrl: transport.url, headers: transport.headers }
const api = OpenCode.make(options)
const directory = yield* Effect.tryPromise(() => api.file.list({ location: { directory: process.cwd() } })).pipe(
Effect.map((response) => response.location.directory),
Effect.catch(() =>
Effect.tryPromise(() => api.location.get()).pipe(Effect.map((response) => response.directory)),
),
)
return yield* run({
client: createOpencodeClient({ ...options, directory }),
api,
discover: discover
? async () => {
const next = await discover()
return {
client: createOpencodeClient({ baseUrl: next.url, headers: next.headers, directory }),
api: OpenCode.make({ baseUrl: next.url, headers: next.headers }),
}
}
: undefined,
reload,
args,
config,
pluginHost: {
async start(input) {
disposeSlots = await loadBuiltinPlugins(input.api, input.runtime)
},
async dispose() {
disposeSlots?.()
},
},
})
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
}

View file

@ -0,0 +1,242 @@
/** @jsxImportSource @opentui/solid */
import { createCliRenderer, RGBA, type CliRenderer, type ColorInput, type ScrollbackWriter } from "@opentui/core"
import { createScrollbackWriter, render, useKeyboard } from "@opentui/solid"
import { registerOpencodeSpinner } from "@opencode-ai/tui/component/register-spinner"
import { Show, createSignal } from "solid-js"
registerOpencodeSpinner()
export type TimelineHost = {
readonly signal: AbortSignal
intro(text: string): Promise<void>
item(text: string): Promise<void>
pending(text: string): Promise<void>
success(text: string): Promise<void>
failure(text: string): Promise<void>
outro(text: string): Promise<void>
close(): Promise<void>
}
type RowKind = "intro" | "item" | "success" | "failure" | "outro"
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
const IDLE_TIMEOUT = 1_000
const COLORS = {
accent: RGBA.fromIndex(6),
error: RGBA.fromIndex(1),
foreground: RGBA.defaultForeground(),
muted: RGBA.fromIndex(8),
success: RGBA.fromIndex(2),
}
const ROWS: Record<RowKind, { marker: string; color: ColorInput; connector: boolean }> = {
intro: { marker: "┌", color: COLORS.muted, connector: true },
item: { marker: "●", color: COLORS.accent, connector: true },
success: { marker: "◇", color: COLORS.success, connector: true },
failure: { marker: "■", color: COLORS.error, connector: false },
outro: { marker: "└", color: COLORS.muted, connector: false },
}
function row(kind: RowKind, value: string): ScrollbackWriter {
const style = ROWS[kind]
return createScrollbackWriter(
() => (
<box width="100%" minHeight={1} flexDirection="column">
<box width="100%" minHeight={1} flexDirection="row" gap={1}>
<text fg={style.color} flexShrink={0}>
{style.marker}
</text>
<text fg={COLORS.foreground} wrapMode="word">
{value}
</text>
</box>
<Show when={style.connector}>
<text fg={COLORS.muted}></text>
</Show>
</box>
),
{ startOnNewLine: true, trailingNewline: !style.connector },
)
}
function TimelineFooter(props: { pending: () => string | undefined; cancel: () => void }) {
useKeyboard((event) => {
if (event.name !== "escape" && !(event.ctrl && event.name === "c")) return
event.preventDefault()
props.cancel()
})
return (
<box width="100%" height={1} flexDirection="row" gap={1}>
<Show when={props.pending()}>
{(text) => (
<>
<spinner frames={SPINNER_FRAMES} interval={80} color={COLORS.accent} />
<text fg={COLORS.foreground} wrapMode="none" truncate>
{text()}
</text>
</>
)}
</Show>
</box>
)
}
function bounded(task: Promise<unknown>) {
return new Promise<void>((resolve) => {
const timer = setTimeout(resolve, IDLE_TIMEOUT)
timer.unref()
const finish = () => {
clearTimeout(timer)
resolve()
}
void task.then(finish, finish)
})
}
async function shutdown(renderer: CliRenderer): Promise<void> {
await bounded(renderer.idle())
try {
renderer.externalOutputMode = "passthrough"
} finally {
try {
renderer.screenMode = "main-screen"
} finally {
if (!renderer.isDestroyed) renderer.destroy()
}
}
}
export async function createTimelineHost(): Promise<TimelineHost> {
const stdout = process.stdout
const controller = new AbortController()
const signals: NodeJS.Signals[] = ["SIGINT", "SIGHUP", "SIGQUIT"]
const cancel = () => {
if (!controller.signal.aborted) controller.abort()
}
signals.forEach((signal) => process.on(signal, cancel))
if (!stdout.isTTY || !process.stdin.isTTY) {
let closed = false
let writing = false
let active: Promise<void> | undefined
let closeTask: Promise<void> | undefined
const write = async (kind: RowKind | "pending", text: string) => {
if (closed) throw new Error("timeline closed")
if (writing) throw new Error("timeline write already in progress")
writing = true
try {
const style = kind === "pending" ? undefined : ROWS[kind]
const marker = kind === "pending" ? "." : ROWS[kind].marker
const connector = style?.connector ? "│\n" : ""
active = new Promise<void>((resolve, reject) => {
stdout.write(`${marker} ${text}\n${connector}`, (error) => (error ? reject(error) : resolve()))
})
await active
} finally {
writing = false
active = undefined
}
}
const close = () => {
if (closeTask) return closeTask
closed = true
closeTask = (async () => {
await active?.catch(() => { })
signals.forEach((signal) => process.off(signal, cancel))
})()
return closeTask
}
return {
signal: controller.signal,
intro: (text) => write("intro", text),
item: (text) => write("item", text),
pending: (text) => write("pending", text),
success: (text) => write("success", text),
failure: (text) => write("failure", text),
outro: (text) => write("outro", text),
close,
}
}
let renderer: CliRenderer | undefined
try {
// Start on a fresh row so delayed SSH cursor reports cannot make
// split-footer overwrite the shell command.
process.stdout.write("\n")
renderer = await createCliRenderer({
stdin: process.stdin,
useMouse: false,
autoFocus: false,
openConsoleOnError: false,
exitOnCtrlC: false,
exitSignals: [],
screenMode: "split-footer",
footerHeight: 1,
externalOutputMode: "capture-stdout",
consoleMode: "disabled",
clearOnShutdown: false,
})
const activeRenderer = renderer
const [pending, setPending] = createSignal<string>()
const renderTask = render(() => <TimelineFooter pending={pending} cancel={cancel} />, activeRenderer)
void renderTask.catch(cancel)
await bounded(activeRenderer.idle())
let closed = false
let writing = false
let active: Promise<void> | undefined
let closeTask: Promise<void> | undefined
const write = (kind: RowKind | "pending", text: string) => {
if (closed) return Promise.reject(new Error("timeline closed"))
if (writing) return Promise.reject(new Error("timeline write already in progress"))
writing = true
active = (async () => {
if (kind === "pending") {
setPending(text)
activeRenderer.requestRender()
} else {
if (kind === "success" || kind === "failure" || kind === "outro") setPending(undefined)
activeRenderer.writeToScrollback(row(kind, text))
activeRenderer.requestRender()
}
await bounded(activeRenderer.idle())
})().finally(() => {
writing = false
active = undefined
})
return active
}
const close = () => {
if (closeTask) return closeTask
closed = true
closeTask = (async () => {
await active?.catch(() => { })
try {
await shutdown(activeRenderer)
await bounded(renderTask)
} finally {
signals.forEach((signal) => process.off(signal, cancel))
}
})()
return closeTask
}
return {
signal: controller.signal,
intro: (text) => write("intro", text),
item: (text) => write("item", text),
pending: (text) => write("pending", text),
success: (text) => write("success", text),
failure: (text) => write("failure", text),
outro: (text) => write("outro", text),
close,
}
} catch (error) {
try {
if (renderer) await shutdown(renderer)
} finally {
signals.forEach((signal) => process.off(signal, cancel))
}
throw error
}
}

View file

@ -1,4 +1,5 @@
import { Effect } from "effect" import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import path from "node:path" import path from "node:path"
import { Standalone } from "../../src/services/standalone" import { Standalone } from "../../src/services/standalone"
@ -7,9 +8,11 @@ process.argv[1] = path.join(import.meta.dir, "../../src/index.ts")
await Effect.runPromise( await Effect.runPromise(
Effect.scoped( Effect.scoped(
Effect.gen(function* () { Effect.gen(function* () {
const transport = yield* Standalone.transport() const endpoint = yield* Standalone.start()
const response = yield* Effect.promise(() => fetch(new URL("/api/health", transport.url), { headers: transport.headers })) const response = yield* Effect.promise(() =>
console.log(`${transport.pid} ${transport.url} ${response.status}`) fetch(new URL("/api/health", endpoint.url), { headers: Service.headers(endpoint) }),
)
console.log(`${endpoint.pid} ${endpoint.url} ${response.status}`)
return yield* Effect.never return yield* Effect.never
}), }),
), ),

View file

@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationVersion } from "@opencode-ai/core/installation/version"
import path from "node:path" import path from "node:path"
import { mergeInteractiveInput, mergeNonInteractiveInput, pickRunModel } from "../src/mini" import { mergeInteractiveInput, mergeNonInteractiveInput, parseRunModel, pickRunModel } from "../src/mini"
async function cli(args: string[]) { async function cli(args: string[]) {
const child = Bun.spawn([process.execPath, "run", "src/index.ts", ...args], { const child = Bun.spawn([process.execPath, "run", "src/index.ts", ...args], {
@ -39,6 +39,12 @@ describe("mini command", () => {
).toEqual({ providerID: "session-provider", modelID: "session-model" }) ).toEqual({ providerID: "session-provider", modelID: "session-model" })
}) })
test("parses model variants from the model reference", () => {
expect(JSON.stringify(parseRunModel("openrouter/openai/gpt-5#high"))).toBe(
JSON.stringify({ model: { providerID: "openrouter", modelID: "openai/gpt-5" }, variant: "high" }),
)
})
test("is registered in the preview CLI", async () => { test("is registered in the preview CLI", async () => {
const result = await cli(["--help"]) const result = await cli(["--help"])
@ -52,6 +58,7 @@ describe("mini command", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0)
expect(result.stdout).toContain("--server string") expect(result.stdout).toContain("--server string")
expect(result.stdout).not.toContain("--variant")
expect(result.stdout).not.toContain("--attach") expect(result.stdout).not.toContain("--attach")
expect(result.stdout).not.toContain("--command") expect(result.stdout).not.toContain("--command")
}) })
@ -71,6 +78,8 @@ describe("mini command", () => {
const url = new URL(request.url) const url = new URL(request.url)
if (url.pathname === "/api/health") if (url.pathname === "/api/health")
return Response.json({ healthy: true, version: InstallationVersion, pid: process.pid }) return Response.json({ healthy: true, version: InstallationVersion, pid: process.pid })
if (url.pathname === "/api/location")
return Response.json({ directory: process.cwd(), project: { id: "global", directory: process.cwd() } })
if (url.pathname === "/api/model") { if (url.pathname === "/api/model") {
modelRequests++ modelRequests++
return Response.json({ return Response.json({
@ -87,8 +96,6 @@ describe("mini command", () => {
"run", "run",
"--server", "--server",
server.url.toString(), server.url.toString(),
"--dir",
process.cwd(),
"--model", "--model",
"definitely/missing", "definitely/missing",
"hi", "hi",
@ -104,7 +111,8 @@ describe("mini command", () => {
test("reports pre-admission errors as JSON", async () => { test("reports pre-admission errors as JSON", async () => {
const server = Bun.serve({ const server = Bun.serve({
port: 0, port: 0,
fetch() { fetch(request) {
if (new URL(request.url).pathname === "/api/session") return new Response("boom", { status: 500 })
return Response.json({ healthy: true, version: "incompatible", pid: process.pid }) return Response.json({ healthy: true, version: "incompatible", pid: process.pid })
}, },
}) })
@ -116,7 +124,7 @@ describe("mini command", () => {
expect(JSON.parse(result.stdout)).toMatchObject({ expect(JSON.parse(result.stdout)).toMatchObject({
type: "error", type: "error",
sessionID: "", sessionID: "",
error: { type: "unknown", message: "Failed to resolve server directory" }, error: { type: "unknown", message: "UnexpectedStatus" },
}) })
} finally { } finally {
server.stop(true) server.stop(true)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -11,9 +11,13 @@ import { join } from "node:path"
// is all a client needs to connect. The daemon's own configuration (port, // is all a client needs to connect. The daemon's own configuration (port,
// persisted password) is CLI-owned and never read here. // persisted password) is CLI-owned and never read here.
export type Transport = { export type Endpoint = {
readonly url: string readonly url: string
readonly headers?: RequestInit["headers"] readonly auth?: {
readonly type: "basic"
readonly username: string
readonly password: string
}
} }
export type Options = { export type Options = {
@ -31,7 +35,7 @@ export type Options = {
// Read-only lookup: registration file plus health check and version gate. // Read-only lookup: registration file plus health check and version gate.
// Never spawns; escalation to start() is the caller's policy. // Never spawns; escalation to start() is the caller's policy.
export const discover = Effect.fn("service.discover")(function* (options: Options = {}) { export const discover = Effect.fn("service.discover")(function* (options: Options = {}) {
return (yield* discoverLocal(options))?.transport return (yield* discoverLocal(options))?.endpoint
}) })
const discoverLocal = Effect.fnUntraced(function* (options: Options) { const discoverLocal = Effect.fnUntraced(function* (options: Options) {
@ -72,7 +76,7 @@ export const start = Effect.fn("service.start")(function* (options: Options = {}
child.kill("SIGTERM") child.kill("SIGTERM")
}), }),
), ),
Effect.map((found) => found.transport), Effect.map((found) => found.endpoint),
Effect.tapError(() => Effect.try({ try: () => child.kill("SIGTERM"), catch: () => undefined }).pipe(Effect.ignore)), Effect.tapError(() => Effect.try({ try: () => child.kill("SIGTERM"), catch: () => undefined }).pipe(Effect.ignore)),
Effect.mapError(() => new Error("Failed to start server")), Effect.mapError(() => new Error("Failed to start server")),
) )
@ -90,8 +94,9 @@ function fallback() {
return join(state, "opencode", "service.json") return join(state, "opencode", "service.json")
} }
function auth(password: string): RequestInit["headers"] { export function headers(endpoint: Endpoint): RequestInit["headers"] {
return { authorization: "Basic " + btoa("opencode:" + password) } if (endpoint.auth === undefined) return undefined
return { authorization: "Basic " + btoa(endpoint.auth.username + ":" + endpoint.auth.password) }
} }
export const Info = Schema.Struct({ export const Info = Schema.Struct({
@ -120,14 +125,20 @@ const read = Effect.fnUntraced(function* (file?: string) {
type LocalService = { type LocalService = {
readonly info: Info readonly info: Info
readonly transport: Transport readonly endpoint: Endpoint
} }
const probe = Effect.fnUntraced(function* (info: Info, version?: string, allowLegacy = false) { const probe = Effect.fnUntraced(function* (info: Info, version?: string, allowLegacy = false) {
const headers = info.password === undefined ? undefined : auth(info.password) const endpoint = {
url: info.url,
auth:
info.password === undefined
? undefined
: { type: "basic" as const, username: "opencode", password: info.password },
} satisfies Endpoint
const response = yield* Effect.tryPromise(() => const response = yield* Effect.tryPromise(() =>
fetch(new URL("/api/health", info.url), { fetch(new URL("/api/health", info.url), {
headers, headers: headers(endpoint),
signal: AbortSignal.timeout(2_000), signal: AbortSignal.timeout(2_000),
}), }),
).pipe(Effect.option, Effect.map(Option.getOrUndefined)) ).pipe(Effect.option, Effect.map(Option.getOrUndefined))
@ -138,7 +149,7 @@ const probe = Effect.fnUntraced(function* (info: Info, version?: string, allowLe
if (health.value.pid !== info.pid) return undefined if (health.value.pid !== info.pid) return undefined
if (info.version !== undefined && health.value.version !== info.version) return undefined if (info.version !== undefined && health.value.version !== info.version) return undefined
if (version !== undefined && health.value.version !== version) return undefined if (version !== undefined && health.value.version !== version) return undefined
return { info, transport: { url: info.url, headers } } satisfies LocalService return { info, endpoint } satisfies LocalService
} }
if ( if (
!allowLegacy || !allowLegacy ||
@ -146,7 +157,7 @@ const probe = Effect.fnUntraced(function* (info: Info, version?: string, allowLe
(typeof body === "object" && body !== null && ("version" in body || "pid" in body)) (typeof body === "object" && body !== null && ("version" in body || "pid" in body))
) )
return undefined return undefined
return { info, transport: { url: info.url, headers } } satisfies LocalService return { info, endpoint } satisfies LocalService
}) })
// Health-checked lookup without the version gate: lifecycle operations must be // Health-checked lookup without the version gate: lifecycle operations must be

View file

@ -1,5 +1,6 @@
import type { import type {
HealthGetOutput, HealthGetOutput,
ServerGetOutput,
LocationGetInput, LocationGetInput,
LocationGetOutput, LocationGetOutput,
AgentListInput, AgentListInput,
@ -328,6 +329,13 @@ export function make(options: ClientOptions) {
requestOptions, requestOptions,
), ),
}, },
server: {
get: (requestOptions?: RequestOptions) =>
request<ServerGetOutput>(
{ method: "GET", path: `/api/server`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
requestOptions,
),
},
location: { location: {
get: (input?: LocationGetInput, requestOptions?: RequestOptions) => get: (input?: LocationGetInput, requestOptions?: RequestOptions) =>
request<LocationGetOutput>( request<LocationGetOutput>(
@ -508,7 +516,15 @@ export function make(options: ClientOptions) {
{ {
method: "POST", method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/prompt`, path: `/api/session/${encodeURIComponent(input.sessionID)}/prompt`,
body: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] }, body: {
id: input["id"],
text: input["text"],
files: input["files"],
agents: input["agents"],
metadata: input["metadata"],
delivery: input["delivery"],
resume: input["resume"],
},
successStatus: 200, successStatus: 200,
declaredStatuses: [409, 400, 404, 401], declaredStatuses: [409, 400, 404, 401],
empty: false, empty: false,
@ -550,22 +566,24 @@ export function make(options: ClientOptions) {
requestOptions, requestOptions,
), ),
synthetic: (input: SessionSyntheticInput, requestOptions?: RequestOptions) => synthetic: (input: SessionSyntheticInput, requestOptions?: RequestOptions) =>
request<SessionSyntheticOutput>( request<{ readonly data: SessionSyntheticOutput }>(
{ {
method: "POST", method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`, path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`,
body: { body: {
id: input["id"],
text: input["text"], text: input["text"],
description: input["description"], description: input["description"],
metadata: input["metadata"], metadata: input["metadata"],
delivery: input["delivery"],
resume: input["resume"], resume: input["resume"],
}, },
successStatus: 204, successStatus: 200,
declaredStatuses: [404, 400, 401], declaredStatuses: [409, 404, 400, 401],
empty: true, empty: false,
}, },
requestOptions, requestOptions,
), ).then((value) => value.data),
shell: (input: SessionShellInput, requestOptions?: RequestOptions) => shell: (input: SessionShellInput, requestOptions?: RequestOptions) =>
request<SessionShellOutput>( request<SessionShellOutput>(
{ {

View file

@ -159,6 +159,8 @@ export const isProjectCopyError = (value: unknown): value is ProjectCopyError =>
export type HealthGetOutput = { readonly healthy: true; readonly version: string; readonly pid: number } export type HealthGetOutput = { readonly healthy: true; readonly version: string; readonly pid: number }
export type ServerGetOutput = { readonly urls: ReadonlyArray<string> }
export type LocationGetInput = { export type LocationGetInput = {
readonly location?: { readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
@ -541,73 +543,120 @@ export type SessionPromptInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: { readonly id?: {
readonly id?: string | null readonly id?: string | null
readonly prompt: { readonly text: string
readonly text: string readonly files?: ReadonlyArray<{
readonly files?: ReadonlyArray<{ readonly uri: string
readonly uri: string readonly name?: string
readonly name?: string readonly description?: string
readonly description?: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly agents?: ReadonlyArray<{
readonly agents?: ReadonlyArray<{ readonly name: string
readonly name: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly metadata?: { readonly [x: string]: JsonValue }
}
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["id"] }["id"]
readonly prompt: { readonly text: {
readonly id?: string | null readonly id?: string | null
readonly prompt: { readonly text: string
readonly text: string readonly files?: ReadonlyArray<{
readonly files?: ReadonlyArray<{ readonly uri: string
readonly uri: string readonly name?: string
readonly name?: string readonly description?: string
readonly description?: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly agents?: ReadonlyArray<{
readonly agents?: ReadonlyArray<{ readonly name: string
readonly name: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly metadata?: { readonly [x: string]: JsonValue }
}
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["prompt"] }["text"]
readonly files?: {
readonly id?: string | null
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["files"]
readonly agents?: {
readonly id?: string | null
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["agents"]
readonly metadata?: {
readonly id?: string | null
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["metadata"]
readonly delivery?: { readonly delivery?: {
readonly id?: string | null readonly id?: string | null
readonly prompt: { readonly text: string
readonly text: string readonly files?: ReadonlyArray<{
readonly files?: ReadonlyArray<{ readonly uri: string
readonly uri: string readonly name?: string
readonly name?: string readonly description?: string
readonly description?: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly agents?: ReadonlyArray<{
readonly agents?: ReadonlyArray<{ readonly name: string
readonly name: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly metadata?: { readonly [x: string]: JsonValue }
}
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["delivery"] }["delivery"]
readonly resume?: { readonly resume?: {
readonly id?: string | null readonly id?: string | null
readonly prompt: { readonly text: string
readonly text: string readonly files?: ReadonlyArray<{
readonly files?: ReadonlyArray<{ readonly uri: string
readonly uri: string readonly name?: string
readonly name?: string readonly description?: string
readonly description?: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly agents?: ReadonlyArray<{
readonly agents?: ReadonlyArray<{ readonly name: string
readonly name: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly metadata?: { readonly [x: string]: JsonValue }
}
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["resume"] }["resume"]
@ -618,7 +667,10 @@ export type SessionPromptOutput = {
readonly admittedSeq: number readonly admittedSeq: number
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly prompt: { readonly timeCreated: number
readonly promotedSeq?: number
readonly type: "user"
readonly data: {
readonly text: string readonly text: string
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly data: string readonly data: string
@ -632,10 +684,9 @@ export type SessionPromptOutput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly metadata?: { readonly [x: string]: JsonValue }
} }
readonly delivery: "steer" | "queue" readonly delivery: "steer" | "queue"
readonly timeCreated: number
readonly promotedSeq?: number
} }
}["data"] }["data"]
@ -819,7 +870,10 @@ export type SessionCommandOutput = {
readonly admittedSeq: number readonly admittedSeq: number
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly prompt: { readonly timeCreated: number
readonly promotedSeq?: number
readonly type: "user"
readonly data: {
readonly text: string readonly text: string
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly data: string readonly data: string
@ -833,10 +887,9 @@ export type SessionCommandOutput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly metadata?: { readonly [x: string]: JsonValue }
} }
readonly delivery: "steer" | "queue" readonly delivery: "steer" | "queue"
readonly timeCreated: number
readonly promotedSeq?: number
} }
}["data"] }["data"]
@ -863,33 +916,72 @@ export type SessionSkillOutput = void
export type SessionSyntheticInput = { export type SessionSyntheticInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly text: { readonly id?: {
readonly id?: string | null
readonly text: string readonly text: string
readonly description?: string | null readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["id"]
readonly text: {
readonly id?: string | null
readonly text: string
readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["text"] }["text"]
readonly description?: { readonly description?: {
readonly id?: string | null
readonly text: string readonly text: string
readonly description?: string | null readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["description"] }["description"]
readonly metadata?: { readonly metadata?: {
readonly id?: string | null
readonly text: string readonly text: string
readonly description?: string | null readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["metadata"] }["metadata"]
readonly resume?: { readonly delivery?: {
readonly id?: string | null
readonly text: string readonly text: string
readonly description?: string | null readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["delivery"]
readonly resume?: {
readonly id?: string | null
readonly text: string
readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["resume"] }["resume"]
} }
export type SessionSyntheticOutput = void export type SessionSyntheticOutput = {
readonly data: {
readonly admittedSeq: number
readonly id: string
readonly sessionID: string
readonly timeCreated: number
readonly promotedSeq?: number
readonly type: "synthetic"
readonly data: {
readonly text: string
readonly description?: string
readonly metadata?: { readonly [x: string]: JsonValue }
}
readonly delivery: "steer" | "queue"
}
}["data"]
export type SessionShellInput = { export type SessionShellInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
@ -906,11 +998,11 @@ export type SessionCompactInput = {
export type SessionCompactOutput = { export type SessionCompactOutput = {
readonly data: { readonly data: {
readonly type: "compaction"
readonly admittedSeq: number readonly admittedSeq: number
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly timeCreated: number readonly timeCreated: number
readonly type: "compaction"
readonly handledSeq?: number readonly handledSeq?: number
} }
}["data"] }["data"]
@ -1227,7 +1319,7 @@ export type SessionLogOutput =
readonly id: string readonly id: string
readonly created: number readonly created: number
readonly metadata?: { readonly [x: string]: unknown } readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.promoted" readonly type: "session.input.promoted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 } readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 }
readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly inputID: string } readonly data: { readonly sessionID: string; readonly inputID: string }
@ -1236,28 +1328,42 @@ export type SessionLogOutput =
readonly id: string readonly id: string
readonly created: number readonly created: number
readonly metadata?: { readonly [x: string]: unknown } readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.admitted" readonly type: "session.input.admitted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 } readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 }
readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly data: {
readonly sessionID: string readonly sessionID: string
readonly inputID: string readonly inputID: string
readonly prompt: { readonly input:
readonly text: string | {
readonly files?: ReadonlyArray<{ readonly type: "user"
readonly data: string readonly data: {
readonly mime: string readonly text: string
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string } readonly files?: ReadonlyArray<{
readonly name?: string readonly data: string
readonly description?: string readonly mime: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
}> readonly name?: string
readonly agents?: ReadonlyArray<{ readonly description?: string
readonly name: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly agents?: ReadonlyArray<{
} readonly name: string
readonly delivery: "steer" | "queue" readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: unknown }
}
readonly delivery: "steer" | "queue"
}
| {
readonly type: "synthetic"
readonly data: {
readonly text: string
readonly description?: string
readonly metadata?: { readonly [x: string]: unknown }
}
readonly delivery: "steer" | "queue"
}
} }
} }
| { | {
@ -2598,7 +2704,7 @@ export type FormRequestListOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" readonly mode: "form"
readonly fields: ReadonlyArray< readonly fields: ReadonlyArray<
@ -2695,7 +2801,7 @@ export type FormRequestListOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url" readonly mode: "url"
readonly url: string readonly url: string
@ -2710,7 +2816,7 @@ export type FormListOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" readonly mode: "form"
readonly fields: ReadonlyArray< readonly fields: ReadonlyArray<
@ -2807,7 +2913,7 @@ export type FormListOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url" readonly mode: "url"
readonly url: string readonly url: string
@ -2819,7 +2925,7 @@ export type FormCreateInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: { readonly id?: {
readonly id?: string | null readonly id?: string | null
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url" readonly mode: "form" | "url"
readonly fields?: ReadonlyArray< readonly fields?: ReadonlyArray<
@ -2914,9 +3020,9 @@ export type FormCreateInput = {
> | null > | null
readonly url?: string | null readonly url?: string | null
}["id"] }["id"]
readonly title?: { readonly title: {
readonly id?: string | null readonly id?: string | null
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url" readonly mode: "form" | "url"
readonly fields?: ReadonlyArray< readonly fields?: ReadonlyArray<
@ -3013,7 +3119,7 @@ export type FormCreateInput = {
}["title"] }["title"]
readonly metadata?: { readonly metadata?: {
readonly id?: string | null readonly id?: string | null
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url" readonly mode: "form" | "url"
readonly fields?: ReadonlyArray< readonly fields?: ReadonlyArray<
@ -3110,7 +3216,7 @@ export type FormCreateInput = {
}["metadata"] }["metadata"]
readonly mode: { readonly mode: {
readonly id?: string | null readonly id?: string | null
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url" readonly mode: "form" | "url"
readonly fields?: ReadonlyArray< readonly fields?: ReadonlyArray<
@ -3207,7 +3313,7 @@ export type FormCreateInput = {
}["mode"] }["mode"]
readonly fields?: { readonly fields?: {
readonly id?: string | null readonly id?: string | null
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url" readonly mode: "form" | "url"
readonly fields?: ReadonlyArray< readonly fields?: ReadonlyArray<
@ -3304,7 +3410,7 @@ export type FormCreateInput = {
}["fields"] }["fields"]
readonly url?: { readonly url?: {
readonly id?: string | null readonly id?: string | null
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url" readonly mode: "form" | "url"
readonly fields?: ReadonlyArray< readonly fields?: ReadonlyArray<
@ -3406,7 +3512,7 @@ export type FormCreateOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" readonly mode: "form"
readonly fields: ReadonlyArray< readonly fields: ReadonlyArray<
@ -3503,7 +3609,7 @@ export type FormCreateOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url" readonly mode: "url"
readonly url: string readonly url: string
@ -3520,7 +3626,7 @@ export type FormGetOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" readonly mode: "form"
readonly fields: ReadonlyArray< readonly fields: ReadonlyArray<
@ -3617,7 +3723,7 @@ export type FormGetOutput = {
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url" readonly mode: "url"
readonly url: string readonly url: string
@ -4586,7 +4692,7 @@ export type EventSubscribeOutput =
readonly id: string readonly id: string
readonly created: number readonly created: number
readonly metadata?: { readonly [x: string]: unknown } readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.promoted" readonly type: "session.input.promoted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 } readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 }
readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly inputID: string } readonly data: { readonly sessionID: string; readonly inputID: string }
@ -4595,28 +4701,42 @@ export type EventSubscribeOutput =
readonly id: string readonly id: string
readonly created: number readonly created: number
readonly metadata?: { readonly [x: string]: unknown } readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.admitted" readonly type: "session.input.admitted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 } readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: 1 }
readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly data: {
readonly sessionID: string readonly sessionID: string
readonly inputID: string readonly inputID: string
readonly prompt: { readonly input:
readonly text: string | {
readonly files?: ReadonlyArray<{ readonly type: "user"
readonly data: string readonly data: {
readonly mime: string readonly text: string
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string } readonly files?: ReadonlyArray<{
readonly name?: string readonly data: string
readonly description?: string readonly mime: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
}> readonly name?: string
readonly agents?: ReadonlyArray<{ readonly description?: string
readonly name: string readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } }>
}> readonly agents?: ReadonlyArray<{
} readonly name: string
readonly delivery: "steer" | "queue" readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: unknown }
}
readonly delivery: "steer" | "queue"
}
| {
readonly type: "synthetic"
readonly data: {
readonly text: string
readonly description?: string
readonly metadata?: { readonly [x: string]: unknown }
}
readonly delivery: "steer" | "queue"
}
} }
} }
| { | {
@ -5339,7 +5459,7 @@ export type EventSubscribeOutput =
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: unknown } readonly metadata?: { readonly [x: string]: unknown }
readonly mode: "form" readonly mode: "form"
readonly fields: ReadonlyArray< readonly fields: ReadonlyArray<
@ -5436,7 +5556,7 @@ export type EventSubscribeOutput =
| { | {
readonly id: string readonly id: string
readonly sessionID: string readonly sessionID: string
readonly title?: string readonly title: string
readonly metadata?: { readonly [x: string]: unknown } readonly metadata?: { readonly [x: string]: unknown }
readonly mode: "url" readonly mode: "url"
readonly url: string readonly url: string

View file

@ -37,7 +37,9 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
expect(ProjectV2.Current).toBe(Project.Current) expect(ProjectV2.Current).toBe(Project.Current)
expect(ProjectV2.Directory).toBe(Project.Directory) expect(ProjectV2.Directory).toBe(Project.Directory)
expect(ProjectV2.Directories).toBe(Project.Directories) expect(ProjectV2.Directories).toBe(Project.Directories)
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted) expect(CoreSessionInput.Message).toBe(SessionInput.Message)
expect(CoreSessionInput.User).toBe(SessionInput.User)
expect(CoreSessionInput.Synthetic).toBe(SessionInput.Synthetic)
expect(CoreSessionMessage.Info).toBe(SessionMessage.Info) expect(CoreSessionMessage.Info).toBe(SessionMessage.Info)
expect(Api.groups["server.session"].identifier).toBe("server.session") expect(Api.groups["server.session"].identifier).toBe("server.session")
expect(Api.groups["server.project"].identifier).toBe("server.project") expect(Api.groups["server.project"].identifier).toBe("server.project")

View file

@ -178,7 +178,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
}) })
const admitted = yield* client.session.prompt({ const admitted = yield* client.session.prompt({
sessionID: Session.ID.make("ses_test"), sessionID: Session.ID.make("ses_test"),
prompt: Prompt.make({ text: "Hello" }), text: "Hello",
resume: false, resume: false,
}) })
yield* client.session.compact({ sessionID: Session.ID.make("ses_test") }) yield* client.session.compact({ sessionID: Session.ID.make("ses_test") })
@ -201,7 +201,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
expect(Object.getPrototypeOf(result.created)).toBe(Object.prototype) expect(Object.getPrototypeOf(result.created)).toBe(Object.prototype)
expect(result.created.id).toBe("ses_test") expect(result.created.id).toBe("ses_test")
expect(Object.getPrototypeOf(result.admitted)).toBe(Object.prototype) expect(Object.getPrototypeOf(result.admitted)).toBe(Object.prototype)
expect(Object.getPrototypeOf(result.admitted.prompt)).toBe(Object.prototype) expect(Object.getPrototypeOf(result.admitted.data)).toBe(Object.prototype)
expect(DateTime.toEpochMillis(result.admitted.timeCreated)).toBe(1_717_171_717_000) expect(DateTime.toEpochMillis(result.admitted.timeCreated)).toBe(1_717_171_717_000)
expect(result.context).toEqual([]) expect(result.context).toEqual([])
expect(logQueries[0]).toEqual({ after: "0" }) expect(logQueries[0]).toEqual({ after: "0" })
@ -259,7 +259,8 @@ const admission = {
admittedSeq: 0, admittedSeq: 0,
id: "msg_test", id: "msg_test",
sessionID: "ses_test", sessionID: "ses_test",
prompt: { text: "Hello" }, type: "user",
data: { text: "Hello" },
delivery: "steer", delivery: "steer",
timeCreated: 1_717_171_717_000, timeCreated: 1_717_171_717_000,
}, },

View file

@ -6,6 +6,7 @@ test("exposes every standard HTTP API group", () => {
expect(Object.keys(client)).toEqual([ expect(Object.keys(client)).toEqual([
"health", "health",
"server",
"location", "location",
"agent", "agent",
"plugin", "plugin",
@ -45,6 +46,21 @@ test("exposes every standard HTTP API group", () => {
expect(Object.keys(client.project)).toEqual(["list", "current", "directories"]) expect(Object.keys(client.project)).toEqual(["list", "current", "directories"])
}) })
test("server.get uses the public HTTP contract", async () => {
let request: Request | undefined
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input) => {
request = input instanceof Request ? input : new Request(input)
return Response.json({ urls: ["http://192.168.1.10:4096"] })
},
})
expect(await client.server.get()).toEqual({ urls: ["http://192.168.1.10:4096"] })
expect(request?.method).toBe("GET")
expect(request?.url).toBe("http://localhost:3000/api/server")
})
test("MCP resource catalog uses the public HTTP contract", async () => { test("MCP resource catalog uses the public HTTP contract", async () => {
let request: Request | undefined let request: Request | undefined
const client = OpenCode.make({ const client = OpenCode.make({
@ -258,6 +274,7 @@ test("session methods use the public HTTP contract", async () => {
}) })
} }
if (url.includes("/prompt")) return Response.json(admission) if (url.includes("/prompt")) return Response.json(admission)
if (url.includes("/synthetic")) return Response.json(syntheticAdmission)
if (url.endsWith("/compact")) return Response.json(compactionAdmission) if (url.endsWith("/compact")) return Response.json(compactionAdmission)
if (url.includes("/context")) return Response.json({ data: [] }) if (url.includes("/context")) return Response.json({ data: [] })
if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage }) if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage })
@ -278,7 +295,13 @@ test("session methods use the public HTTP contract", async () => {
}) })
const admitted = await client.session.prompt({ const admitted = await client.session.prompt({
sessionID: "ses_test", sessionID: "ses_test",
prompt: { text: "Hello" }, text: "Hello",
resume: false,
})
const synthetic = await client.session.synthetic({
sessionID: "ses_test",
text: "Completed",
delivery: "queue",
resume: false, resume: false,
}) })
await client.session.compact({ sessionID: "ses_test" }) await client.session.compact({ sessionID: "ses_test" })
@ -293,6 +316,7 @@ test("session methods use the public HTTP contract", async () => {
expect(active).toEqual({ ses_test: { type: "running" } }) expect(active).toEqual({ ses_test: { type: "running" } })
expect(created.id).toBe("ses_test") expect(created.id).toBe("ses_test")
expect(admitted.id).toBe("msg_test") expect(admitted.id).toBe("msg_test")
expect(synthetic).toMatchObject({ type: "synthetic", data: { text: "Completed" }, delivery: "queue" })
expect(context).toEqual([]) expect(context).toEqual([])
expect(log).toEqual([modelSwitchedEvent, synced]) expect(log).toEqual([modelSwitchedEvent, synced])
expect(message).toEqual(modelSwitchedMessage) expect(message).toEqual(modelSwitchedMessage)
@ -303,6 +327,7 @@ test("session methods use the public HTTP contract", async () => {
["POST", "http://localhost:3000/api/session/ses_test/agent"], ["POST", "http://localhost:3000/api/session/ses_test/agent"],
["POST", "http://localhost:3000/api/session/ses_test/model"], ["POST", "http://localhost:3000/api/session/ses_test/model"],
["POST", "http://localhost:3000/api/session/ses_test/prompt"], ["POST", "http://localhost:3000/api/session/ses_test/prompt"],
["POST", "http://localhost:3000/api/session/ses_test/synthetic"],
["POST", "http://localhost:3000/api/session/ses_test/compact"], ["POST", "http://localhost:3000/api/session/ses_test/compact"],
["POST", "http://localhost:3000/api/session/ses_test/wait"], ["POST", "http://localhost:3000/api/session/ses_test/wait"],
["GET", "http://localhost:3000/api/session/ses_test/context"], ["GET", "http://localhost:3000/api/session/ses_test/context"],
@ -313,7 +338,14 @@ test("session methods use the public HTTP contract", async () => {
const body = requests.find((request) => request.url.endsWith("/api/session/ses_test/prompt"))?.init?.body const body = requests.find((request) => request.url.endsWith("/api/session/ses_test/prompt"))?.init?.body
if (typeof body !== "string") throw new Error("Expected JSON request body") if (typeof body !== "string") throw new Error("Expected JSON request body")
expect(JSON.parse(body)).toEqual({ expect(JSON.parse(body)).toEqual({
prompt: { text: "Hello" }, text: "Hello",
resume: false,
})
const syntheticBody = requests.find((request) => request.url.endsWith("/synthetic"))?.init?.body
if (typeof syntheticBody !== "string") throw new Error("Expected JSON synthetic request body")
expect(JSON.parse(syntheticBody)).toEqual({
text: "Completed",
delivery: "queue",
resume: false, resume: false,
}) })
}) })
@ -376,12 +408,25 @@ const admission = {
admittedSeq: 0, admittedSeq: 0,
id: "msg_test", id: "msg_test",
sessionID: "ses_test", sessionID: "ses_test",
prompt: { text: "Hello" }, type: "user",
data: { text: "Hello" },
delivery: "steer", delivery: "steer",
timeCreated: 1_717_171_717_000, timeCreated: 1_717_171_717_000,
}, },
} }
const syntheticAdmission = {
data: {
admittedSeq: 1,
id: "msg_synthetic",
sessionID: "ses_test",
type: "synthetic",
data: { text: "Completed" },
delivery: "queue",
timeCreated: 1_717_171_717_000,
},
}
const compactionAdmission = { const compactionAdmission = {
data: { data: {
type: "compaction", type: "compaction",

View file

@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/codemode", "name": "@opencode-ai/codemode",
"version": "1.17.14", "version": "1.17.15",
"description": "Effect-native confined code execution over schema-described tools", "description": "Effect-native confined code execution over schema-described tools",
"private": true, "private": true,
"type": "module", "type": "module",

View file

@ -0,0 +1,28 @@
Test262: ECMAScript Test Suite ("Software") is protected by copyright and is being
made available under the "BSD License", included below. This Software may be subject to third party rights (rights
from parties other than Ecma International), including patent rights, and no licenses under such third party rights
are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA
CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://www.ecma-international.org/ipr FOR
INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS*.
Copyright (C) 2012 Ecma International
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
* Ecma International Standards hereafter means Ecma International Standards as well as Ecma Technical Reports

View file

@ -0,0 +1,325 @@
/*
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
* - test/built-ins/Array/prototype/map/15.4.4.19-8-1.js
* - test/built-ins/Array/prototype/map/15.4.4.19-8-2.js
* - test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js
* - test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js
* - test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js
* - test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js
* - test/built-ins/Array/prototype/find/predicate-call-parameters.js
* - test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js
* - test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js
* - test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js
* - test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js
* - test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js
* - test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js
* - test/built-ins/Array/prototype/findLast/predicate-call-parameters.js
* - test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js
* - test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js
* - test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js
* - test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js
* - test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js
* - test/built-ins/Array/prototype/some/15.4.4.17-7-1.js
* - test/built-ins/Array/prototype/some/15.4.4.17-8-1.js
* - test/built-ins/Array/prototype/every/15.4.4.16-7-1.js
* - test/built-ins/Array/prototype/every/15.4.4.16-8-1.js
* - test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js
* - test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js
* - test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js
* - test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js
* - test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js
* - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js
* - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js
* - test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js
* - test/built-ins/Array/prototype/flatMap/depth-always-one.js
* - test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js
* - test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js
* - test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js
* - test/built-ins/Array/prototype/sort/stability-5-elements.js
* - test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js
* - test/built-ins/Array/prototype/toSorted/comparefn-default.js
* - test/built-ins/Array/prototype/toSorted/immutable.js
* - test/built-ins/Array/prototype/toSorted/zero-or-one-element.js
*
* Copyright (C) 2015 the V8 project authors. All rights reserved.
* Copyright (C) 2018 Mathias Bynens. All rights reserved.
* Copyright (C) 2018 Shilpi Jain and Michael Ficarra. All rights reserved.
* Copyright (C) 2021 Igalia, S.L. All rights reserved.
* Copyright (C) 2021 Microsoft. All rights reserved.
* Copyright (C) 2025 Google. All rights reserved.
* Copyright (C) 2026 Garham Lee. All rights reserved.
* Copyright (c) 2012 Ecma International. All rights reserved.
* Copyright 2009 the Sputnik authors. All rights reserved.
* Test262 portions are governed by the BSD license in LICENSE.test262.
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const cases = [
{
path: "test/built-ins/Array/prototype/map/15.4.4.19-8-1.js",
code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.map((value) => { input[2] = 3; input[5] = 6; return 1 }); return [result.length, result[5] === undefined]`,
expected: [5, true],
},
{
path: "test/built-ins/Array/prototype/map/15.4.4.19-8-2.js",
code: `const input = [1, 2, 3, 4, 5]; const result = input.map((value) => { input[4] = -1; return value > 0 ? 1 : 0 }); return [result.length, result[4]]`,
expected: [5, 0],
},
{
path: "test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js",
code: `const input = []; input[10] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.map(() => { calls += 1; return 1 }); return [result.length, calls, 0 in result, 1 in result]`,
expected: [10, 1, false, true],
},
{
path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js",
code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.filter(() => { input[2] = 3; input[5] = 6; return true }); return result`,
expected: [1, 2, 3, 4, 5],
},
{
path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js",
code: `const input = [1, 2, 3, 4, 5]; return input.filter((value) => { input[2] = -1; input[4] = -1; return value > 0 })`,
expected: [1, 2, 4],
},
{
path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js",
code: `const input = []; input[9] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.filter(() => { calls += 1; return false }); return [result, calls]`,
expected: [[], 1],
},
{
path: "test/built-ins/Array/prototype/find/predicate-call-parameters.js",
code: `const input = [10, 20]; const seen = []; input.find((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
expected: [
[10, 0, true],
[20, 1, true],
],
},
{
path: "test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js",
code: `return [1, 2, 3].find((value) => value > 1)`,
expected: 2,
},
{
path: "test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js",
code: `return [1, 2, 3].find((value) => value > 4) === undefined`,
expected: true,
},
{
path: "test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js",
code: `let calls = 0; const result = [].find(() => { calls += 1; return true }); return [result === undefined, calls]`,
expected: [true, 0],
},
{
path: "test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js",
code: `const input = [10, 20]; const seen = []; input.findIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
expected: [
[10, 0, true],
[20, 1, true],
],
},
{
path: "test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js",
code: `return [1, 2, 3].findIndex((value) => value > 1)`,
expected: 1,
},
{
path: "test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js",
code: `return [1, 2, 3].findIndex((value) => value > 4)`,
expected: -1,
},
{
path: "test/built-ins/Array/prototype/findLast/predicate-call-parameters.js",
code: `const input = [10, 20]; const seen = []; input.findLast((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
expected: [
[20, 1, true],
[10, 0, true],
],
},
{
path: "test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js",
code: `return [1, 2, 3].findLast((value) => value < 3)`,
expected: 2,
},
{
path: "test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js",
code: `return [1, 2, 3].findLast((value) => value > 4) === undefined`,
expected: true,
},
{
path: "test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js",
code: `const input = [10, 20]; const seen = []; input.findLastIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
expected: [
[20, 1, true],
[10, 0, true],
],
},
{
path: "test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js",
code: `return [1, 2, 3].findLastIndex((value) => value < 3)`,
expected: 1,
},
{
path: "test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js",
code: `return [1, 2, 3].findLastIndex((value) => value > 4)`,
expected: -1,
},
{
path: "test/built-ins/Array/prototype/some/15.4.4.17-7-1.js",
code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.some((value) => { input[2] = 3; seen.push(value); return false }); return [result, seen.includes(3)]`,
expected: [false, true],
},
{
path: "test/built-ins/Array/prototype/some/15.4.4.17-8-1.js",
code: `return [].some(() => true)`,
expected: false,
},
{
path: "test/built-ins/Array/prototype/every/15.4.4.16-7-1.js",
code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.every((value) => { input[2] = 3; seen.push(value); return true }); return [result, seen.includes(3)]`,
expected: [true, true],
},
{
path: "test/built-ins/Array/prototype/every/15.4.4.16-8-1.js",
code: `return [].every(() => false)`,
expected: true,
},
{
path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js",
code: `const input = [1, 2]; input[3] = 4; input[4] = 5; let calls = 0; input.forEach(() => { calls += 1; input[2] = 3; input[5] = 6 }); return calls`,
expected: 5,
},
{
path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js",
code: `const input = [1, 2, 3]; const seen = []; input.forEach((value, index) => { seen.push(value); if (index === 0) input.pop() }); return seen`,
expected: [1, 2],
},
{
path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js",
code: `const input = [1, 2]; input[3] = 4; input[4] = "5"; return input.reduce((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`,
expected: "105",
},
{
path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js",
code: `let calls = 0; const result = [1].reduce(() => { calls += 1; return 2 }); return [result, calls]`,
expected: [1, 0],
},
{
path: "test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js",
code: `const input = [1, 2, 3, 4, 5]; input.reduce(() => 1); return input`,
expected: [1, 2, 3, 4, 5],
},
{
path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js",
code: `const input = ["1", 2]; input[3] = 4; input[4] = "5"; return input.reduceRight((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`,
expected: "54321",
},
{
path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js",
code: `let calls = 0; const result = [1].reduceRight(() => { calls += 1; return 2 }); return [result, calls]`,
expected: [1, 0],
},
{
path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js",
code: `const input = [1, 2, 3, 4, 5]; input.reduceRight(() => 1); return input`,
expected: [1, 2, 3, 4, 5],
},
{
path: "test/built-ins/Array/prototype/flatMap/depth-always-one.js",
code: `return [1, 2, 3].flatMap((value) => [[value * 2]])`,
expected: [[2], [4], [6]],
},
{
path: "test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js",
code: `try { [1, 2].flatMap(() => { throw "stop" }) } catch (error) { return error === "stop" } return false`,
expected: true,
},
{
path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js",
code: `const input = []; input[2] = 0; input.pop(); input.sort(); return [input.length, input[0] === undefined, input[1] === undefined]`,
expected: [2, true, true],
},
{
path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js",
code: `return ["z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "M", "L", "K", "J", "I", "H", "G", "F", "E", "D", "C", "B", "A"].sort()`,
expected: [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
],
},
{
path: "test/built-ins/Array/prototype/sort/stability-5-elements.js",
code: `const input = [{ n: "A", r: 2 }, { n: "B", r: 3 }, { n: "C", r: 2 }, { n: "D", r: 3 }, { n: "E", r: 3 }]; return input.sort((left, right) => right.r - left.r).map((item) => item.n).join("")`,
expected: "BDEAC",
},
{
path: "test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js",
code: `const mixed = [333, 33, 3, 222, 22, 2, 111, 11, 1]; return [[1, 2, 3, 4].toSorted((a, b) => a - b), [4, 3, 2, 1].toSorted((a, b) => a - b), mixed.toSorted((a, b) => a - b), [1, 2, 3, 4].toSorted((a, b) => b - a), [4, 3, 2, 1].toSorted((a, b) => b - a), mixed.toSorted((a, b) => b - a)]`,
expected: [
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 11, 22, 33, 111, 222, 333],
[4, 3, 2, 1],
[4, 3, 2, 1],
[333, 222, 111, 33, 22, 11, 3, 2, 1],
],
},
{
path: "test/built-ins/Array/prototype/toSorted/comparefn-default.js",
code: `return [[1, 2, 3, 4].toSorted(), [4, 3, 2, 1].toSorted(), ["a", 2, 1, "z"].toSorted(), [333, 33, 3, 222, 22, 2, 111, 11, 1].toSorted()]`,
expected: [
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, "a", "z"],
[1, 11, 111, 2, 22, 222, 3, 33, 333],
],
},
{
path: "test/built-ins/Array/prototype/toSorted/immutable.js",
code: `const input = [2, 0, 1]; const result = input.toSorted(); return [input, result !== input]`,
expected: [[2, 0, 1], true],
},
{
path: "test/built-ins/Array/prototype/toSorted/zero-or-one-element.js",
code: `const zero = []; const one = [1]; const zeroResult = zero.toSorted(); const oneResult = one.toSorted(); return [zeroResult, oneResult, zeroResult !== zero, oneResult !== one]`,
expected: [[], [1], true, true],
},
] as const
describe("Test262 Array callback adaptations", () => {
for (const item of cases) {
test(item.path, async () => {
expect(await value(item.code)).toEqual(item.expected)
})
}
})

View file

@ -0,0 +1,323 @@
/*
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
* - test/built-ins/Array/prototype/includes/samevaluezero.js
* - test/built-ins/Array/prototype/includes/using-fromindex.js
* - test/built-ins/Array/prototype/join/S15.4.4.5_A3.1_T1.js
* - test/built-ins/Array/prototype/join/S15.4.4.5_A3.2_T1.js
* - test/built-ins/Array/prototype/slice/S15.4.4.10_A1.2_T2.js
* - test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T1.js
* - test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T2.js
* - test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T3.js
* - test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js
* - test/built-ins/Array/prototype/indexOf/length-zero-returns-minus-one.js
* - test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js
* - test/built-ins/Array/prototype/lastIndexOf/length-zero-returns-minus-one.js
* - test/built-ins/Array/prototype/at/returns-item.js
* - test/built-ins/Array/prototype/at/returns-item-relative-index.js
* - test/built-ins/Array/prototype/at/returns-undefined-for-out-of-range-index.js
* - test/built-ins/Array/prototype/flat/null-undefined-elements.js
* - test/built-ins/Array/prototype/flat/positive-infinity.js
* - test/built-ins/Array/prototype/reverse/S15.4.4.8_A1_T1.js
* - test/built-ins/Array/prototype/toReversed/immutable.js
* - test/built-ins/Array/prototype/toReversed/zero-or-one-element.js
* - test/built-ins/Array/prototype/with/immutable.js
* - test/built-ins/Array/prototype/with/index-negative.js
* - test/built-ins/Array/prototype/push/S15.4.4.7_A1_T1.js
* - test/built-ins/Array/prototype/pop/S15.4.4.6_A1.1_T1.js
* - test/built-ins/Array/prototype/shift/S15.4.4.9_A1.1_T1.js
* - test/built-ins/Array/prototype/unshift/S15.4.4.13_A1_T1.js
* - test/built-ins/Array/prototype/splice/S15.4.4.12_A1.1_T1.js
* - test/built-ins/Array/prototype/splice/S15.4.4.12_A1.2_T1.js
* - test/built-ins/Array/prototype/splice/called_with_one_argument.js
* - test/built-ins/Array/prototype/fill/fill-values.js
* - test/built-ins/Array/prototype/fill/fill-values-custom-start-and-end.js
* - test/built-ins/Array/prototype/fill/return-this.js
* - test/built-ins/Array/prototype/copyWithin/non-negative-target-start-and-end.js
* - test/built-ins/Array/prototype/copyWithin/return-this.js
* - test/built-ins/Array/prototype/keys/iteration.js
* - test/built-ins/Array/prototype/values/iteration.js
* - test/built-ins/Array/prototype/entries/iteration.js
* - test/built-ins/Array/isArray/15.4.3.2-0-3.js
* - test/built-ins/Array/isArray/15.4.3.2-0-4.js
* - test/built-ins/Array/from/from-array.js
* - test/built-ins/Array/from/from-string.js
* - test/built-ins/Array/from/array-like-has-length-but-no-indexes-with-values.js
* - test/built-ins/Array/of/creates-a-new-array-from-arguments.js
*
* Copyright (C) 2015 André Bargull. All rights reserved.
* Copyright (C) 2015 the V8 project authors. All rights reserved.
* Copyright (C) 2016 the V8 project authors. All rights reserved.
* Copyright (C) 2018 Shilpi Jain and Michael Ficarra. All rights reserved.
* Copyright (C) 2020 Alexey Shvayka. All rights reserved.
* Copyright (C) 2020 Rick Waldron. All rights reserved.
* Copyright (C) 2021 Igalia, S.L. All rights reserved.
* Copyright (c) 2012 Ecma International. All rights reserved.
* Copyright (c) 2014 Hank Yates. All rights reserved.
* Copyright (c) 2015 the V8 project authors. All rights reserved.
* Copyright (c) 2021 Rick Waldron. All rights reserved.
* Copyright 2009 the Sputnik authors. All rights reserved.
* Copyright 2015 Microsoft Corporation. All rights reserved.
* Copyright 2016 The V8 project authors. All rights reserved.
* Test262 portions are governed by the BSD license in LICENSE.test262.
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const cases = [
{
path: "test/built-ins/Array/prototype/includes/samevaluezero.js",
code: `const input = [42, 0, 1, NaN]; return [input.includes(42), input.includes("42"), input.includes([42]), input.includes(true), input.includes(NaN), input.includes(0), input.includes(-0), input.includes(null), input.includes("")]`,
expected: [true, false, false, false, true, true, true, false, false],
},
{
path: "test/built-ins/Array/prototype/includes/using-fromindex.js",
code: `const input = ["a", "b", "c"]; return [input.includes("a", 0), input.includes("a", 1), input.includes("a", -4), input.includes("a", -3), input.includes("a", -2), input.includes("b", 0), input.includes("b", 1), input.includes("b", 2), input.includes("b", -3), input.includes("b", -2), input.includes("b", -1), input.includes("c", 0), input.includes("c", 2), input.includes("c", 3), input.includes("c", -3), input.includes("c", -1)]`,
expected: [true, false, true, true, false, true, true, false, true, true, false, true, true, false, true, true],
},
{
path: "test/built-ins/Array/prototype/join/S15.4.4.5_A3.1_T1.js",
code: `return [[0, 1, 2, 3].join("&"), [0, 1, 2, 3].join("")]`,
expected: ["0&1&2&3", "0123"],
},
{
path: "test/built-ins/Array/prototype/join/S15.4.4.5_A3.2_T1.js",
code: `return [
["", "", ""].join(""),
["&", "&", "&"].join("&"),
[true, true, true].join(),
[null, null, null].join(),
[undefined, undefined, undefined].join(),
[Infinity, Infinity, Infinity].join(),
[NaN, NaN, NaN].join(),
]`,
expected: ["", "&&&&&", "true,true,true", ",,", ",,", "Infinity,Infinity,Infinity", "NaN,NaN,NaN"],
},
{
path: "test/built-ins/Array/prototype/slice/S15.4.4.10_A1.2_T2.js",
code: `return [0, 1, 2, 3, 4].slice(-1, 5)`,
expected: [4],
},
{
path: "test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T1.js",
code: `return [].concat([0, 1], [2, 3, 4])`,
expected: [0, 1, 2, 3, 4],
},
{
path: "test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T2.js",
code: `const object = { value: 1 }; const result = [0].concat(object, [1, 2], -1, true, "NaN"); return [result, result[1] === object]`,
expected: [[0, { value: 1 }, 1, 2, -1, true, "NaN"], true],
},
{
path: "test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T3.js",
code: `const input = [0, 1]; const result = input.concat(); return [result, result !== input]`,
expected: [[0, 1], true],
},
{
path: "test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js",
code: `const result = [true].indexOf(true, -0); return [result, 1 / result === Infinity]`,
expected: [0, true],
},
{
path: "test/built-ins/Array/prototype/indexOf/length-zero-returns-minus-one.js",
code: `return [].indexOf(1)`,
expected: -1,
},
{
path: "test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js",
code: `const result = [true].lastIndexOf(true, -0); return [result, 1 / result === Infinity]`,
expected: [0, true],
},
{
path: "test/built-ins/Array/prototype/lastIndexOf/length-zero-returns-minus-one.js",
code: `return [].lastIndexOf(1)`,
expected: -1,
},
{
path: "test/built-ins/Array/prototype/at/returns-item.js",
code: `const input = [1, 2, 3, 4, undefined, 5]; return [input.at(0), input.at(1), input.at(2), input.at(3), input.at(4) === undefined, input.at(5)]`,
expected: [1, 2, 3, 4, true, 5],
},
{
path: "test/built-ins/Array/prototype/at/returns-item-relative-index.js",
code: `const input = [1, 2, 3, 4, undefined, 5]; return [input.at(0), input.at(-1), input.at(-2) === undefined, input.at(-3), input.at(-4)]`,
expected: [1, 5, true, 4, 3],
},
{
path: "test/built-ins/Array/prototype/at/returns-undefined-for-out-of-range-index.js",
code: `const input = []; return [input.at(-2) === undefined, input.at(0) === undefined, input.at(1) === undefined]`,
expected: [true, true, true],
},
{
path: "test/built-ins/Array/prototype/flat/null-undefined-elements.js",
code: `const result = [1, [null, [undefined]]].flat(2); return [result.length, result[0], result[1] === null, result[2] === undefined]`,
expected: [3, 1, true, true],
},
{
path: "test/built-ins/Array/prototype/flat/positive-infinity.js",
code: `return [1, [2, [3, [4]]]].flat(Infinity)`,
expected: [1, 2, 3, 4],
},
{
path: "test/built-ins/Array/prototype/reverse/S15.4.4.8_A1_T1.js",
code: `const empty = []; const one = [1]; const input = [1, 2]; const emptyResult = empty.reverse(); const oneResult = one.reverse(); const result = input.reverse(); return [emptyResult === empty, oneResult === one, result === input, input]`,
expected: [true, true, true, [2, 1]],
},
{
path: "test/built-ins/Array/prototype/toReversed/immutable.js",
code: `const input = [0, 1, 2]; const result = input.toReversed(); return [input, result !== input]`,
expected: [[0, 1, 2], true],
},
{
path: "test/built-ins/Array/prototype/toReversed/zero-or-one-element.js",
code: `const zero = []; const one = [1]; const zeroResult = zero.toReversed(); const oneResult = one.toReversed(); return [zeroResult, oneResult, zeroResult !== zero, oneResult !== one]`,
expected: [[], [1], true, true],
},
{
path: "test/built-ins/Array/prototype/with/immutable.js",
code: `const input = [0, 1, 2]; const result = input.with(1, 3); return [input, result !== input, input.with(1, 1) !== input]`,
expected: [[0, 1, 2], true, true],
},
{
path: "test/built-ins/Array/prototype/with/index-negative.js",
code: `const input = [0, 1, 2]; return [input.with(-1, 4), input.with(-3, 4)]`,
expected: [
[0, 1, 4],
[4, 1, 2],
],
},
{
path: "test/built-ins/Array/prototype/push/S15.4.4.7_A1_T1.js",
code: `const input = []; return [input.push(1), input.push(), input.push(-1), input]`,
expected: [1, 1, 2, [1, -1]],
},
{
path: "test/built-ins/Array/prototype/pop/S15.4.4.6_A1.1_T1.js",
code: `const input = []; return [input.pop() === undefined, input.length]`,
expected: [true, 0],
},
{
path: "test/built-ins/Array/prototype/shift/S15.4.4.9_A1.1_T1.js",
code: `const input = []; return [input.shift() === undefined, input.length]`,
expected: [true, 0],
},
{
path: "test/built-ins/Array/prototype/unshift/S15.4.4.13_A1_T1.js",
code: `const input = []; return [input.unshift(1), input[0], input.unshift(), input.unshift(-1), input]`,
expected: [1, 1, 1, 2, [-1, 1]],
},
{
path: "test/built-ins/Array/prototype/splice/S15.4.4.12_A1.1_T1.js",
code: `const input = [0, 1, 2, 3]; const removed = input.splice(0, 3); return [input, removed]`,
expected: [[3], [0, 1, 2]],
},
{
path: "test/built-ins/Array/prototype/splice/S15.4.4.12_A1.2_T1.js",
code: `const input = [0, 1]; const removed = input.splice(-2, -1); return [input, removed]`,
expected: [[0, 1], []],
},
{
path: "test/built-ins/Array/prototype/splice/called_with_one_argument.js",
code: `const input = ["first", "second", "third"]; const removed = input.splice(1); return [input, removed]`,
expected: [["first"], ["second", "third"]],
},
{
path: "test/built-ins/Array/prototype/fill/fill-values-custom-start-and-end.js",
code: `const input = [0, 0, 0, 0, 0]; input.fill(8, -3, 4); const sparse = []; sparse[4] = 0; sparse.fill(8, 1, 3); return [[0, 0, 0].fill(8, 1, 2), input, [0, 0, 0, 0, 0].fill(8, -2, -1), [0, 0, 0, 0, 0].fill(8, -1, -3), [0 in sparse, sparse[1], sparse[2], 3 in sparse, sparse[4]]]`,
expected: [
[0, 8, 0],
[0, 0, 8, 8, 0],
[0, 0, 0, 8, 0],
[0, 0, 0, 0, 0],
[false, 8, 8, false, 0],
],
},
{
path: "test/built-ins/Array/prototype/fill/return-this.js",
code: `const input = []; return input.fill(1) === input`,
expected: true,
},
{
path: "test/built-ins/Array/prototype/fill/fill-values.js",
code: `const omitted = [0, 0].fill(); return [[].fill(8), omitted.map((value) => value === undefined), [0, 0, 0].fill(8)]`,
expected: [[], [true, true], [8, 8, 8]],
},
{
path: "test/built-ins/Array/prototype/copyWithin/non-negative-target-start-and-end.js",
code: `return [[0, 1, 2, 3].copyWithin(0, 0, 0), [0, 1, 2, 3].copyWithin(0, 0, 2), [0, 1, 2, 3].copyWithin(0, 1, 2), [0, 1, 2, 3].copyWithin(1, 0, 2), [0, 1, 2, 3, 4, 5].copyWithin(1, 3, 5)]`,
expected: [
[0, 1, 2, 3],
[0, 1, 2, 3],
[1, 1, 2, 3],
[0, 0, 1, 3],
[0, 3, 4, 3, 4, 5],
],
},
{
path: "test/built-ins/Array/prototype/copyWithin/return-this.js",
code: `const input = [0, 1, 2, 3]; const result = input.copyWithin(1, 0, 2); return [input, result === input]`,
expected: [[0, 0, 1, 3], true],
},
{
path: "test/built-ins/Array/prototype/keys/iteration.js",
code: `return ["a", "b", "c"].keys()`,
expected: [0, 1, 2],
},
{
path: "test/built-ins/Array/prototype/values/iteration.js",
code: `return ["a", "b", "c"].values()`,
expected: ["a", "b", "c"],
},
{
path: "test/built-ins/Array/prototype/entries/iteration.js",
code: `return ["a", "b"].entries()`,
expected: [
[0, "a"],
[1, "b"],
],
},
{
path: "test/built-ins/Array/isArray/15.4.3.2-0-3.js",
code: `return [Array.isArray([]), Array.isArray([1]), Array.isArray(Array.of(1))]`,
expected: [true, true, true],
},
{
path: "test/built-ins/Array/isArray/15.4.3.2-0-4.js",
code: `return [Array.isArray(42), Array.isArray({}), Array.isArray(null), Array.isArray("array")]`,
expected: [false, false, false, false],
},
{
path: "test/built-ins/Array/from/from-array.js",
code: `const input = [0, "foo", undefined, Infinity]; const result = Array.from(input); return [result.length, result[0], result[1], result[2] === undefined, result[3] === Infinity, result !== input, result instanceof Array]`,
expected: [4, 0, "foo", true, true, true, true],
},
{
path: "test/built-ins/Array/from/from-string.js",
code: `return Array.from("Test")`,
expected: ["T", "e", "s", "t"],
},
{
path: "test/built-ins/Array/from/array-like-has-length-but-no-indexes-with-values.js",
code: `const result = Array.from({ length: 5 }); const mapped = result.map(() => 1); return [result.length, result.map((value) => value === undefined), mapped.length, mapped]`,
expected: [5, [true, true, true, true, true], 5, [1, 1, 1, 1, 1]],
},
{
path: "test/built-ins/Array/of/creates-a-new-array-from-arguments.js",
code: `const mixed = Array.of(undefined, false, null, undefined); return [Array.of("Mike", "Rick", "Leo"), mixed.length, mixed[0] === undefined, mixed[1], mixed[2], mixed[3] === undefined, Array.of()]`,
expected: [["Mike", "Rick", "Leo"], 4, true, false, null, true, []],
},
] as const
describe("Test262 Array core adaptations", () => {
for (const item of cases) {
test(item.path, async () => {
expect(await value(item.code)).toEqual(item.expected)
})
}
})

View file

@ -42,11 +42,6 @@ describe("H2: string property access reads as undefined (not a throw)", () => {
test("unknown property on a number is undefined", async () => { test("unknown property on a number is undefined", async () => {
expect(await value(`return (5).foo ?? "n"`)).toBe("n") expect(await value(`return (5).foo ?? "n"`)).toBe("n")
}) })
test("supported string methods still work", async () => {
expect(await value(`return "AB".toLowerCase()`)).toBe("ab")
expect(await value(`return "hello".length`)).toBe(5)
})
}) })
describe("H3: array property access reads as undefined (not a throw)", () => { describe("H3: array property access reads as undefined (not a throw)", () => {
@ -63,8 +58,7 @@ describe("H3: array property access reads as undefined (not a throw)", () => {
expect(await value(`return [1,2,3].toSpliced === undefined`)).toBe(true) expect(await value(`return [1,2,3].toSpliced === undefined`)).toBe(true)
}) })
test("supported array methods and indexing still work", async () => { test("array indexing still works", async () => {
expect(await value(`return [1,2,3].map(x => x + 1)`)).toEqual([2, 3, 4])
expect(await value(`return [1,2,3][9] === undefined`)).toBe(true) expect(await value(`return [1,2,3][9] === undefined`)).toBe(true)
expect(await value(`return [1,2,3][9]`)).toBeNull() expect(await value(`return [1,2,3][9]`)).toBeNull()
}) })
@ -202,9 +196,6 @@ describe("Error values and instanceof", () => {
"TypeError", "TypeError",
true, true,
]) ])
expect(await value(`try { "a".normalize("NOPE") } catch (e) { return [e.name, e instanceof RangeError] }`)).toEqual(
["RangeError", true],
)
expect(await value(`try { "a".match("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`)).toEqual([ expect(await value(`try { "a".match("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`)).toEqual([
"SyntaxError", "SyntaxError",
true, true,
@ -263,55 +254,18 @@ describe("Error values and instanceof", () => {
}) })
}) })
describe("array methods: splice, fill, copyWithin, keys/values/entries", () => { describe("CodeMode-specific array behavior", () => {
test("sort and reverse mutate and return the receiver", async () => { test("sort with a comparator mutates and returns the receiver", async () => {
expect( expect(
await value(` await value(`
const sorted = [3, 1, 2] const input = [3, 1, 2]
const sortResult = sorted.sort((a, b) => a - b) const result = input.sort((a, b) => a - b)
const reversed = [1, 2, 3] return { input, same: input === result }
const reverseResult = reversed.reverse()
return { sorted, sameSort: sorted === sortResult, reversed, sameReverse: reversed === reverseResult }
`), `),
).toEqual({ sorted: [1, 2, 3], sameSort: true, reversed: [3, 2, 1], sameReverse: true }) ).toEqual({ input: [1, 2, 3], same: true })
}) })
test("array callbacks receive the receiver and observe later mutations", async () => { test("splice can replace and insert elements", async () => {
expect(
await value(`
const values = [1, 2, 3]
const seen = values.map((value, index, receiver) => {
if (index === 0) values[1] = 9
return [value, receiver === values]
})
return seen
`),
).toEqual([
[1, true],
[9, true],
[3, true],
])
expect(
await value(`
const values = [1, 2, 3]
const seen = []
values.forEach((value, index) => {
seen.push(value)
if (index === 0) values.pop()
})
return seen
`),
).toEqual([1, 2])
})
test("splice removes in place and returns the removed elements", async () => {
expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({
removed: [2, 3],
a: [1, 4],
})
})
test("splice inserts new elements at the cut", async () => {
expect(await value(`const a = ["a","d"]; a.splice(1, 0, "b", "c"); return a`)).toEqual(["a", "b", "c", "d"]) expect(await value(`const a = ["a","d"]; a.splice(1, 0, "b", "c"); return a`)).toEqual(["a", "b", "c", "d"])
expect(await value(`const a = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({ expect(await value(`const a = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({
removed: [2], removed: [2],
@ -319,32 +273,12 @@ describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
}) })
}) })
test("splice with one argument removes to the end; negative start counts back", async () => {
expect(await value(`const a = [1,2,3]; const removed = a.splice(1); return { removed, a }`)).toEqual({
removed: [2, 3],
a: [1],
})
expect(await value(`const a = [1,2,3]; const removed = a.splice(-1); return { removed, a }`)).toEqual({
removed: [3],
a: [1, 2],
})
})
test("splice rejects inserting a container into itself", async () => { test("splice rejects inserting a container into itself", async () => {
const err = await error(`const a = [1]; a.splice(0, 0, [a]); return a`) const err = await error(`const a = [1]; a.splice(0, 0, [a]); return a`)
expect(err.kind).toBe("InvalidDataValue") expect(err.kind).toBe("InvalidDataValue")
expect(err.message).toContain("circular") expect(err.message).toContain("circular")
}) })
test("fill overwrites a range and returns the mutated array", async () => {
expect(await value(`const a = [1,2,3,4]; return a.fill(0, 1, 3)`)).toEqual([1, 0, 0, 4])
expect(await value(`return [1,2,3].fill("z")`)).toEqual(["z", "z", "z"])
})
test("copyWithin copies a range in place", async () => {
expect(await value(`return [1,2,3,4,5].copyWithin(0, 3)`)).toEqual([4, 5, 3, 4, 5])
})
test("keys/values/entries return arrays usable with for...of and spread", async () => { test("keys/values/entries return arrays usable with for...of and spread", async () => {
expect(await value(`return [...["x","y","z"].keys()]`)).toEqual([0, 1, 2]) expect(await value(`return [...["x","y","z"].keys()]`)).toEqual([0, 1, 2])
expect(await value(`return ["x","y"].values()`)).toEqual(["x", "y"]) expect(await value(`return ["x","y"].values()`)).toEqual(["x", "y"])
@ -359,16 +293,9 @@ describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
}) })
}) })
describe("string methods: localeCompare, normalize, trim aliases", () => { describe("CodeMode-specific string behavior", () => {
test("localeCompare orders strings for sorting", async () => { test("localeCompare orders strings for sorting", async () => {
expect(await value(`return ["b","a","c"].sort((x, y) => x.localeCompare(y))`)).toEqual(["a", "b", "c"]) expect(await value(`return ["b","a","c"].sort((x, y) => x.localeCompare(y))`)).toEqual(["a", "b", "c"])
expect(await value(`return "a".localeCompare("a")`)).toBe(0)
})
test("normalize applies unicode normalization forms", async () => {
expect(await value(`return "\\u0065\\u0301".normalize("NFC").length`)).toBe(1)
expect(await value(`return "\\u00e9".normalize("NFD").length`)).toBe(2)
expect(await value(`return "x".normalize() === "x"`)).toBe(true)
}) })
test("an invalid normalize form is a clear catchable error", async () => { test("an invalid normalize form is a clear catchable error", async () => {
@ -453,11 +380,6 @@ describe("H5: builtin coercion functions work as array callbacks", () => {
expect(await value(`return [1, 2, 3].map(String)`)).toEqual(["1", "2", "3"]) expect(await value(`return [1, 2, 3].map(String)`)).toEqual(["1", "2", "3"])
}) })
test("arrow callbacks still work (no regression)", async () => {
expect(await value(`return [1, 2, 3, 4].filter(x => x % 2 === 0)`)).toEqual([2, 4])
expect(await value(`return [1, 2, 3].reduce((a, b) => a + b, 0)`)).toBe(6)
})
test("a non-callable callback is still rejected", async () => { test("a non-callable callback is still rejected", async () => {
const err = await error(`return [1,2,3].map(42)`) const err = await error(`return [1,2,3].map(42)`)
expect(err.message).toContain("callback") expect(err.message).toContain("callback")

View file

@ -154,9 +154,7 @@ describe("RegExp", () => {
).toEqual(["1", "22"]) ).toEqual(["1", "22"])
}) })
test("string match: non-global carries index, global lists all matches", async () => { test("an unmatched string pattern returns null", async () => {
expect(await value(`const m = "a1b22".match(/\\d+/); return [m[0], m.index]`)).toEqual(["1", 1])
expect(await value(`return "a1b22".match(/\\d+/g)`)).toEqual(["1", "22"])
expect(await value(`return "abc".match(/\\d/)`)).toBeNull() expect(await value(`return "abc".match(/\\d/)`)).toBeNull()
}) })
@ -164,13 +162,6 @@ describe("RegExp", () => {
expect(await value(`return "a1b22".matchAll(/(\\d+)/g).map((m) => m[1])`)).toEqual(["1", "22"]) expect(await value(`return "a1b22".matchAll(/(\\d+)/g).map((m) => m[1])`)).toEqual(["1", "22"])
}) })
test("replace and replaceAll with patterns and $1 substitution", async () => {
expect(await value(`return "a1b2".replace(/\\d/, "#")`)).toBe("a#b2")
expect(await value(`return "a1b2".replace(/\\d/g, "#")`)).toBe("a#b#")
expect(await value(`return "a1b2".replaceAll(/\\d/g, "#")`)).toBe("a#b#")
expect(await value(`return "hi bob".replace(/b(o)b/, "[$1]")`)).toBe("hi [o]")
})
test("function replacers receive captures, offsets, input, and named groups", async () => { test("function replacers receive captures, offsets, input, and named groups", async () => {
expect( expect(
await value(` await value(`
@ -236,12 +227,6 @@ describe("RegExp", () => {
expect(await value(`try { "a".replaceAll(/a/, "b"); return "no" } catch { return "caught" }`)).toBe("caught") expect(await value(`try { "a".replaceAll(/a/, "b"); return "no" } catch { return "caught" }`)).toBe("caught")
}) })
test("split and search accept patterns", async () => {
expect(await value(`return "a1b22c".split(/\\d+/)`)).toEqual(["a", "b", "c"])
expect(await value(`return "ab42".search(/\\d/)`)).toBe(2)
expect(await value(`return "ab".search(/\\d/)`)).toBe(-1)
})
test("new RegExp constructs from strings; invalid patterns are catchable", async () => { test("new RegExp constructs from strings; invalid patterns are catchable", async () => {
expect(await value(`return new RegExp("a+", "i").test("AAA")`)).toBe(true) expect(await value(`return new RegExp("a+", "i").test("AAA")`)).toBe(true)
expect(await value(`try { new RegExp("("); return "no" } catch { return "caught" }`)).toBe("caught") expect(await value(`try { new RegExp("("); return "no" } catch { return "caught" }`)).toBe("caught")
@ -650,9 +635,7 @@ describe("stdlib integration", () => {
true, true,
) )
expect( expect(
await value( await value(`try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`),
`try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`,
),
).toBe(true) ).toBe(true)
}) })

View file

@ -0,0 +1,580 @@
/*
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
* - test/built-ins/String/prototype/toLowerCase/S15.5.4.16_A2_T1.js
* - test/built-ins/String/prototype/toLowerCase/special_casing.js
* - test/built-ins/String/prototype/toLowerCase/special_casing_conditional.js
* - test/built-ins/String/prototype/toLowerCase/Final_Sigma_U180E.js
* - test/built-ins/String/prototype/toLowerCase/supplementary_plane.js
* - test/built-ins/String/prototype/toUpperCase/S15.5.4.18_A2_T1.js
* - test/built-ins/String/prototype/toUpperCase/special_casing.js
* - test/built-ins/String/prototype/toUpperCase/supplementary_plane.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-1.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-2.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-3.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-4.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-5.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-6.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-7.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-8.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-9.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-10.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-11.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-12.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-13.js
* - test/built-ins/String/prototype/trim/15.5.4.20-3-14.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-1.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-2.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-3.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-4.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-5.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-6.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-8.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-10.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-11.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-12.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-13.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-14.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-16.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-18.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-19.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-20.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-21.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-22.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-24.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-27.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-28.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-29.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-30.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-32.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-34.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-35.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-36.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-37.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-38.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-39.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-40.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-41.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-42.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-43.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-44.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-45.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-46.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-47.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-48.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-49.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-50.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-51.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-52.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-53.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-54.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-55.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-56.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-57.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-58.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-59.js
* - test/built-ins/String/prototype/trim/15.5.4.20-4-60.js
* - test/built-ins/String/prototype/trim/u180e.js
* - test/built-ins/String/prototype/trimStart/this-value-whitespace.js
* - test/built-ins/String/prototype/trimStart/this-value-line-terminator.js
* - test/built-ins/String/prototype/trimEnd/this-value-whitespace.js
* - test/built-ins/String/prototype/trimEnd/this-value-line-terminator.js
* - test/built-ins/String/prototype/repeat/repeat-string-n-times.js
* - test/built-ins/String/prototype/repeat/empty-string-returns-empty.js
* - test/built-ins/String/prototype/repeat/count-is-zero-returns-empty-string.js
* - test/built-ins/String/prototype/repeat/count-coerced-to-zero-returns-empty-string.js
* - test/built-ins/String/prototype/padStart/fill-string-empty.js
* - test/built-ins/String/prototype/padStart/normal-operation.js
* - test/built-ins/String/prototype/padStart/fill-string-omitted.js
* - test/built-ins/String/prototype/padStart/max-length-not-greater-than-string.js
* - test/built-ins/String/prototype/padEnd/fill-string-empty.js
* - test/built-ins/String/prototype/padEnd/normal-operation.js
* - test/built-ins/String/prototype/padEnd/fill-string-omitted.js
* - test/built-ins/String/prototype/padEnd/max-length-not-greater-than-string.js
* - test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T4.js
* - test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T7.js
* - test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T8.js
* - test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T1.js
* - test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T2.js
* - test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T3.js
* - test/built-ins/String/prototype/charAt/S9.4_A1.js
* - test/built-ins/String/prototype/charAt/S9.4_A2.js
* - test/built-ins/String/prototype/charAt/pos-rounding.js
* - test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T4.js
* - test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T7.js
* - test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T8.js
* - test/built-ins/String/prototype/charCodeAt/pos-rounding.js
* - test/built-ins/String/prototype/codePointAt/return-single-code-unit.js
* - test/built-ins/String/prototype/codePointAt/return-first-code-unit.js
* - test/built-ins/String/prototype/codePointAt/return-utf16-decode.js
* - test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js
* - test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-less-than-zero.js
* - test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-equal-or-more-than-size.js
* - test/built-ins/String/prototype/at/returns-code-unit.js
* - test/built-ins/String/prototype/at/returns-item.js
* - test/built-ins/String/prototype/at/returns-item-relative-index.js
* - test/built-ins/String/prototype/at/returns-undefined-for-out-of-range-index.js
* - test/built-ins/String/prototype/at/index-non-numeric-argument-tointeger.js
* - test/built-ins/String/prototype/concat/S15.5.4.6_A1_T4.js
* - test/built-ins/String/prototype/toString/string-primitive.js
* - test/built-ins/String/prototype/normalize/return-normalized-string.js
* - test/built-ins/String/prototype/normalize/return-normalized-string-using-default-parameter.js
* - test/built-ins/String/prototype/normalize/form-is-not-valid-throws.js
* - test/built-ins/String/prototype/localeCompare/15.5.4.9_CE.js
* - test/built-ins/String/fromCharCode/S15.5.3.2_A2.js
* - test/built-ins/String/fromCharCode/S15.5.3.2_A3_T1.js
* - test/built-ins/String/fromCharCode/S9.7_A1.js
* - test/built-ins/String/fromCharCode/S9.7_A2.1.js
* - test/built-ins/String/fromCharCode/S9.7_A2.2.js
* - test/built-ins/String/fromCharCode/S9.7_A3.2_T1.js
* - test/built-ins/String/fromCodePoint/arguments-is-empty.js
* - test/built-ins/String/fromCodePoint/return-string-value.js
* - test/built-ins/String/fromCodePoint/argument-is-not-integer.js
* - test/built-ins/String/fromCodePoint/number-is-out-of-range.js
*
* Copyright 2009 the Sputnik authors. All rights reserved.
* Copyright (C) 2009 the Sputnik authors. All rights reserved.
* Copyright (c) 2012 Ecma International. All rights reserved.
* Copyright 2012 Norbert Lindenberg. All rights reserved.
* Copyright 2012 Mozilla Corporation. All rights reserved.
* Copyright 2013 Microsoft Corporation. All rights reserved.
* Copyright (C) 2015 the V8 project authors. All rights reserved.
* Copyright (C) 2015 André Bargull. All rights reserved.
* Copyright (C) 2016 the V8 project authors. All rights reserved.
* Copyright (C) 2016 André Bargull. All rights reserved.
* Copyright (C) 2016 Jordan Harband. All rights reserved.
* Copyright (C) 2016 Mathias Bynens. All rights reserved.
* Copyright (c) 2017 Valerie Young. All rights reserved.
* Copyright (C) 2017 Valerie Young. All rights reserved.
* Copyright (C) 2020 Rick Waldron. All rights reserved.
* Copyright (C) 2022 Richard Gibson. All rights reserved.
* Test262 portions are governed by the BSD license in LICENSE.test262.
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
type Argument = string | number | undefined
type Outcome = "undefined" | "length" | "RangeError"
type Assertion = {
label: string
input?: string
args?: ReadonlyArray<Argument>
expected?: string | number
outcome?: Outcome
}
type Vector = {
path: string
method: string
static?: boolean
assertions: ReadonlyArray<Assertion>
}
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const literal = (input: Argument) => {
if (input === undefined) return "undefined"
if (typeof input === "string") return JSON.stringify(input)
if (Number.isNaN(input)) return "NaN"
if (input === Infinity) return "Infinity"
if (input === -Infinity) return "-Infinity"
if (Object.is(input, -0)) return "-0"
return JSON.stringify(input)
}
const vectors: Array<Vector> = []
const add = (path: string, method: string, assertions: ReadonlyArray<Assertion>, staticMethod = false) => {
vectors.push({ path, method, assertions, static: staticMethod })
}
const assertion = (label: string, input: string, expected: string | number, args: ReadonlyArray<Argument> = []) => ({
label,
input,
args,
expected,
})
add("test/built-ins/String/prototype/toLowerCase/S15.5.4.16_A2_T1.js", "toLowerCase", [
assertion("#1 direct value", "Hello, WoRlD!", "hello, world!"),
assertion("#2 String value", "Hello, WoRlD!", "hello, world!"),
])
add("test/built-ins/String/prototype/toLowerCase/special_casing.js", "toLowerCase", [
assertion(
"103 SpecialCasing mappings",
"\u00DF\u0130\uFB00\uFB01\uFB02\uFB03\uFB04\uFB05\uFB06\u0587\uFB13\uFB14\uFB15\uFB16\uFB17\u0149\u0390\u03B0\u01F0\u1E96\u1E97\u1E98\u1E99\u1E9A\u1F50\u1F52\u1F54\u1F56\u1FB6\u1FC6\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2\u1FE3\u1FE4\u1FE6\u1FE7\u1FF6\u1F80\u1F81\u1F82\u1F83\u1F84\u1F85\u1F86\u1F87\u1F88\u1F89\u1F8A\u1F8B\u1F8C\u1F8D\u1F8E\u1F8F\u1F90\u1F91\u1F92\u1F93\u1F94\u1F95\u1F96\u1F97\u1F98\u1F99\u1F9A\u1F9B\u1F9C\u1F9D\u1F9E\u1F9F\u1FA0\u1FA1\u1FA2\u1FA3\u1FA4\u1FA5\u1FA6\u1FA7\u1FA8\u1FA9\u1FAA\u1FAB\u1FAC\u1FAD\u1FAE\u1FAF\u1FB3\u1FBC\u1FC3\u1FCC\u1FF3\u1FFC\u1FB2\u1FB4\u1FC2\u1FC4\u1FF2\u1FF4\u1FB7\u1FC7\u1FF7",
"\u00DF\u0069\u0307\uFB00\uFB01\uFB02\uFB03\uFB04\uFB05\uFB06\u0587\uFB13\uFB14\uFB15\uFB16\uFB17\u0149\u0390\u03B0\u01F0\u1E96\u1E97\u1E98\u1E99\u1E9A\u1F50\u1F52\u1F54\u1F56\u1FB6\u1FC6\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2\u1FE3\u1FE4\u1FE6\u1FE7\u1FF6\u1F80\u1F81\u1F82\u1F83\u1F84\u1F85\u1F86\u1F87\u1F80\u1F81\u1F82\u1F83\u1F84\u1F85\u1F86\u1F87\u1F90\u1F91\u1F92\u1F93\u1F94\u1F95\u1F96\u1F97\u1F90\u1F91\u1F92\u1F93\u1F94\u1F95\u1F96\u1F97\u1FA0\u1FA1\u1FA2\u1FA3\u1FA4\u1FA5\u1FA6\u1FA7\u1FA0\u1FA1\u1FA2\u1FA3\u1FA4\u1FA5\u1FA6\u1FA7\u1FB3\u1FB3\u1FC3\u1FC3\u1FF3\u1FF3\u1FB2\u1FB4\u1FC2\u1FC4\u1FF2\u1FF4\u1FB7\u1FC7\u1FF7",
),
])
add("test/built-ins/String/prototype/toLowerCase/special_casing_conditional.js", "toLowerCase", [
assertion("single sigma", "\u03A3", "\u03C3"),
assertion("preceded by cased", "A\u03A3", "a\u03C2"),
assertion("preceded by supplementary cased", "\uD835\uDCA2\u03A3", "\uD835\uDCA2\u03C2"),
assertion("preceded by full stop", "A.\u03A3", "a.\u03C2"),
assertion("preceded by soft hyphen", "A\u00AD\u03A3", "a\u00AD\u03C2"),
assertion("preceded by combining mark", "A\uD834\uDE42\u03A3", "a\uD834\uDE42\u03C2"),
assertion("preceded by uncased combining mark", "\u0345\u03A3", "\u0345\u03C3"),
assertion("preceded by cased and combining mark", "\u0391\u0345\u03A3", "\u03B1\u0345\u03C2"),
assertion("followed by cased", "A\u03A3B", "a\u03C3b"),
assertion("followed by supplementary cased", "A\u03A3\uD835\uDCA2", "a\u03C3\uD835\uDCA2"),
assertion("followed by full stop and cased", "A\u03A3.b", "a\u03C3.b"),
assertion("followed by soft hyphen and cased", "A\u03A3\u00ADB", "a\u03C3\u00ADb"),
assertion("followed by combining mark and cased", "A\u03A3\uD834\uDE42B", "a\u03C3\uD834\uDE42b"),
assertion("followed by uncased combining mark", "A\u03A3\u0345", "a\u03C2\u0345"),
assertion("followed by combining mark and cased Greek", "A\u03A3\u0345\u0391", "a\u03C3\u0345\u03B1"),
])
add("test/built-ins/String/prototype/toLowerCase/Final_Sigma_U180E.js", "toLowerCase", [
assertion("preceded by U+180E", "A\u180E\u03A3", "a\u180E\u03C2"),
assertion("preceded by U+180E and followed by cased", "A\u180E\u03A3B", "a\u180E\u03C3b"),
assertion("followed by U+180E", "A\u03A3\u180E", "a\u03C2\u180E"),
assertion("followed by U+180E and cased", "A\u03A3\u180EB", "a\u03C3\u180Eb"),
assertion("surrounded by U+180E", "A\u180E\u03A3\u180E", "a\u180E\u03C2\u180E"),
assertion("surrounded by U+180E and followed by cased", "A\u180E\u03A3\u180EB", "a\u180E\u03C3\u180Eb"),
])
add("test/built-ins/String/prototype/toLowerCase/supplementary_plane.js", "toLowerCase", [
assertion(
"40 Deseret mappings",
"\uD801\uDC00\uD801\uDC01\uD801\uDC02\uD801\uDC03\uD801\uDC04\uD801\uDC05\uD801\uDC06\uD801\uDC07\uD801\uDC08\uD801\uDC09\uD801\uDC0A\uD801\uDC0B\uD801\uDC0C\uD801\uDC0D\uD801\uDC0E\uD801\uDC0F\uD801\uDC10\uD801\uDC11\uD801\uDC12\uD801\uDC13\uD801\uDC14\uD801\uDC15\uD801\uDC16\uD801\uDC17\uD801\uDC18\uD801\uDC19\uD801\uDC1A\uD801\uDC1B\uD801\uDC1C\uD801\uDC1D\uD801\uDC1E\uD801\uDC1F\uD801\uDC20\uD801\uDC21\uD801\uDC22\uD801\uDC23\uD801\uDC24\uD801\uDC25\uD801\uDC26\uD801\uDC27",
"\uD801\uDC28\uD801\uDC29\uD801\uDC2A\uD801\uDC2B\uD801\uDC2C\uD801\uDC2D\uD801\uDC2E\uD801\uDC2F\uD801\uDC30\uD801\uDC31\uD801\uDC32\uD801\uDC33\uD801\uDC34\uD801\uDC35\uD801\uDC36\uD801\uDC37\uD801\uDC38\uD801\uDC39\uD801\uDC3A\uD801\uDC3B\uD801\uDC3C\uD801\uDC3D\uD801\uDC3E\uD801\uDC3F\uD801\uDC40\uD801\uDC41\uD801\uDC42\uD801\uDC43\uD801\uDC44\uD801\uDC45\uD801\uDC46\uD801\uDC47\uD801\uDC48\uD801\uDC49\uD801\uDC4A\uD801\uDC4B\uD801\uDC4C\uD801\uDC4D\uD801\uDC4E\uD801\uDC4F",
),
])
add("test/built-ins/String/prototype/toUpperCase/S15.5.4.18_A2_T1.js", "toUpperCase", [
assertion("#1 direct value", "Hello, WoRlD!", "HELLO, WORLD!"),
assertion("#2 String value", "Hello, WoRlD!", "HELLO, WORLD!"),
])
add("test/built-ins/String/prototype/toUpperCase/special_casing.js", "toUpperCase", [
assertion(
"103 SpecialCasing mappings",
"\u00DF\u0130\uFB00\uFB01\uFB02\uFB03\uFB04\uFB05\uFB06\u0587\uFB13\uFB14\uFB15\uFB16\uFB17\u0149\u0390\u03B0\u01F0\u1E96\u1E97\u1E98\u1E99\u1E9A\u1F50\u1F52\u1F54\u1F56\u1FB6\u1FC6\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2\u1FE3\u1FE4\u1FE6\u1FE7\u1FF6\u1F80\u1F81\u1F82\u1F83\u1F84\u1F85\u1F86\u1F87\u1F88\u1F89\u1F8A\u1F8B\u1F8C\u1F8D\u1F8E\u1F8F\u1F90\u1F91\u1F92\u1F93\u1F94\u1F95\u1F96\u1F97\u1F98\u1F99\u1F9A\u1F9B\u1F9C\u1F9D\u1F9E\u1F9F\u1FA0\u1FA1\u1FA2\u1FA3\u1FA4\u1FA5\u1FA6\u1FA7\u1FA8\u1FA9\u1FAA\u1FAB\u1FAC\u1FAD\u1FAE\u1FAF\u1FB3\u1FBC\u1FC3\u1FCC\u1FF3\u1FFC\u1FB2\u1FB4\u1FC2\u1FC4\u1FF2\u1FF4\u1FB7\u1FC7\u1FF7",
"\u0053\u0053\u0130\u0046\u0046\u0046\u0049\u0046\u004C\u0046\u0046\u0049\u0046\u0046\u004C\u0053\u0054\u0053\u0054\u0535\u0552\u0544\u0546\u0544\u0535\u0544\u053B\u054E\u0546\u0544\u053D\u02BC\u004E\u0399\u0308\u0301\u03A5\u0308\u0301\u004A\u030C\u0048\u0331\u0054\u0308\u0057\u030A\u0059\u030A\u0041\u02BE\u03A5\u0313\u03A5\u0313\u0300\u03A5\u0313\u0301\u03A5\u0313\u0342\u0391\u0342\u0397\u0342\u0399\u0308\u0300\u0399\u0308\u0301\u0399\u0342\u0399\u0308\u0342\u03A5\u0308\u0300\u03A5\u0308\u0301\u03A1\u0313\u03A5\u0342\u03A5\u0308\u0342\u03A9\u0342\u1F08\u0399\u1F09\u0399\u1F0A\u0399\u1F0B\u0399\u1F0C\u0399\u1F0D\u0399\u1F0E\u0399\u1F0F\u0399\u1F08\u0399\u1F09\u0399\u1F0A\u0399\u1F0B\u0399\u1F0C\u0399\u1F0D\u0399\u1F0E\u0399\u1F0F\u0399\u1F28\u0399\u1F29\u0399\u1F2A\u0399\u1F2B\u0399\u1F2C\u0399\u1F2D\u0399\u1F2E\u0399\u1F2F\u0399\u1F28\u0399\u1F29\u0399\u1F2A\u0399\u1F2B\u0399\u1F2C\u0399\u1F2D\u0399\u1F2E\u0399\u1F2F\u0399\u1F68\u0399\u1F69\u0399\u1F6A\u0399\u1F6B\u0399\u1F6C\u0399\u1F6D\u0399\u1F6E\u0399\u1F6F\u0399\u1F68\u0399\u1F69\u0399\u1F6A\u0399\u1F6B\u0399\u1F6C\u0399\u1F6D\u0399\u1F6E\u0399\u1F6F\u0399\u0391\u0399\u0391\u0399\u0397\u0399\u0397\u0399\u03A9\u0399\u03A9\u0399\u1FBA\u0399\u0386\u0399\u1FCA\u0399\u0389\u0399\u1FFA\u0399\u038F\u0399\u0391\u0342\u0399\u0397\u0342\u0399\u03A9\u0342\u0399",
),
])
add("test/built-ins/String/prototype/toUpperCase/supplementary_plane.js", "toUpperCase", [
assertion(
"40 Deseret mappings",
"\uD801\uDC28\uD801\uDC29\uD801\uDC2A\uD801\uDC2B\uD801\uDC2C\uD801\uDC2D\uD801\uDC2E\uD801\uDC2F\uD801\uDC30\uD801\uDC31\uD801\uDC32\uD801\uDC33\uD801\uDC34\uD801\uDC35\uD801\uDC36\uD801\uDC37\uD801\uDC38\uD801\uDC39\uD801\uDC3A\uD801\uDC3B\uD801\uDC3C\uD801\uDC3D\uD801\uDC3E\uD801\uDC3F\uD801\uDC40\uD801\uDC41\uD801\uDC42\uD801\uDC43\uD801\uDC44\uD801\uDC45\uD801\uDC46\uD801\uDC47\uD801\uDC48\uD801\uDC49\uD801\uDC4A\uD801\uDC4B\uD801\uDC4C\uD801\uDC4D\uD801\uDC4E\uD801\uDC4F",
"\uD801\uDC00\uD801\uDC01\uD801\uDC02\uD801\uDC03\uD801\uDC04\uD801\uDC05\uD801\uDC06\uD801\uDC07\uD801\uDC08\uD801\uDC09\uD801\uDC0A\uD801\uDC0B\uD801\uDC0C\uD801\uDC0D\uD801\uDC0E\uD801\uDC0F\uD801\uDC10\uD801\uDC11\uD801\uDC12\uD801\uDC13\uD801\uDC14\uD801\uDC15\uD801\uDC16\uD801\uDC17\uD801\uDC18\uD801\uDC19\uD801\uDC1A\uD801\uDC1B\uD801\uDC1C\uD801\uDC1D\uD801\uDC1E\uD801\uDC1F\uD801\uDC20\uD801\uDC21\uD801\uDC22\uD801\uDC23\uD801\uDC24\uD801\uDC25\uD801\uDC26\uD801\uDC27",
),
])
const whitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
const lineTerminators = "\u000A\u000D\u2028\u2029"
const trim = (file: string, input: string, expected: string) =>
add(`test/built-ins/String/prototype/trim/${file}`, "trim", [assertion("upstream assertion", input, expected)])
trim("15.5.4.20-3-1.js", lineTerminators, "")
trim("15.5.4.20-3-2.js", whitespace, "")
trim("15.5.4.20-3-3.js", whitespace + lineTerminators, "")
trim("15.5.4.20-3-4.js", whitespace + lineTerminators + "abc", "abc")
trim("15.5.4.20-3-5.js", "abc" + whitespace + lineTerminators, "abc")
trim("15.5.4.20-3-6.js", whitespace + lineTerminators + "abc" + whitespace + lineTerminators, "abc")
trim("15.5.4.20-3-7.js", "ab" + whitespace + lineTerminators + "cd", "ab" + whitespace + lineTerminators + "cd")
trim("15.5.4.20-3-8.js", "\0\u0000", "\0\u0000")
trim("15.5.4.20-3-9.js", "\0", "\0")
trim("15.5.4.20-3-10.js", "\u0000", "\u0000")
trim("15.5.4.20-3-11.js", "\0\u0000abc", "\0\u0000abc")
trim("15.5.4.20-3-12.js", "abc\0\u0000", "abc\0\u0000")
trim("15.5.4.20-3-13.js", "\0\u0000abc\0\u0000", "\0\u0000abc\0\u0000")
trim("15.5.4.20-3-14.js", "a\0\u0000bc", "a\0\u0000bc")
trim("15.5.4.20-4-1.js", "\u0009a bc \u0009", "a bc")
trim("15.5.4.20-4-2.js", " \u0009abc \u0009", "abc")
trim("15.5.4.20-4-3.js", "\u0009abc", "abc")
trim("15.5.4.20-4-4.js", "\u000Babc", "abc")
trim("15.5.4.20-4-5.js", "\u000Cabc", "abc")
trim("15.5.4.20-4-6.js", "\u0020abc", "abc")
trim("15.5.4.20-4-8.js", "\u00A0abc", "abc")
trim("15.5.4.20-4-10.js", "\uFEFFabc", "abc")
trim("15.5.4.20-4-11.js", "abc\u0009", "abc")
trim("15.5.4.20-4-12.js", "abc\u000B", "abc")
trim("15.5.4.20-4-13.js", "abc\u000C", "abc")
trim("15.5.4.20-4-14.js", "abc\u0020", "abc")
trim("15.5.4.20-4-16.js", "abc\u00A0", "abc")
trim("15.5.4.20-4-18.js", "abc\uFEFF", "abc")
trim("15.5.4.20-4-19.js", "\u0009abc\u0009", "abc")
trim("15.5.4.20-4-20.js", "\u000Babc\u000B", "abc")
trim("15.5.4.20-4-21.js", "\u000Cabc\u000C", "abc")
trim("15.5.4.20-4-22.js", "\u0020abc\u0020", "abc")
trim("15.5.4.20-4-24.js", "\u00A0abc\u00A0", "abc")
trim("15.5.4.20-4-27.js", "\u0009\u0009", "")
trim("15.5.4.20-4-28.js", "\u000B\u000B", "")
trim("15.5.4.20-4-29.js", "\u000C\u000C", "")
trim("15.5.4.20-4-30.js", "\u0020\u0020", "")
trim("15.5.4.20-4-32.js", "\u00A0\u00A0", "")
trim("15.5.4.20-4-34.js", "\uFEFF\uFEFF", "")
trim("15.5.4.20-4-35.js", "ab\u0009c", "ab\u0009c")
trim("15.5.4.20-4-36.js", "ab\u000Bc", "ab\u000Bc")
trim("15.5.4.20-4-37.js", "ab\u000Cc", "ab\u000Cc")
trim("15.5.4.20-4-38.js", "ab\u0020c", "ab\u0020c")
trim("15.5.4.20-4-39.js", "ab\u0085c", "ab\u0085c")
trim("15.5.4.20-4-40.js", "ab\u00A0c", "ab\u00A0c")
trim("15.5.4.20-4-41.js", "ab\u200Bc", "ab\u200Bc")
trim("15.5.4.20-4-42.js", "ab\uFEFFc", "ab\uFEFFc")
trim("15.5.4.20-4-43.js", "\u000Aabc", "abc")
trim("15.5.4.20-4-44.js", "\u000Dabc", "abc")
trim("15.5.4.20-4-45.js", "\u2028abc", "abc")
trim("15.5.4.20-4-46.js", "\u2029abc", "abc")
trim("15.5.4.20-4-47.js", "abc\u000A", "abc")
trim("15.5.4.20-4-48.js", "abc\u000D", "abc")
trim("15.5.4.20-4-49.js", "abc\u2028", "abc")
trim("15.5.4.20-4-50.js", "abc\u2029", "abc")
trim("15.5.4.20-4-51.js", "\u000Aabc\u000A", "abc")
trim("15.5.4.20-4-52.js", "\u000Dabc\u000D", "abc")
trim("15.5.4.20-4-53.js", "\u2028abc\u2028", "abc")
trim("15.5.4.20-4-54.js", "\u2029abc\u2029", "abc")
trim("15.5.4.20-4-55.js", "\u000A\u000A", "")
trim("15.5.4.20-4-56.js", "\u000D\u000D", "")
trim("15.5.4.20-4-57.js", "\u2028\u2028", "")
trim("15.5.4.20-4-58.js", "\u2029\u2029", "")
trim("15.5.4.20-4-59.js", "\u2029 abc", "abc")
trim("15.5.4.20-4-60.js", " ", "")
add("test/built-ins/String/prototype/trim/u180e.js", "trim", [
assertion("trailing U+180E", "_\u180E", "_\u180E"),
assertion("only U+180E", "\u180E", "\u180E"),
assertion("leading U+180E", "\u180E_", "\u180E_"),
])
const directionalWhitespace = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF"
add("test/built-ins/String/prototype/trimStart/this-value-whitespace.js", "trimStart", [
assertion("all whitespace", directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace, "a" + directionalWhitespace + "b" + directionalWhitespace),
])
add("test/built-ins/String/prototype/trimStart/this-value-line-terminator.js", "trimStart", [
assertion("all line terminators", lineTerminators + "a" + lineTerminators + "b" + lineTerminators, "a" + lineTerminators + "b" + lineTerminators),
])
add("test/built-ins/String/prototype/trimEnd/this-value-whitespace.js", "trimEnd", [
assertion("all whitespace", directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace, directionalWhitespace + "a" + directionalWhitespace + "b"),
])
add("test/built-ins/String/prototype/trimEnd/this-value-line-terminator.js", "trimEnd", [
assertion("all line terminators", lineTerminators + "a" + lineTerminators + "b" + lineTerminators, lineTerminators + "a" + lineTerminators + "b"),
])
add("test/built-ins/String/prototype/repeat/repeat-string-n-times.js", "repeat", [
assertion("repeat once", "abc", "abc", [1]),
assertion("repeat three times", "abc", "abcabcabc", [3]),
{ label: "repeat 10000 times length", input: ".", args: [10000], expected: 10000, outcome: "length" },
])
add("test/built-ins/String/prototype/repeat/empty-string-returns-empty.js", "repeat", [
assertion("count 1", "", "", [1]),
assertion("count 3", "", "", [3]),
assertion("maximum 32-bit count", "", "", [0xffffffff]),
])
add("test/built-ins/String/prototype/repeat/count-is-zero-returns-empty-string.js", "repeat", [
assertion("zero", "foo", "", [0]),
])
add("test/built-ins/String/prototype/repeat/count-coerced-to-zero-returns-empty-string.js", "repeat", [
assertion("fraction truncates to zero", "abc", "", [0.9]),
])
add("test/built-ins/String/prototype/padStart/fill-string-empty.js", "padStart", [assertion("empty fill", "abc", "abc", [5, ""])])
add("test/built-ins/String/prototype/padStart/normal-operation.js", "padStart", [
assertion("truncated multi-character fill", "abc", "defdabc", [7, "def"]),
assertion("single-character fill", "abc", "**abc", [5, "*"]),
assertion("truncated surrogate pair", "abc", "\uD83D\uDCA9\uD83Dabc", [6, "\uD83D\uDCA9"]),
])
add("test/built-ins/String/prototype/padStart/fill-string-omitted.js", "padStart", [
assertion("omitted fill", "abc", " abc", [5]),
assertion("undefined fill", "abc", " abc", [5, undefined]),
])
add("test/built-ins/String/prototype/padStart/max-length-not-greater-than-string.js", "padStart", [
assertion("NaN", "abc", "abc", [NaN, "def"]),
assertion("negative infinity", "abc", "abc", [-Infinity, "def"]),
assertion("zero", "abc", "abc", [0, "def"]),
assertion("negative one", "abc", "abc", [-1, "def"]),
assertion("equal length", "abc", "abc", [3, "def"]),
assertion("fraction truncates", "abc", "abc", [3.9999, "def"]),
])
add("test/built-ins/String/prototype/padEnd/fill-string-empty.js", "padEnd", [assertion("empty fill", "abc", "abc", [5, ""])])
add("test/built-ins/String/prototype/padEnd/normal-operation.js", "padEnd", [
assertion("truncated multi-character fill", "abc", "abcdefd", [7, "def"]),
assertion("single-character fill", "abc", "abc**", [5, "*"]),
assertion("truncated surrogate pair", "abc", "abc\uD83D\uDCA9\uD83D", [6, "\uD83D\uDCA9"]),
])
add("test/built-ins/String/prototype/padEnd/fill-string-omitted.js", "padEnd", [
assertion("omitted fill", "abc", "abc ", [5]),
assertion("undefined fill", "abc", "abc ", [5, undefined]),
])
add("test/built-ins/String/prototype/padEnd/max-length-not-greater-than-string.js", "padEnd", [
assertion("NaN", "abc", "abc", [NaN, "def"]),
assertion("negative infinity", "abc", "abc", [-Infinity, "def"]),
assertion("zero", "abc", "abc", [0, "def"]),
assertion("negative one", "abc", "abc", [-1, "def"]),
assertion("equal length", "abc", "abc", [3, "def"]),
assertion("fraction truncates", "abc", "abc", [3.9999, "def"]),
])
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T4.js", "charAt", [assertion("omitted position", "lego", "l")])
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T7.js", "charAt", [assertion("undefined position", "lego", "l", [undefined])])
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T8.js", "charAt", [assertion("undefined position", "42", "4", [undefined])])
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T1.js", "charAt", ["A", "B", "C", "A", "B", "C"].map((expected, position) => assertion(`position ${position}`, "ABCABC", expected, [position])))
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T2.js", "charAt", [-2, -1].map((position) => assertion(`position ${position}`, "ABCABC", "", [position])))
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T3.js", "charAt", [6, 7].map((position) => assertion(`position ${position}`, "ABCABC", "", [position])))
add("test/built-ins/String/prototype/charAt/S9.4_A1.js", "charAt", [assertion("NaN position", "abc", "a", [NaN])])
add("test/built-ins/String/prototype/charAt/S9.4_A2.js", "charAt", [
assertion("positive zero", "abc", "a", [0]),
assertion("negative zero", "abc", "a", [-0]),
])
add("test/built-ins/String/prototype/charAt/pos-rounding.js", "charAt", [
assertion("-0.99999", "abc", "a", [-0.99999]),
assertion("-0.00001", "abc", "a", [-0.00001]),
assertion("0.00001", "abc", "a", [0.00001]),
assertion("0.99999", "abc", "a", [0.99999]),
assertion("1.00001", "abc", "b", [1.00001]),
assertion("1.99999", "abc", "b", [1.99999]),
])
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T4.js", "charCodeAt", [assertion("omitted position", "smart", 0x73)])
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T7.js", "charCodeAt", [assertion("undefined position", "lego", 0x6c, [undefined])])
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T8.js", "charCodeAt", [assertion("undefined position", "42", 0x34, [undefined])])
add("test/built-ins/String/prototype/charCodeAt/pos-rounding.js", "charCodeAt", [
assertion("-0.99999", "abc", 0x61, [-0.99999]),
assertion("-0.00001", "abc", 0x61, [-0.00001]),
assertion("0.00001", "abc", 0x61, [0.00001]),
assertion("0.99999", "abc", 0x61, [0.99999]),
assertion("1.00001", "abc", 0x62, [1.00001]),
assertion("1.99999", "abc", 0x62, [1.99999]),
])
add("test/built-ins/String/prototype/codePointAt/return-single-code-unit.js", "codePointAt", [
assertion("a", "abc", 97, [0]), assertion("b", "abc", 98, [1]), assertion("c", "abc", 99, [2]),
assertion("ordinary BMP", "\uAAAA\uBBBB", 0xaaaa, [0]), assertion("before high-surrogate range", "\uD7FF\uAAAA", 0xd7ff, [0]),
assertion("low surrogate", "\uDC00\uAAAA", 0xdc00, [0]), assertion("trailing D800", "123\uD800", 0xd800, [3]),
assertion("trailing DAAA", "123\uDAAA", 0xdaaa, [3]), assertion("trailing DBFF", "123\uDBFF", 0xdbff, [3]),
])
add("test/built-ins/String/prototype/codePointAt/return-first-code-unit.js", "codePointAt", [
assertion("D800 before DBFF", "\uD800\uDBFF", 0xd800, [0]), assertion("D800 before E000", "\uD800\uE000", 0xd800, [0]),
assertion("DAAA before DBFF", "\uDAAA\uDBFF", 0xdaaa, [0]), assertion("DAAA before E000", "\uDAAA\uE000", 0xdaaa, [0]),
assertion("DBFF before DBFF", "\uDBFF\uDBFF", 0xdbff, [0]), assertion("DBFF before E000", "\uDBFF\uE000", 0xdbff, [0]),
assertion("D800 before NUL", "\uD800\u0000", 0xd800, [0]), assertion("D800 before FFFF", "\uD800\uFFFF", 0xd800, [0]),
assertion("DAAA before NUL", "\uDAAA\u0000", 0xdaaa, [0]), assertion("DAAA before FFFF", "\uDAAA\uFFFF", 0xdaaa, [0]),
assertion("DBFF before FFFF", "\uDBFF\uFFFF", 0xdbff, [0]),
])
add("test/built-ins/String/prototype/codePointAt/return-utf16-decode.js", "codePointAt", [
assertion("U+10000", "\uD800\uDC00", 65536, [0]), assertion("U+101D0", "\uD800\uDDD0", 66000, [0]),
assertion("U+103FF", "\uD800\uDFFF", 66559, [0]), assertion("U+BA800", "\uDAAA\uDC00", 763904, [0]),
assertion("U+BA9D0", "\uDAAA\uDDD0", 764368, [0]), assertion("U+BABFF", "\uDAAA\uDFFF", 764927, [0]),
assertion("U+10FC00", "\uDBFF\uDC00", 1113088, [0]), assertion("U+10FDD0", "\uDBFF\uDDD0", 1113552, [0]),
assertion("U+10FFFF", "\uDBFF\uDFFF", 1114111, [0]),
])
add("test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js", "codePointAt", [
assertion("NaN", "\uD800\uDC00", 65536, [NaN]), assertion("undefined", "\uD800\uDC00", 65536, [undefined]),
])
add("test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-less-than-zero.js", "codePointAt", [
{ label: "negative one", input: "abc", args: [-1], outcome: "undefined" },
{ label: "negative infinity", input: "abc", args: [-Infinity], outcome: "undefined" },
])
add("test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-equal-or-more-than-size.js", "codePointAt", [
{ label: "equal to size", input: "abc", args: [3], outcome: "undefined" },
{ label: "greater than size", input: "abc", args: [4], outcome: "undefined" },
{ label: "positive infinity", input: "abc", args: [Infinity], outcome: "undefined" },
])
add("test/built-ins/String/prototype/at/returns-code-unit.js", "at", [
assertion("position 0", "12\uD80034", "1", [0]), assertion("position 1", "12\uD80034", "2", [1]),
assertion("unpaired surrogate", "12\uD80034", "\uD800", [2]), assertion("position 3", "12\uD80034", "3", [3]),
assertion("position 4", "12\uD80034", "4", [4]),
])
add("test/built-ins/String/prototype/at/returns-item.js", "at", ["1", "2", "3", "4", "5"].map((expected, position) => assertion(`position ${position}`, "12345", expected, [position])))
add("test/built-ins/String/prototype/at/returns-item-relative-index.js", "at", [
assertion("zero", "12345", "1", [0]), assertion("negative one", "12345", "5", [-1]),
assertion("negative three", "12345", "3", [-3]), assertion("negative four", "12345", "2", [-4]),
])
add("test/built-ins/String/prototype/at/returns-undefined-for-out-of-range-index.js", "at", [-2, 0, 1].map((position) => ({ label: `position ${position}`, input: "", args: [position], outcome: "undefined" })))
add("test/built-ins/String/prototype/at/index-non-numeric-argument-tointeger.js", "at", [assertion("undefined", "01", "0", [undefined])])
add("test/built-ins/String/prototype/concat/S15.5.4.6_A1_T4.js", "concat", [assertion("no arguments", "lego", "lego")])
add("test/built-ins/String/prototype/toString/string-primitive.js", "toString", [
assertion("empty string", "", ""), assertion("non-empty string", "str", "str"),
])
add("test/built-ins/String/prototype/normalize/return-normalized-string.js", "normalize", [
assertion("NFC short", "\u1E9B\u0323", "\u1E9B\u0323", ["NFC"]),
assertion("NFD short", "\u1E9B\u0323", "\u017F\u0323\u0307", ["NFD"]),
assertion("NFKC short", "\u1E9B\u0323", "\u1E69", ["NFKC"]),
assertion("NFKD short", "\u1E9B\u0323", "\u0073\u0323\u0307", ["NFKD"]),
assertion("NFC long", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFC"]),
assertion("NFD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFD"]),
assertion("NFKC long", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFKC"]),
assertion("NFKD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFKD"]),
])
add("test/built-ins/String/prototype/normalize/return-normalized-string-using-default-parameter.js", "normalize", [
assertion("omitted", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301"),
assertion("undefined", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [undefined]),
])
add("test/built-ins/String/prototype/normalize/form-is-not-valid-throws.js", "normalize", [
{ label: "bar", input: "foo", args: ["bar"], outcome: "RangeError" },
{ label: "NFC1", input: "foo", args: ["NFC1"], outcome: "RangeError" },
])
add("test/built-ins/String/prototype/localeCompare/15.5.4.9_CE.js", "localeCompare", [
assertion("D70", "o\u0308", 0, ["ö"]), assertion("reordered diaeresis", "ä\u0323", 0, ["a\u0323\u0308"]),
assertion("reordered marks", "a\u0308\u0323", 0, ["a\u0323\u0308"]), assertion("precomposed dot below", "ạ\u0308", 0, ["a\u0323\u0308"]),
assertion("breve after diaeresis", "ä\u0306", 0, ["a\u0308\u0306"]), assertion("diaeresis after breve", "ă\u0308", 0, ["a\u0306\u0308"]),
assertion("Hangul", "\u1111\u1171\u11B6", 0, ["퓛"]), assertion("angstrom compatibility", "Å", 0, ["Å"]),
assertion("angstrom decomposed", "Å", 0, ["A\u030A"]), assertion("reordered horn and dot", "x\u031B\u0323", 0, ["x\u0323\u031B"]),
assertion("Vietnamese precomposed 1", "ự", 0, ["ụ\u031B"]), assertion("Vietnamese decomposed", "ự", 0, ["u\u031B\u0323"]),
assertion("Vietnamese precomposed 2", "ự", 0, ["ư\u0323"]), assertion("Vietnamese reordered", "ự", 0, ["u\u0323\u031B"]),
assertion("cedilla", "Ç", 0, ["C\u0327"]), assertion("q reordered", "q\u0307\u0323", 0, ["q\u0323\u0307"]),
assertion("Hangul syllable", "가", 0, ["\u1100\u1161"]), assertion("ohm", "Ω", 0, ["Ω"]),
assertion("angstrom", "Å", 0, ["A\u030A"]), assertion("circumflex", "ô", 0, ["o\u0302"]),
assertion("s with marks", "ṩ", 0, ["s\u0323\u0307"]), assertion("d composed plus dot", "ḋ\u0323", 0, ["d\u0323\u0307"]),
assertion("d two precompositions", "ḋ\u0323", 0, ["ḍ\u0307"]),
])
add("test/built-ins/String/fromCharCode/S15.5.3.2_A2.js", "fromCharCode", [{ label: "no arguments", expected: "" }], true)
add("test/built-ins/String/fromCharCode/S15.5.3.2_A3_T1.js", "fromCharCode", [{ label: "ABBA", args: [65, 66, 66, 65], expected: "ABBA" }], true)
add("test/built-ins/String/fromCharCode/S9.7_A1.js", "fromCharCode", [
{ label: "NaN", args: [NaN], expected: 0 }, { label: "zero", args: [0], expected: 0 }, { label: "negative zero", args: [-0], expected: 0 },
{ label: "positive infinity", args: [Infinity], expected: 0 }, { label: "negative infinity", args: [-Infinity], expected: 0 },
], true)
add("test/built-ins/String/fromCharCode/S9.7_A2.1.js", "fromCharCode", [
[0, 0], [1, 1], [-1, 65535], [65535, 65535], [65534, 65534], [65536, 0], [4294967295, 65535], [4294967294, 65534], [4294967296, 0],
].map(([input, expected]) => ({ label: String(input), args: [input!], expected })), true)
add("test/built-ins/String/fromCharCode/S9.7_A2.2.js", "fromCharCode", [
[-32767, 32769], [-32768, 32768], [-32769, 32767], [-65535, 1], [-65536, 0], [-65537, 65535], [65535, 65535], [65536, 0], [65537, 1], [131071, 65535], [131072, 0], [131073, 1],
].map(([input, expected]) => ({ label: String(input), args: [input!], expected })), true)
add("test/built-ins/String/fromCharCode/S9.7_A3.2_T1.js", "fromCharCode", [
{ label: "positive fraction", args: [1.2345], expected: 1 }, { label: "negative fraction", args: [-5.4321], expected: 65531 },
], true)
add("test/built-ins/String/fromCodePoint/arguments-is-empty.js", "fromCodePoint", [{ label: "no arguments", expected: "" }], true)
add("test/built-ins/String/fromCodePoint/return-string-value.js", "fromCodePoint", [
{ label: "NUL", args: [0], expected: "\x00" }, { label: "asterisk", args: [42], expected: "*" },
{ label: "AZ", args: [65, 90], expected: "AZ" }, { label: "Cyrillic", args: [0x404], expected: "\u0404" },
{ label: "hex supplementary", args: [0x2f804], expected: "\uD87E\uDC04" }, { label: "decimal supplementary", args: [194564], expected: "\uD87E\uDC04" },
{ label: "mixed supplementary", args: [0x1d306, 0x61, 0x1d307], expected: "\uD834\uDF06a\uD834\uDF07" },
{ label: "maximum code point", args: [1114111], expected: "\uDBFF\uDFFF" },
], true)
add("test/built-ins/String/fromCodePoint/argument-is-not-integer.js", "fromCodePoint", [
{ label: "fraction", args: [3.14], outcome: "RangeError" }, { label: "fraction after valid", args: [42, 3.14], outcome: "RangeError" },
], true)
add("test/built-ins/String/fromCodePoint/number-is-out-of-range.js", "fromCodePoint", [
{ label: "negative one", args: [-1], outcome: "RangeError" }, { label: "negative after valid", args: [1, -1], outcome: "RangeError" },
{ label: "above maximum", args: [1114112], outcome: "RangeError" }, { label: "infinity", args: [Infinity], outcome: "RangeError" },
], true)
describe("Test262-adapted core String behavior", () => {
for (const vector of vectors) {
test(vector.path, async () => {
const results = vector.assertions.map((item) => {
const args = (item.args ?? []).map(literal).join(", ")
const expression = vector.static
? `String.${vector.method}(${args})`
: `${JSON.stringify(item.input)}.${vector.method}(${args})`
const observed = vector.static && vector.method === "fromCharCode" && typeof item.expected === "number"
? `${expression}.charCodeAt(0)`
: expression
const checked = item.outcome === "undefined"
? `${observed} === undefined`
: item.outcome === "length"
? `${observed}.length`
: item.outcome === "RangeError"
? `(() => { try { ${observed}; return false } catch (error) { return error instanceof RangeError } })()`
: observed
return `{ label: ${JSON.stringify(item.label)}, value: ${checked} }`
})
const expected = vector.assertions.map((item) => ({
label: item.label,
value: item.outcome === undefined || item.outcome === "length" ? item.expected! : true,
}))
expect(await value(`return [${results.join(",")}]`)).toEqual(expected)
})
}
})

View file

@ -0,0 +1,625 @@
/*
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
* - test/built-ins/String/prototype/split/separator-regexp.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-s-and-3-and-instance-is-string-a-b-c-de-f.js
* - test/built-ins/String/prototype/split/argument-is-regexp-s-and-instance-is-string-a-b-c-de-f.js
* - test/built-ins/String/prototype/split/argument-is-regexp-d-and-instance-is-string-dfe23iu-34-65.js
* - test/built-ins/String/prototype/split/argument-is-regexp-reg-exp-d-and-instance-is-string-dfe23iu-34-65.js
* - test/built-ins/String/prototype/split/argument-is-regexp-a-z-and-instance-is-string-abc.js
* - test/built-ins/String/prototype/split/argument-is-reg-exp-a-z-and-instance-is-string-abc.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-l-and-undefined-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-l-and-0-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-l-and-1-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-l-and-2-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-l-and-3-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-regexp-l-and-4-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/argument-is-regexp-l-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/argument-is-new-reg-exp-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-0-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-1-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-2-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-3-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-4-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-undefined-and-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-2-instance-is-string-one-two-three-four-five.js
* - test/built-ins/String/prototype/split/separator-regexp-comma-instance-is-string-one-1-two-2-four-4.js
* - test/built-ins/String/prototype/split/argument-is-regexp-x-and-instance-is-string-a-b-c-de-f.js
* - test/built-ins/String/prototype/replace/regexp-capture-by-index.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A1_T17.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T1.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T2.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T3.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T4.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T5.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T6.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T7.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T8.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T9.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A2_T10.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A3_T1.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A3_T2.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A3_T3.js
* - test/built-ins/String/prototype/replace/S15.5.4.11_A5_T1.js
* - test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js
* - test/built-ins/String/prototype/replaceAll/searchValue-empty-string.js
* - test/built-ins/String/prototype/replaceAll/searchValue-empty-string-this-empty-string.js
* - test/built-ins/String/prototype/replaceAll/replaceValue-value-replaces-string.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0024.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0026.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0060.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0027.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024N.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024NN.js
* - test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x003C.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A1_T14.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T2.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T3.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T4.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T5.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T6.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T7.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T8.js
* - test/built-ins/String/prototype/match/S15.5.4.10_A2_T12.js
* - test/built-ins/String/prototype/matchAll/regexp-prototype-matchAll-v-u-flag.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A1_T14.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T1.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T2.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T3.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T4.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T5.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T6.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A2_T7.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A3_T1.js
* - test/built-ins/String/prototype/search/S15.5.4.12_A3_T2.js
*
* Copyright 2009 the Sputnik authors. All rights reserved.
* Copyright (C) 2019 Leo Balter. All rights reserved.
* Copyright (C) 2020 Rick Waldron. All rights reserved.
* Copyright (C) 2023 Richard Gibson. All rights reserved.
* Copyright (C) 2024 Tan Meng. All rights reserved.
* Test262 portions are governed by the BSD license in LICENSE.test262.
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
type Vector = {
readonly path: string
readonly code: string
readonly expected: CodeMode.DataValue
}
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const run = (name: string, vectors: ReadonlyArray<Vector>) => {
describe(name, () => {
for (const vector of vectors) {
test(vector.path, async () => {
expect(await value(vector.code)).toEqual(vector.expected)
})
}
})
}
run("Test262-adapted regexp split behavior", [
{
path: "test/built-ins/String/prototype/split/separator-regexp.js",
code: `
return [
"x".split(/^/), "x".split(/$/), "x".split(/.?/), "x".split(/.*/), "x".split(/.+/),
"x".split(/.*?/), "x".split(/.{1}/), "x".split(/.{1,}/), "x".split(/.{1,2}/),
"x".split(/()/), "x".split(/./), "x".split(/(?:)/), "x".split(/(...)/),
"x".split(/(|)/), "x".split(/[]/), "x".split(/[^]/), "x".split(/[.-.]/),
"x".split(/\\0/), "x".split(/\\b/), "x".split(/\\B/), "x".split(/\\d/),
"x".split(/\\D/), "x".split(/\\n/), "x".split(/\\r/), "x".split(/\\s/),
"x".split(/\\S/), "x".split(/\\v/), "x".split(/\\w/), "x".split(/\\W/),
]
`,
expected: [
["x"], ["x"], ["", ""], ["", ""], ["", ""], ["x"], ["", ""], ["", ""], ["", ""],
["x"], ["", ""], ["x"], ["x"], ["x"], ["x"], ["", ""], ["x"], ["x"], ["x"],
["x"], ["x"], ["", ""], ["x"], ["x"], ["x"], ["", ""], ["x"], ["", ""], ["x"],
],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-s-and-3-and-instance-is-string-a-b-c-de-f.js",
code: `return "a b c de f".split(/\\s/, 3)`,
expected: ["a", "b", "c"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-regexp-s-and-instance-is-string-a-b-c-de-f.js",
code: `return "a b c de f".split(/\\s/)`,
expected: ["a", "b", "c", "de", "f"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-regexp-d-and-instance-is-string-dfe23iu-34-65.js",
code: `return "dfe23iu 34 =+65--".split(/\\d+/)`,
expected: ["dfe", "iu ", " =+", "--"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-regexp-reg-exp-d-and-instance-is-string-dfe23iu-34-65.js",
code: `return "dfe23iu 34 =+65--".split(new RegExp("\\\\d+"))`,
expected: ["dfe", "iu ", " =+", "--"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-regexp-a-z-and-instance-is-string-abc.js",
code: `return "abc".split(/[a-z]/)`,
expected: ["", "", "", ""],
},
{
path: "test/built-ins/String/prototype/split/argument-is-reg-exp-a-z-and-instance-is-string-abc.js",
code: `return "abc".split(new RegExp("[a-z]"))`,
expected: ["", "", "", ""],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-l-and-undefined-and-instance-is-string-hello.js",
code: `return "hello".split(/l/, undefined)`,
expected: ["he", "", "o"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-l-and-0-and-instance-is-string-hello.js",
code: `return "hello".split(/l/, 0)`,
expected: [],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-l-and-1-and-instance-is-string-hello.js",
code: `return "hello".split(/l/, 1)`,
expected: ["he"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-l-and-2-and-instance-is-string-hello.js",
code: `return "hello".split(/l/, 2)`,
expected: ["he", ""],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-l-and-3-and-instance-is-string-hello.js",
code: `return "hello".split(/l/, 3)`,
expected: ["he", "", "o"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-regexp-l-and-4-and-instance-is-string-hello.js",
code: `return "hello".split(/l/, 4)`,
expected: ["he", "", "o"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-regexp-l-and-instance-is-string-hello.js",
code: `return "hello".split(/l/)`,
expected: ["he", "", "o"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-new-reg-exp-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp())`,
expected: ["h", "e", "l", "l", "o"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-0-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp(), 0)`,
expected: [],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-1-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp(), 1)`,
expected: ["h"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-2-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp(), 2)`,
expected: ["h", "e"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-3-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp(), 3)`,
expected: ["h", "e", "l"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-4-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp(), 4)`,
expected: ["h", "e", "l", "l"],
},
{
path: "test/built-ins/String/prototype/split/arguments-are-new-reg-exp-and-undefined-and-instance-is-string-hello.js",
code: `return "hello".split(new RegExp(), undefined)`,
expected: ["h", "e", "l", "l", "o"],
},
{
path: "test/built-ins/String/prototype/split/call-split-2-instance-is-string-one-two-three-four-five.js",
code: `return "one two three four five".split(/ /, 2)`,
expected: ["one", "two"],
},
{
path: "test/built-ins/String/prototype/split/separator-regexp-comma-instance-is-string-one-1-two-2-four-4.js",
code: `return "one-1,two-2,four-4".split(/,/)`,
expected: ["one-1", "two-2", "four-4"],
},
{
path: "test/built-ins/String/prototype/split/argument-is-regexp-x-and-instance-is-string-a-b-c-de-f.js",
code: `return "a b c de f".split(/X/)`,
expected: ["a b c de f"],
},
])
run("Test262-adapted replace behavior", [
{
path: "test/built-ins/String/prototype/replace/regexp-capture-by-index.js",
code: `
const str = "foo-x-bar"
const patterns = ["x", /x/, /(x)/, /(x)($^)?/, /((((((((((x))))))))))/]
const replacements = ["|$0|", "|$00|", "|$000|", "|$1|", "|$01|", "|$010|", "|$2|", "|$02|", "|$020|", "|$10|", "|$100|", "|$20|", "|$200|"]
return replacements.flatMap((replacement) => patterns.map((pattern) => str.replace(pattern, replacement)))
`,
expected: [
"foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar",
"foo-|$00|-bar", "foo-|$00|-bar", "foo-|$00|-bar", "foo-|$00|-bar", "foo-|$00|-bar",
"foo-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-bar",
"foo-|$1|-bar", "foo-|$1|-bar", "foo-|x|-bar", "foo-|x|-bar", "foo-|x|-bar",
"foo-|$01|-bar", "foo-|$01|-bar", "foo-|x|-bar", "foo-|x|-bar", "foo-|x|-bar",
"foo-|$010|-bar", "foo-|$010|-bar", "foo-|x0|-bar", "foo-|x0|-bar", "foo-|x0|-bar",
"foo-|$2|-bar", "foo-|$2|-bar", "foo-|$2|-bar", "foo-||-bar", "foo-|x|-bar",
"foo-|$02|-bar", "foo-|$02|-bar", "foo-|$02|-bar", "foo-||-bar", "foo-|x|-bar",
"foo-|$020|-bar", "foo-|$020|-bar", "foo-|$020|-bar", "foo-|0|-bar", "foo-|x0|-bar",
"foo-|$10|-bar", "foo-|$10|-bar", "foo-|x0|-bar", "foo-|x0|-bar", "foo-|x|-bar",
"foo-|$100|-bar", "foo-|$100|-bar", "foo-|x00|-bar", "foo-|x00|-bar", "foo-|x0|-bar",
"foo-|$20|-bar", "foo-|$20|-bar", "foo-|$20|-bar", "foo-|0|-bar", "foo-|x0|-bar",
"foo-|$200|-bar", "foo-|$200|-bar", "foo-|$200|-bar", "foo-|00|-bar", "foo-|x00|-bar",
],
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A1_T17.js",
code: `return "asdf".replace(new RegExp(undefined, "g"), "1")`,
expected: "1a1s1d1f1",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T1.js",
code: `return "She sells seashells by the seashore.".replace(/sh/g, "sch")`,
expected: "She sells seaschells by the seaschore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T2.js",
code: `return "She sells seashells by the seashore.".replace(/sh/g, "$$sch")`,
expected: "She sells sea$schells by the sea$schore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T3.js",
code: `return "She sells seashells by the seashore.".replace(/sh/g, "$&sch")`,
expected: "She sells seashschells by the seashschore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T4.js",
code: `return "She sells seashells by the seashore.".replace(/sh/g, "$\`sch")`,
expected: "She sells seaShe sells seaschells by the seaShe sells seashells by the seaschore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T5.js",
code: `return "She sells seashells by the seashore.".replace(/sh/g, "$'sch")`,
expected: "She sells seaells by the seashore.schells by the seaore.schore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T6.js",
code: `return "She sells seashells by the seashore.".replace(/sh/, "sch")`,
expected: "She sells seaschells by the seashore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T7.js",
code: `return "She sells seashells by the seashore.".replace(/sh/, "$$sch")`,
expected: "She sells sea$schells by the seashore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T8.js",
code: `return "She sells seashells by the seashore.".replace(/sh/, "$&sch")`,
expected: "She sells seashschells by the seashore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T9.js",
code: `return "She sells seashells by the seashore.".replace(/sh/, "$\`sch")`,
expected: "She sells seaShe sells seaschells by the seashore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A2_T10.js",
code: `return "She sells seashells by the seashore.".replace(/sh/, "$'sch")`,
expected: "She sells seaells by the seashore.schells by the seashore.",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A3_T1.js",
code: `return "uid=31".replace(/(uid=)(\\d+)/, "$1115")`,
expected: "uid=115",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A3_T2.js",
code: `return "uid=31".replace(/(uid=)(\\d+)/, "$1115")`,
expected: "uid=115",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A3_T3.js",
code: `return "uid=31".replace(/(uid=)(\\d+)/, "$11A15")`,
expected: "uid=1A15",
},
{
path: "test/built-ins/String/prototype/replace/S15.5.4.11_A5_T1.js",
code: `return "aaaaaaaaaa,aaaaaaaaaaaaaaa".replace(/^(a+)\\1*,\\1+$/, "$1")`,
expected: "aaaaa",
},
])
run("Test262-adapted replaceAll behavior", [
{
path: "test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js",
code: `
return [
"abc abc abc".replaceAll(new RegExp("b", "g"), "z"),
"abc abc abc".replaceAll(new RegExp("b", "gy"), "z"),
"abc abc abc".replaceAll(new RegExp("b", "giy"), "z"),
"No Uppercase!".replaceAll(new RegExp("[A-Z]", "g"), ""),
"No Uppercase?".replaceAll(new RegExp("[A-Z]", "gy"), ""),
"NO UPPERCASE!".replaceAll(new RegExp("[A-Z]", "gy"), ""),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "$2-$1"),
"abcabcabcabc".replaceAll(new RegExp("(a(.))", "g"), "$1$2$3"),
"aabacadaeafagahaiajakalamano a azaya".replaceAll(new RegExp("(((((((((((((a(.).).).).).).).).))))))", "g"), "($10)-($12)-($1)"),
"abcba".replaceAll(new RegExp("b", "g"), "$'"),
"abcba".replaceAll(new RegExp("b", "g"), "$\`"),
"abcba".replaceAll(new RegExp("(?<named>b)", "g"), "($<named>)"),
"abcba".replaceAll(new RegExp("(?<named>b)", "g"), "($<named)"),
"abcba".replaceAll(new RegExp("(?<named>b)", "g"), "($<unnamed>)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$$$)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$$)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$&)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$1)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$\`)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($$')"),
"abcabcabcabc".replaceAll(new RegExp("a(?<z>b)(ca)", "g"), "($$<z>)"),
"abcabcabcabc".replaceAll(new RegExp("a(b)(ca)", "g"), "($&)"),
]
`,
expected: [
"azc azc azc", "abc abc abc", "abc abc abc", "o ppercase!", "o Uppercase?", " UPPERCASE!",
"ca-bbcca-bbc", "abb$3cabb$3cabb$3cabb$3c",
"(aabaca)-(aaba)-(aabacadaea)f(agahai)-(agah)-(agahaiajak)(alaman)-(alam)-(alamano a )azaya",
"acbacaa", "aacabca", "a(b)c(b)a", "a($<named)c($<named)a", "a()c()a", "($)bc($)bc",
"($)bc($)bc", "($$)bc($$)bc", "($$)bc($$)bc", "($&)bc($&)bc", "($1)bc($1)bc",
"($`)bc($`)bc", "($')bc($')bc", "($<z>)bc($<z>)bc", "(abca)bc(abca)bc",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/searchValue-empty-string.js",
code: `return ["aab c \\nx".replaceAll("", "_"), "a".replaceAll("", "_")]`,
expected: ["_a_a_b_ _c_ _ _\n_x_", "_a_"],
},
{
path: "test/built-ins/String/prototype/replaceAll/searchValue-empty-string-this-empty-string.js",
code: `return "".replaceAll("", "abc")`,
expected: "abc",
},
{
path: "test/built-ins/String/prototype/replaceAll/replaceValue-value-replaces-string.js",
code: `return ["aaab a a aac".replaceAll("aa", "z"), "aaab a a aac".replaceAll("aa", "a"), "aaab a a aac".replaceAll("a", "a"), "aaab a a aac".replaceAll("a", "z")]`,
expected: ["zab a a zc", "aab a a ac", "aaab a a aac", "zzzb z z zzc"],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024.js",
code: `
const str = "Ninguém é igual a ninguém. Todo o ser humano é um estranho ímpar."
return [str.replaceAll("ninguém", "$"), str.replaceAll("é", "$"), str.replaceAll("é", "$ -"), str.replaceAll("é", "$$$")]
`,
expected: [
"Ninguém é igual a $. Todo o ser humano é um estranho ímpar.",
"Ningu$m $ igual a ningu$m. Todo o ser humano $ um estranho ímpar.",
"Ningu$ -m $ - igual a ningu$ -m. Todo o ser humano $ - um estranho ímpar.",
"Ningu$$m $$ igual a ningu$$m. Todo o ser humano $$ um estranho ímpar.",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0024.js",
code: `
const str = "Ninguém é igual a ninguém. Todo o ser humano é um estranho ímpar."
return [str.replaceAll("ninguém", "$$"), str.replaceAll("é", "$$"), str.replaceAll("é", "$$ -"), str.replaceAll("é", "$$&"), str.replaceAll("é", "$$$"), str.replaceAll("é", "$$$$")]
`,
expected: [
"Ninguém é igual a $. Todo o ser humano é um estranho ímpar.",
"Ningu$m $ igual a ningu$m. Todo o ser humano $ um estranho ímpar.",
"Ningu$ -m $ - igual a ningu$ -m. Todo o ser humano $ - um estranho ímpar.",
"Ningu$&m $& igual a ningu$&m. Todo o ser humano $& um estranho ímpar.",
"Ningu$$m $$ igual a ningu$$m. Todo o ser humano $$ um estranho ímpar.",
"Ningu$$m $$ igual a ningu$$m. Todo o ser humano $$ um estranho ímpar.",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0026.js",
code: `
const str = "Ninguém é igual a ninguém. Todo o ser humano é um estranho ímpar."
return [str.replaceAll("ninguém", "$&"), str.replaceAll("ninguém", "($&)"), str.replaceAll("é", "($&)"), str.replaceAll("é", "($&) $&")]
`,
expected: [
"Ninguém é igual a ninguém. Todo o ser humano é um estranho ímpar.",
"Ninguém é igual a (ninguém). Todo o ser humano é um estranho ímpar.",
"Ningu(é)m (é) igual a ningu(é)m. Todo o ser humano (é) um estranho ímpar.",
"Ningu(é) ém (é) é igual a ningu(é) ém. Todo o ser humano (é) é um estranho ímpar.",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0060.js",
code: `
const str = "Ninguém é igual a ninguém. Todo o ser humano é um estranho ímpar."
return [str.replaceAll("ninguém", "$\`"), str.replaceAll("Ninguém", "$\`"), str.replaceAll("ninguém", "($\`)"), str.replaceAll("é", "($\`)")]
`,
expected: [
"Ninguém é igual a Ninguém é igual a . Todo o ser humano é um estranho ímpar.",
" é igual a ninguém. Todo o ser humano é um estranho ímpar.",
"Ninguém é igual a (Ninguém é igual a ). Todo o ser humano é um estranho ímpar.",
"Ningu(Ningu)m (Ninguém ) igual a ningu(Ninguém é igual a ningu)m. Todo o ser humano (Ninguém é igual a ninguém. Todo o ser humano ) um estranho ímpar.",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0027.js",
code: `
const str = "Ninguém é igual a ninguém. Todo o ser humano é um estranho ímpar."
return [str.replaceAll("ninguém", "$'"), str.replaceAll(".", "--- $'"), str.replaceAll("é", "($')")]
`,
expected: [
"Ninguém é igual a . Todo o ser humano é um estranho ímpar.. Todo o ser humano é um estranho ímpar.",
"Ninguém é igual a ninguém--- Todo o ser humano é um estranho ímpar. Todo o ser humano é um estranho ímpar--- ",
"Ningu(m é igual a ninguém. Todo o ser humano é um estranho ímpar.)m ( igual a ninguém. Todo o ser humano é um estranho ímpar.) igual a ningu(m. Todo o ser humano é um estranho ímpar.)m. Todo o ser humano ( um estranho ímpar.) um estranho ímpar.",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024N.js",
code: `
const str = "ABC AAA ABC AAA"
return ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9"].map((replacement) => str.replaceAll("ABC", replacement))
`,
expected: ["$1 AAA $1 AAA", "$2 AAA $2 AAA", "$3 AAA $3 AAA", "$4 AAA $4 AAA", "$5 AAA $5 AAA", "$6 AAA $6 AAA", "$7 AAA $7 AAA", "$8 AAA $8 AAA", "$9 AAA $9 AAA"],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024NN.js",
code: `
const str = "aaaaaaaaaaaaaaaa aaaaaaaa aaaaaaaaaaaaaaaa"
return [str.replaceAll("a", "$11"), str.replaceAll("a", "$29")]
`,
expected: [
"$11$11$11$11$11$11$11$11$11$11$11$11$11$11$11$11 $11$11$11$11$11$11$11$11 $11$11$11$11$11$11$11$11$11$11$11$11$11$11$11$11",
"$29$29$29$29$29$29$29$29$29$29$29$29$29$29$29$29 $29$29$29$29$29$29$29$29 $29$29$29$29$29$29$29$29$29$29$29$29$29$29$29$29",
],
},
{
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x003C.js",
code: `return "aaaaaaaaaaaaaaaa aaaaaaaa aaaaaaaaaaaaaaaa".replaceAll("a", "$<")`,
expected: "$<$<$<$<$<$<$<$<$<$<$<$<$<$<$<$< $<$<$<$<$<$<$<$< $<$<$<$<$<$<$<$<$<$<$<$<$<$<$<$<",
},
])
run("Test262-adapted match behavior", [
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A1_T14.js",
code: `const match = "ABBABABAB77BBAA".match(new RegExp("77")); return [match[0], match.index]`,
expected: ["77", 9],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T2.js",
code: `return "343443444".match(/34/g)`,
expected: ["34", "34", "34"],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T3.js",
code: `return "123456abcde7890".match(/\\d{1}/g)`,
expected: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T4.js",
code: `return "123456abcde7890".match(/\\d{2}/g)`,
expected: ["12", "34", "56", "78", "90"],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T5.js",
code: `return "123456abcde7890".match(/\\D{2}/g)`,
expected: ["ab", "cd"],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T6.js",
code: `const match = "Boston, Mass. 02134".match(/([\\d]{5})([- ]?[\\d]{4})?$/); return [match[0], match[1], match[2] === undefined, match.length, match.index]`,
expected: ["02134", "02134", true, 3, 14],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T7.js",
code: `return "Boston, Mass. 02134".match(/([\\d]{5})([- ]?[\\d]{4})?$/g)`,
expected: ["02134"],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T8.js",
code: `const match = "Boston, MA 02134".match(/([\\d]{5})([- ]?[\\d]{4})?$/); return [match[0], match[1], match[2] === undefined, match.length, match.index]`,
expected: ["02134", "02134", true, 3, 11],
},
{
path: "test/built-ins/String/prototype/match/S15.5.4.10_A2_T12.js",
code: `return "Boston, MA 02134".match(/([\\d]{5})([- ]?[\\d]{4})?$/g)`,
expected: ["02134"],
},
])
run("Test262-adapted matchAll behavior", [
{
path: "test/built-ins/String/prototype/matchAll/regexp-prototype-matchAll-v-u-flag.js",
code: `
const text = "𠮷a𠮷b𠮷"
const collect = (regex) => {
const matches = text.matchAll(regex)
return matches.map((match) => match[0]).concat(matches.map((match) => match.index))
}
const empty = text.matchAll(/(?:)/gu)
const complex = "a𠮷b􏿿c".matchAll(/\\P{ASCII}/gu)
return [
collect(/𠮷/g),
collect(/𠮷/gu),
collect(/\\p{Script=Han}/gu),
collect(/./gu),
empty.map((match) => match[0]).concat(empty.map((match) => match.index)).length,
complex.map((match) => match[0]),
]
`,
expected: [
["𠮷", "𠮷", "𠮷", 0, 3, 6],
["𠮷", "𠮷", "𠮷", 0, 3, 6],
["𠮷", "𠮷", "𠮷", 0, 3, 6],
["𠮷", "a", "𠮷", "b", "𠮷", 0, 2, 3, 5, 6],
12,
["𠮷", "􏿿"],
],
},
])
run("Test262-adapted search behavior", [
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A1_T14.js",
code: `return "ABBABABAB77BBAA".search(new RegExp("77"))`,
expected: 9,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T1.js",
code: `return "test string".search("string")`,
expected: 5,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T2.js",
code: `return "test string".search("String")`,
expected: -1,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T3.js",
code: `return "test string".search(/String/i)`,
expected: 5,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T4.js",
code: `return "one two three four five".search(/Four/)`,
expected: -1,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T5.js",
code: `return "one two three four five".search(/four/)`,
expected: 14,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T6.js",
code: `return "test string".search("notexist")`,
expected: -1,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A2_T7.js",
code: `return "test string probe".search("string pro")`,
expected: 5,
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A3_T1.js",
code: `const text = "power of the power of the power of the great sword"; return [text.search(/the/), text.search(/the/g)]`,
expected: [9, 9],
},
{
path: "test/built-ins/String/prototype/search/S15.5.4.12_A3_T2.js",
code: `const text = "power of the power of the power of the great sword"; return [text.search(/of/), text.search(/of/g)]`,
expected: [6, 6],
},
])

View file

@ -0,0 +1,794 @@
/*
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
* - test/built-ins/String/prototype/split/call-split-l-0-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-l-1-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-l-2-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-l-3-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-l-4-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-l-na-n-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-l-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-ll-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-h-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-hello-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-hellothere-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-o-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-x-instance-is-string-hello.js
* - test/built-ins/String/prototype/split/call-split-x-instance-is-empty-string.js
* - test/built-ins/String/prototype/split/call-split-4-instance-is-string-one-1-two-2-four-4.js
* - test/built-ins/String/prototype/split/call-split-on-instance-is-string-one-1-two-2-four-4.js
* - test/built-ins/String/prototype/split/call-split-instance-is-string-one-two-three-four-five.js
* - test/built-ins/String/prototype/split/call-split-instance-is-string-one-two-three.js
* - test/built-ins/String/prototype/split/call-split-instance-is-string.js
* - test/built-ins/String/prototype/split/instance-is-string-one-two-three-four-five.js
* - test/built-ins/String/prototype/split/instance-is-string.js
* - test/built-ins/String/prototype/split/separator-colon-instance-is-string-one-1-two-2-four-4.js
* - test/built-ins/String/prototype/split/separator-comma-instance-is-string-one-two-three-four-five.js
* - test/built-ins/String/prototype/split/separator-empty-string-instance-is-string.js
* - test/built-ins/String/prototype/split/call-split-without-arguments-and-instance-is-empty-string.js
* - test/built-ins/String/prototype/split/separator-undef.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A1_T6.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A1_T14.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T1.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T2.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T3.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T4.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T5.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T6.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T7.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T8.js
* - test/built-ins/String/prototype/slice/S15.5.4.13_A2_T9.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A1_T6.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A1_T14.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T1.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T2.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T3.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T4.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T5.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T6.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T7.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T8.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T9.js
* - test/built-ins/String/prototype/substring/S15.5.4.15_A2_T10.js
* - test/annexB/built-ins/String/prototype/substr/start-negative.js
* - test/annexB/built-ins/String/prototype/substr/length-negative.js
* - test/annexB/built-ins/String/prototype/substr/length-positive.js
* - test/annexB/built-ins/String/prototype/substr/length-falsey.js
* - test/annexB/built-ins/String/prototype/substr/length-undef.js
* - test/annexB/built-ins/String/prototype/substr/surrogate-pairs.js
* - test/built-ins/String/prototype/includes/String.prototype.includes_FailMissingLetter.js
* - test/built-ins/String/prototype/includes/String.prototype.includes_SuccessNoLocation.js
* - test/built-ins/String/prototype/includes/String.prototype.includes_FailBadLocation.js
* - test/built-ins/String/prototype/includes/String.prototype.includes_FailLocation.js
* - test/built-ins/String/prototype/includes/String.prototype.includes_Success.js
* - test/built-ins/String/prototype/includes/searchstring-found-with-position.js
* - test/built-ins/String/prototype/includes/searchstring-found-without-position.js
* - test/built-ins/String/prototype/includes/searchstring-not-found-with-position.js
* - test/built-ins/String/prototype/includes/searchstring-not-found-without-position.js
* - test/built-ins/String/prototype/includes/return-false-with-out-of-bounds-position.js
* - test/built-ins/String/prototype/includes/return-true-if-searchstring-is-empty.js
* - test/built-ins/String/prototype/includes/coerced-values-of-position.js
* - test/built-ins/String/prototype/startsWith/searchstring-found-with-position.js
* - test/built-ins/String/prototype/startsWith/searchstring-found-without-position.js
* - test/built-ins/String/prototype/startsWith/searchstring-not-found-with-position.js
* - test/built-ins/String/prototype/startsWith/searchstring-not-found-without-position.js
* - test/built-ins/String/prototype/startsWith/out-of-bounds-position.js
* - test/built-ins/String/prototype/startsWith/return-true-if-searchstring-is-empty.js
* - test/built-ins/String/prototype/startsWith/coerced-values-of-position.js
* - test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success.js
* - test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_2.js
* - test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_3.js
* - test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_4.js
* - test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail.js
* - test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail_2.js
* - test/built-ins/String/prototype/endsWith/searchstring-found-with-position.js
* - test/built-ins/String/prototype/endsWith/searchstring-found-without-position.js
* - test/built-ins/String/prototype/endsWith/searchstring-not-found-with-position.js
* - test/built-ins/String/prototype/endsWith/searchstring-not-found-without-position.js
* - test/built-ins/String/prototype/endsWith/return-false-if-search-start-is-less-than-zero.js
* - test/built-ins/String/prototype/endsWith/return-true-if-searchstring-is-empty.js
* - test/built-ins/String/prototype/endsWith/coerced-values-of-position.js
* - test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T1.js
* - test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T2.js
* - test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T3.js
* - test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T4.js
* - test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T1.js
* - test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T3.js
* - test/built-ins/String/prototype/indexOf/position-tointeger.js
* - test/built-ins/String/prototype/indexOf/searchstring-tostring.js
* - test/built-ins/String/prototype/lastIndexOf/not-a-substring.js
*
* Copyright 2009 the Sputnik authors. All rights reserved.
* Copyright (c) 2014 Ryan Lewis. All rights reserved.
* Copyright (C) 2015 the V8 project authors. All rights reserved.
* Copyright (C) 2016 the V8 project authors. All rights reserved.
* Copyright (C) 2017 Josh Wolfe. All rights reserved.
* Copyright (C) 2020 Leo Balter. All rights reserved.
* Copyright (C) 2026 Garham Lee. All rights reserved.
* Test262 portions are governed by the BSD license in LICENSE.test262.
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const cases = [
{
path: "test/built-ins/String/prototype/split/call-split-l-0-instance-is-string-hello.js",
code: `const result = "hello".split("l", 0); return [result.length, result[0] === undefined]`,
expected: [0, true],
labels: [
"The value of __split.length is expected to equal the value of __expected.length",
"The value of __split[0] is expected to equal the value of __expected[0]",
],
},
{
path: "test/built-ins/String/prototype/split/call-split-l-1-instance-is-string-hello.js",
code: `const result = "hello".split("l", 1); return [result.length, result[0]]`,
expected: [1, "he"],
labels: [
"The value of __split.length is expected to equal the value of __expected.length",
"The value of __split[0] is expected to equal the value of __expected[0]",
],
},
{
path: "test/built-ins/String/prototype/split/call-split-l-2-instance-is-string-hello.js",
code: `const result = "hello".split("l", 2); return [result.length, result[0], result[1]]`,
expected: [2, "he", ""],
labels: [
"The value of __split.length is expected to equal the value of __expected.length",
"The value of __split[index] is expected to equal the value of __expected[index]",
"The value of __split[index] is expected to equal the value of __expected[index]",
],
},
{
path: "test/built-ins/String/prototype/split/call-split-l-3-instance-is-string-hello.js",
code: `const result = "hello".split("l", 3); return [result.length, result[0], result[1], result[2]]`,
expected: [3, "he", "", "o"],
labels: [
"The value of __split.length is expected to equal the value of __expected.length",
"The value of __split[index] is expected to equal the value of __expected[index]",
"The value of __split[index] is expected to equal the value of __expected[index]",
"The value of __split[index] is expected to equal the value of __expected[index]",
],
},
{
path: "test/built-ins/String/prototype/split/call-split-l-4-instance-is-string-hello.js",
code: `const result = "hello".split("l", 4); return [result.length, result[0], result[1], result[2]]`,
expected: [3, "he", "", "o"],
labels: [
"The value of __split.length is expected to equal the value of __expected.length",
"The value of __split[index] is expected to equal the value of __expected[index]",
"The value of __split[index] is expected to equal the value of __expected[index]",
"The value of __split[index] is expected to equal the value of __expected[index]",
],
},
{
path: "test/built-ins/String/prototype/split/call-split-l-na-n-instance-is-string-hello.js",
code: `const result = "hello".split("l", NaN); return [result.length, result[0] === undefined]`,
expected: [0, true],
labels: [
"The value of __split.length is expected to equal the value of __expected.length",
"The value of __split[0] is expected to equal the value of __expected[0]",
],
},
{
path: "test/built-ins/String/prototype/split/call-split-l-instance-is-string-hello.js",
code: `const result = "hello".split("l"); return [result.length, result[0], result[1], result[2]]`,
expected: [3, "he", "", "o"],
labels: [
"The value of __split.length is 3",
'The value of __split[0] is "he"',
'The value of __split[1] is ""',
'The value of __split[2] is "o"',
],
},
{
path: "test/built-ins/String/prototype/split/call-split-ll-instance-is-string-hello.js",
code: `const result = "hello".split("ll"); return [result.length, result[0], result[1]]`,
expected: [2, "he", "o"],
labels: ["The value of __split.length is 2", 'The value of __split[0] is "he"', 'The value of __split[1] is "o"'],
},
{
path: "test/built-ins/String/prototype/split/call-split-h-instance-is-string-hello.js",
code: `const result = "hello".split("h"); return [result.length, result[0], result[1]]`,
expected: [2, "", "ello"],
labels: ["The value of __split.length is 2", 'The value of __split[0] is ""', 'The value of __split[1] is "ello"'],
},
{
path: "test/built-ins/String/prototype/split/call-split-hello-instance-is-string-hello.js",
code: `const result = "hello".split("hello"); return [result.length, result[0], result[1]]`,
expected: [2, "", ""],
labels: ["The value of __split.length is 2", 'The value of __split[0] is ""', 'The value of __split[1] is ""'],
},
{
path: "test/built-ins/String/prototype/split/call-split-hellothere-instance-is-string-hello.js",
code: `const result = "hello".split("hellothere"); return [result.length, result[0]]`,
expected: [1, "hello"],
labels: ["The value of __split.length is 1", 'The value of __split[0] is "hello"'],
},
{
path: "test/built-ins/String/prototype/split/call-split-o-instance-is-string-hello.js",
code: `const result = "hello".split("o"); return [result.length, result[0], result[1]]`,
expected: [2, "hell", ""],
labels: ["The value of __split.length is 2", 'The value of __split[0] is "hell"', 'The value of __split[1] is ""'],
},
{
path: "test/built-ins/String/prototype/split/call-split-x-instance-is-string-hello.js",
code: `const result = "hello".split("x"); return [result.length, result[0]]`,
expected: [1, "hello"],
labels: ["The value of __split.length is 1", 'The value of __split[0] is "hello"'],
},
{
path: "test/built-ins/String/prototype/split/call-split-x-instance-is-empty-string.js",
code: `const result = "".split("x"); return [result.length, result[0]]`,
expected: [1, ""],
labels: ["The value of __split.length is 1", 'The value of __split[0] is ""'],
},
{
path: "test/built-ins/String/prototype/split/call-split-4-instance-is-string-one-1-two-2-four-4.js",
code: `const result = "one-1 two-2 four-4".split("-4"); return [result.length, result[0], result[1]]`,
expected: [2, "one-1 two-2 four", ""],
labels: [
"The value of __split.length is 2",
'The value of __split[0] is "one-1 two-2 four"',
'The value of __split[1] is ""',
],
},
{
path: "test/built-ins/String/prototype/split/call-split-on-instance-is-string-one-1-two-2-four-4.js",
code: `const result = "one-1 two-2 four-4".split("on"); return [result.length, result[0], result[1]]`,
expected: [2, "", "e-1 two-2 four-4"],
labels: [
"The value of __split.length is 2",
'The value of __split[0] is ""',
'The value of __split[1] is "e-1 two-2 four-4"',
],
},
{
path: "test/built-ins/String/prototype/split/call-split-instance-is-string-one-two-three-four-five.js",
code: `const result = "one two three four five".split(" "); return [result.length, ...result]`,
expected: [5, "one", "two", "three", "four", "five"],
labels: [
"The value of __split.length is 5", 'The value of __split[0] is "one"', 'The value of __split[1] is "two"',
'The value of __split[2] is "three"', 'The value of __split[3] is "four"', 'The value of __split[4] is "five"',
],
},
{
path: "test/built-ins/String/prototype/split/call-split-instance-is-string-one-two-three.js",
code: `const result = "one two three".split(""); return [result[0], result[1], result[11], result[12]]`,
expected: ["o", "n", "e", "e"],
labels: [
'The value of __split[0] is "o"', 'The value of __split[1] is "n"',
'The value of __split[11] is "e"', 'The value of __split[12] is "e"',
],
},
{
path: "test/built-ins/String/prototype/split/call-split-instance-is-string.js",
code: `const result = " ".split(" "); return [result.length, result[0], result[1]]`,
expected: [2, "", ""],
labels: ["The value of __split.length is 2", 'The value of __split[0] is ""', 'The value of __split[1] is ""'],
},
{
path: "test/built-ins/String/prototype/split/instance-is-string-one-two-three-four-five.js",
code: `const result = "one,two,three,four,five".split(); return [result.length, result[0]]`,
expected: [1, "one,two,three,four,five"],
labels: ["The value of __split.length is 1", 'The value of __split[0] is "one,two,three,four,five"'],
},
{
path: "test/built-ins/String/prototype/split/instance-is-string.js",
code: `const result = " ".split(); return [result.length, result[0]]`,
expected: [1, " "],
labels: ["The value of __split.length is 1", 'The value of __split[0] is " "'],
},
{
path: "test/built-ins/String/prototype/split/separator-colon-instance-is-string-one-1-two-2-four-4.js",
code: `const result = "one-1,two-2,four-4".split(":"); return [result.length, result[0]]`,
expected: [1, "one-1,two-2,four-4"],
labels: ["The value of __split.length is 1", 'The value of __split[0] is "one-1,two-2,four-4"'],
},
{
path: "test/built-ins/String/prototype/split/separator-comma-instance-is-string-one-two-three-four-five.js",
code: `const result = "one,two,three,four,five".split(","); return [result.length, ...result]`,
expected: [5, "one", "two", "three", "four", "five"],
labels: [
"The value of __split.length is 5",
'The value of __split[0] is "one"',
'The value of __split[1] is "two"',
'The value of __split[2] is "three"',
'The value of __split[3] is "four"',
'The value of __split[4] is "five"',
],
},
{
path: "test/built-ins/String/prototype/split/separator-empty-string-instance-is-string.js",
code: `const result = " ".split(""); return [result.length, result[0]]`,
expected: [1, " "],
labels: ["The value of __split.length is 1", 'The value of __split[0] is " "'],
},
{
path: "test/built-ins/String/prototype/split/call-split-without-arguments-and-instance-is-empty-string.js",
code: `const result = "".split(); return [result.length, result[0]]`,
expected: [1, ""],
labels: ["The value of __split.length is 1", 'The value of __split[0] is ""'],
},
{
path: "test/built-ins/String/prototype/split/separator-undef.js",
code: `const result = "undefined is not a function".split(); return [Array.isArray(result), result.length, result[0]]`,
expected: [true, 1, "undefined is not a function"],
labels: ["implicit separator, result is array", "implicit separator, result.length", "implicit separator, [0] is the same string"],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A1_T6.js",
code: `return ["undefined".slice(undefined, 3)]`,
expected: ["und"],
labels: ['#1: new String("undefined").slice(x,3) === "und"'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A1_T14.js",
code: `return ["report".slice(undefined)]`,
expected: ["report"],
labels: ['#1: "report".slice(function(){}()) === "report"'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T1.js",
code: `return [typeof "this is a string object".slice()]`,
expected: ["string"],
labels: ['#1: typeof __string.slice() === "string"'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T2.js",
code: `return ["this is a string object".slice(NaN, Infinity)]`,
expected: ["this is a string object"],
labels: ['#1: __string.slice(NaN, Infinity) === "this is a string object"'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T3.js",
code: `return ["".slice(1, 0)]`,
expected: [""],
labels: ['#1: __string.slice(1,0) === ""'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T4.js",
code: `return ["this is a string object".slice(Infinity, NaN)]`,
expected: [""],
labels: ['#1: __string.slice(Infinity, NaN) === ""'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T5.js",
code: `return ["this is a string object".slice(Infinity, Infinity)]`,
expected: [""],
labels: ['#1: __string.slice(Infinity, Infinity) === ""'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T6.js",
code: `return ["this is a string object".slice(-0.01, 0)]`,
expected: [""],
labels: ['#1: __string.slice(-0.01,0) === ""'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T7.js",
code: `const text = "this is a string object"; return [text.slice(text.length, text.length)]`,
expected: [""],
labels: ['#1: __string.slice(__string.length, __string.length) === ""'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T8.js",
code: `const text = "this is a string object"; return [text.slice(text.length + 1, 0)]`,
expected: [""],
labels: ['#1: __string.slice(__string.length+1, 0) === ""'],
},
{
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A2_T9.js",
code: `return ["this is a string object".slice(-Infinity, -Infinity)]`,
expected: [""],
labels: ['#1: __string.slice(-Infinity, -Infinity) === ""'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A1_T6.js",
code: `return ["undefined".substring(undefined, 3)]`,
expected: ["und"],
labels: ['#1: new String("undefined").substring(x,3) === "und"'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A1_T14.js",
code: `return ["report".substring(undefined)]`,
expected: ["report"],
labels: ['#1: "report".substring(function(){}()) === "report"'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T1.js",
code: `return [typeof "this is a string object".substring()]`,
expected: ["string"],
labels: ['#1: typeof __string.substring() === "string"'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T2.js",
code: `return ["this is a string object".substring(NaN, Infinity)]`,
expected: ["this is a string object"],
labels: ['#1: __string.substring(NaN, Infinity) === "this is a string object"'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T3.js",
code: `return ["".substring(1, 0)]`,
expected: [""],
labels: ['#1: __string.substring(1,0) === ""'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T4.js",
code: `return ["this is a string object".substring(Infinity, NaN)]`,
expected: ["this is a string object"],
labels: ['#1: __string.substring(Infinity, NaN) === "this is a string object"'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T5.js",
code: `return ["this is a string object".substring(Infinity, Infinity)]`,
expected: [""],
labels: ['#1: __string.substring(Infinity, Infinity) === ""'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T6.js",
code: `return ["this is a string object".substring(-0.01, 0)]`,
expected: [""],
labels: ['#1: __string.substring(-0.01,0) === ""'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T7.js",
code: `const text = "this is a string object"; return [text.substring(text.length, text.length)]`,
expected: [""],
labels: ['#1: __string.substring(__string.length, __string.length) === ""'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T8.js",
code: `const text = "this is a string object"; return [text.substring(text.length + 1, 0)]`,
expected: ["this is a string object"],
labels: ['#1: __string.substring(__string.length+1, 0) === "this is a string object"'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T9.js",
code: `return ["this is a string object".substring(-Infinity, -Infinity)]`,
expected: [""],
labels: ['#1: __string.substring(-Infinity, -Infinity) === ""'],
},
{
path: "test/built-ins/String/prototype/substring/S15.5.4.15_A2_T10.js",
code: `return ["this_is_a_string object".substring(0, 8)]`,
expected: ["this_is_"],
labels: ['#1: __string.substring(0,8) === "this_is_"'],
},
{
path: "test/annexB/built-ins/String/prototype/substr/start-negative.js",
code: `return ["abc".substr(-1), "abc".substr(-2), "abc".substr(-3), "abc".substr(-4), "abc".substr(-1.1)]`,
expected: ["c", "bc", "abc", "abc", "c"],
labels: ["-1", "-2", "-3", "size + intStart < 0", "floating point rounding semantics"],
},
{
path: "test/annexB/built-ins/String/prototype/substr/length-negative.js",
code: `return [
"abc".substr(0, -1), "abc".substr(0, -2), "abc".substr(0, -3), "abc".substr(0, -4),
"abc".substr(1, -1), "abc".substr(1, -2), "abc".substr(1, -3), "abc".substr(1, -4),
"abc".substr(2, -1), "abc".substr(2, -2), "abc".substr(2, -3), "abc".substr(2, -4),
"abc".substr(3, -1), "abc".substr(3, -2), "abc".substr(3, -3), "abc".substr(3, -4),
]`,
expected: ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
labels: [
"0, -1", "0, -2", "0, -3", "0, -4", "1, -1", "1, -2", "1, -3", "1, -4",
"2, -1", "2, -2", "2, -3", "2, -4", "3, -1", "3, -2", "3, -3", "3, -4",
],
},
{
path: "test/annexB/built-ins/String/prototype/substr/length-positive.js",
code: `return [
"abc".substr(0, 1), "abc".substr(0, 2), "abc".substr(0, 3), "abc".substr(0, 4),
"abc".substr(1, 1), "abc".substr(1, 2), "abc".substr(1, 3), "abc".substr(1, 4),
"abc".substr(2, 1), "abc".substr(2, 2), "abc".substr(2, 3), "abc".substr(2, 4),
"abc".substr(3, 1), "abc".substr(3, 2), "abc".substr(3, 3), "abc".substr(3, 4),
]`,
expected: ["a", "ab", "abc", "abc", "b", "bc", "bc", "bc", "c", "c", "c", "c", "", "", "", ""],
labels: [
"0, 1", "0, 1", "0, 1", "0, 1", "1, 1", "1, 1", "1, 1", "1, 1",
"2, 1", "2, 1", "2, 1", "2, 1", "3, 1", "3, 1", "3, 1", "3, 1",
],
},
{
path: "test/annexB/built-ins/String/prototype/substr/length-falsey.js",
code: `return ["abc".substr(0, NaN), "abc".substr(1, NaN), "abc".substr(2, NaN), "abc".substr(3, NaN)]`,
expected: ["", "", "", ""],
labels: ["start: 0, length: NaN", "start: 1, length: NaN", "start: 2, length: NaN", "start: 3, length: NaN"],
},
{
path: "test/annexB/built-ins/String/prototype/substr/length-undef.js",
code: `return [
"abc".substr(0), "abc".substr(1), "abc".substr(2), "abc".substr(3),
"abc".substr(0, undefined), "abc".substr(1, undefined), "abc".substr(2, undefined), "abc".substr(3, undefined),
]`,
expected: ["abc", "bc", "c", "", "abc", "bc", "c", ""],
labels: [
"start: 0, length: unspecified", "start: 1, length: unspecified", "start: 2, length: unspecified", "start: 3, length: unspecified",
"start: 0, length: undefined", "start: 1, length: undefined", "start: 2, length: undefined", "start: 3, length: undefined",
],
},
{
path: "test/annexB/built-ins/String/prototype/substr/surrogate-pairs.js",
code: `return [
"\uD834\uDF06".substr(0), "\uD834\uDF06".substr(1), "\uD834\uDF06".substr(2),
"\uD834\uDF06".substr(0, 0), "\uD834\uDF06".substr(0, 1), "\uD834\uDF06".substr(0, 2),
]`,
expected: ["\uD834\uDF06", "\uDF06", "", "", "\uD834", "\uD834\uDF06"],
labels: ["start: 0", "start: 1", "start: 2", "end: 0", "end: 1", "end: 2"],
},
{
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailMissingLetter.js",
code: `return ["word".includes("a", 0)]`, expected: [false], labels: ['"word".includes("a", 0)'],
},
{
path: "test/built-ins/String/prototype/includes/String.prototype.includes_SuccessNoLocation.js",
code: `return ["word".includes("w")]`, expected: [true], labels: ['"word".includes("w")'],
},
{
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailBadLocation.js",
code: `return ["word".includes("w", 5)]`, expected: [false], labels: ['"word".includes("w", 5)'],
},
{
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailLocation.js",
code: `return ["word".includes("o", 3)]`, expected: [false], labels: ['"word".includes("o", 3)'],
},
{
path: "test/built-ins/String/prototype/includes/String.prototype.includes_Success.js",
code: `return ["word".includes("w", 0)]`, expected: [true], labels: ['"word".includes("w", 0)'],
},
{
path: "test/built-ins/String/prototype/includes/searchstring-found-with-position.js",
code: `const text = "The future is cool!"; return [text.includes("The future", 0), text.includes(" is ", 1), text.includes("cool!", 10)]`,
expected: [true, true, true],
labels: [
'Returns true for str.includes("The future", 0)',
'Returns true for str.includes(" is ", 1)',
'Returns true for str.includes("cool!", 10)',
],
},
{
path: "test/built-ins/String/prototype/includes/searchstring-found-without-position.js",
code: `const text = "The future is cool!"; return [text.includes("The future"), text.includes("is cool!"), text.includes(text)]`,
expected: [true, true, true],
labels: [
'Returns true for str.includes("The future")',
'Returns true for str.includes("is cool!")',
"Returns true for str.includes(str)",
],
},
{
path: "test/built-ins/String/prototype/includes/searchstring-not-found-with-position.js",
code: `const text = "The future is cool!"; return [text.includes("The future", 1), text.includes(text, 1)]`,
expected: [false, false],
labels: ['Returns false on str.includes("The future", 1)', "Returns false on str.includes(str, 1)"],
},
{
path: "test/built-ins/String/prototype/includes/searchstring-not-found-without-position.js",
code: `const text = "The future is cool!"; return [text.includes("Flash"), text.includes("FUTURE")]`,
expected: [false, false], labels: ["Flash if not included", "includes is case sensitive"],
},
{
path: "test/built-ins/String/prototype/includes/return-false-with-out-of-bounds-position.js",
code: `const text = "The future is cool!"; return [
text.includes("!", text.length + 1), text.includes("!", 100), text.includes("!", Infinity), text.includes("!", text.length),
]`,
expected: [false, false, false, false],
labels: [
'str.includes("!", str.length + 1) returns false', 'str.includes("!", 100) returns false',
'str.includes("!", Infinity) returns false', 'str.includes("!", str.length) returns false',
],
},
{
path: "test/built-ins/String/prototype/includes/return-true-if-searchstring-is-empty.js",
code: `const text = "The future is cool!"; return [text.includes("", text.length), text.includes(""), text.includes("", Infinity)]`,
expected: [true, true, true],
labels: ['str.includes("", str.length) returns true', 'str.includes("") returns true', 'str.includes("", Infinity) returns true'],
},
{
path: "test/built-ins/String/prototype/includes/coerced-values-of-position.js",
code: `const text = "The future is cool!"; return [
text.includes("The future", NaN), text.includes("The future", undefined), text.includes("The future", 0.4),
text.includes("The future", -1), text.includes("The future", 1.4),
]`,
expected: [true, true, true, true, false],
labels: ["NaN coerced to 0", "undefined coerced to 0", "0.4 coerced to 0", "negative position", "1.4 coerced to 1"],
},
{
path: "test/built-ins/String/prototype/startsWith/searchstring-found-with-position.js",
code: `const text = "The future is cool!"; return [text.startsWith("The future", 0), text.startsWith("future", 4), text.startsWith(" is cool!", 10)]`,
expected: [true, true, true],
labels: [
'str.startsWith("The future", 0) === true', 'str.startsWith("future", 4) === true',
'str.startsWith(" is cool!", 10) === true',
],
},
{
path: "test/built-ins/String/prototype/startsWith/searchstring-found-without-position.js",
code: `const text = "The future is cool!"; return [text.startsWith("The "), text.startsWith("The future"), text.startsWith(text)]`,
expected: [true, true, true],
labels: ['str.startsWith("The ") === true', 'str.startsWith("The future") === true', "str.startsWith(str) === true"],
},
{
path: "test/built-ins/String/prototype/startsWith/searchstring-not-found-with-position.js",
code: `const text = "The future is cool!"; return [text.startsWith("The future", 1), text.startsWith(text, 1)]`,
expected: [false, false],
labels: ['str.startsWith("The future", 1) === false', "str.startsWith(str, 1) === false"],
},
{
path: "test/built-ins/String/prototype/startsWith/searchstring-not-found-without-position.js",
code: `const text = "The future is cool!"; return [text.startsWith("Flash"), text.startsWith("THE FUTURE"), text.startsWith("future is cool!")]`,
expected: [false, false, false],
labels: ['str.startsWith("Flash") === false', "startsWith is case sensitive", 'str.startsWith("future is cool!") === false'],
},
{
path: "test/built-ins/String/prototype/startsWith/out-of-bounds-position.js",
code: `const text = "The future is cool!"; return [
text.startsWith("!", text.length), text.startsWith("!", 100), text.startsWith("!", Infinity),
text.startsWith("The future", -1), text.startsWith("The future", -Infinity),
]`,
expected: [false, false, false, true, true],
labels: [
'str.startsWith("!", str.length) returns false', 'str.startsWith("!", 100) returns false',
'str.startsWith("!", Infinity) returns false', "position argument < 0 will search from the start of the string (-1)",
"position argument < 0 will search from the start of the string (-Infinity)",
],
},
{
path: "test/built-ins/String/prototype/startsWith/return-true-if-searchstring-is-empty.js",
code: `const text = "The future is cool!"; return [text.startsWith(""), text.startsWith("", text.length), text.startsWith("", Infinity)]`,
expected: [true, true, true],
labels: ['str.startsWith("") returns true', 'str.startsWith("", str.length) returns true', 'str.startsWith("", Infinity) returns true'],
},
{
path: "test/built-ins/String/prototype/startsWith/coerced-values-of-position.js",
code: `const text = "The future is cool!"; return [
text.startsWith("The future", NaN), text.startsWith("The future", undefined),
text.startsWith("The future", 0.4), text.startsWith("The future", 1.4),
]`,
expected: [true, true, true, false],
labels: ["NaN coerced to 0", "undefined coerced to 0", "0.4 coerced to 0", "1.4 coerced to 1"],
},
{
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success.js",
code: `return ["word".endsWith("d")]`, expected: [true], labels: ['"word".endsWith("d")'],
},
{
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_2.js",
code: `return ["word".endsWith("d", 4)]`, expected: [true], labels: ['"word".endsWith("d", 4)'],
},
{
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_3.js",
code: `return ["word".endsWith("d", 25)]`, expected: [true], labels: ['"word".endsWith("d", 25)'],
},
{
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_4.js",
code: `return ["word".endsWith("r", 3)]`, expected: [true], labels: ['"word".endsWith("r", 3)'],
},
{
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail.js",
code: `return ["word".endsWith("r")]`, expected: [false], labels: ['"word".endsWith("r")'],
},
{
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail_2.js",
code: `return ["word".endsWith("d", 3)]`, expected: [false], labels: ['"word".endsWith("d", 3)'],
},
{
path: "test/built-ins/String/prototype/endsWith/searchstring-found-with-position.js",
code: `const text = "The future is cool!"; return [text.endsWith("The future", 10), text.endsWith("future", 10), text.endsWith(" is cool!", text.length)]`,
expected: [true, true, true],
labels: [
'str.endsWith("The future", 10) === true', 'str.endsWith("future", 10) === true',
'str.endsWith(" is cool!", str.length) === true',
],
},
{
path: "test/built-ins/String/prototype/endsWith/searchstring-found-without-position.js",
code: `const text = "The future is cool!"; return [text.endsWith("cool!"), text.endsWith("!"), text.endsWith(text)]`,
expected: [true, true, true],
labels: ['str.endsWith("cool!") === true', 'str.endsWith("!") === true', "str.endsWith(str) === true"],
},
{
path: "test/built-ins/String/prototype/endsWith/searchstring-not-found-with-position.js",
code: `const text = "The future is cool!"; return [text.endsWith("is cool!", text.length - 1), text.endsWith("!", 1)]`,
expected: [false, false],
labels: ['str.endsWith("is cool!", str.length - 1) === false', 'str.endsWith("!", 1) === false'],
},
{
path: "test/built-ins/String/prototype/endsWith/searchstring-not-found-without-position.js",
code: `const text = "The future is cool!"; return [text.endsWith("is Flash!"), text.endsWith("IS COOL!"), text.endsWith("The future")]`,
expected: [false, false, false],
labels: ['str.endsWith("is Flash!") === false', "endsWith is case sensitive", 'str.endsWith("The future") === false'],
},
{
path: "test/built-ins/String/prototype/endsWith/return-false-if-search-start-is-less-than-zero.js",
code: `return ["web".endsWith("w", 0), "Bob".endsWith(" Bob")]`,
expected: [false, false],
labels: ['"web".endsWith("w", 0) returns false', '"Bob".endsWith(" Bob") returns false'],
},
{
path: "test/built-ins/String/prototype/endsWith/return-true-if-searchstring-is-empty.js",
code: `const text = "The future is cool!"; return [
text.endsWith(""), text.endsWith("", text.length), text.endsWith("", Infinity),
text.endsWith("", -1), text.endsWith("", -Infinity),
]`,
expected: [true, true, true, true, true],
labels: [
'str.endsWith("") returns true', 'str.endsWith("", str.length) returns true', 'str.endsWith("", Infinity) returns true',
'str.endsWith("", -1) returns true', 'str.endsWith("", -Infinity) returns true',
],
},
{
path: "test/built-ins/String/prototype/endsWith/coerced-values-of-position.js",
code: `const text = "The future is cool!"; return [
text.endsWith("", NaN), text.endsWith("", undefined), text.endsWith("The future", 10.4),
]`,
expected: [true, true, true],
labels: ["NaN coerced to 0", "undefined coerced to 0", "10.4 coerced to 10"],
},
{
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T1.js",
code: `return ["abcd".indexOf("abcdab")]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab")===-1'],
},
{
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T2.js",
code: `return ["abcd".indexOf("abcdab", 0)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",0)===-1'],
},
{
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T3.js",
code: `return ["abcd".indexOf("abcdab", 99)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",99)===-1'],
},
{
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T4.js",
code: `return ["abcd".indexOf("abcdab", NaN)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",NaN)===-1'],
},
{
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T1.js",
code: `return ["$$abcdabcd".indexOf("ab", NaN)]`, expected: [2], labels: ['#1: "$$abcdabcd".indexOf("ab",NaN)===2'],
},
{
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T3.js",
code: `return ["$$abcdabcd".indexOf("ab", -Infinity)]`, expected: [2], labels: ['#1: "$$abcdabcd".indexOf("ab", function(){return -Infinity;}())===2'],
},
{
path: "test/built-ins/String/prototype/indexOf/position-tointeger.js",
code: `return [
"aaaa".indexOf("aa", 0), "aaaa".indexOf("aa", 1), "aaaa".indexOf("aa", -0.9),
"aaaa".indexOf("aa", 0.9), "aaaa".indexOf("aa", 1.9), "aaaa".indexOf("aa", NaN),
"aaaa".indexOf("aa", Infinity), "aaaa".indexOf("aa", undefined),
"aaaa".indexOf("aa", 2), "aaaa".indexOf("aa", 2.9),
]`,
expected: [0, 1, 0, 0, 1, 0, -1, 0, 2, 2],
labels: [
"position 0", "position 1", "ToInteger: truncate towards 0 (-0.9)", "ToInteger: truncate towards 0 (0.9)",
"ToInteger: truncate towards 0 (1.9)", "ToInteger: NaN => 0", "position Infinity",
"ToInteger: undefined => NaN => 0", "position 2", "ToInteger: truncate towards 0 (2.9)",
],
},
{
path: "test/built-ins/String/prototype/indexOf/searchstring-tostring.js",
code: `return ["foo".indexOf(""), "__foo__".indexOf("foo")]`,
expected: [0, 2], labels: ['"foo".indexOf("")', '"__foo__".indexOf("foo")'],
},
{
path: "test/built-ins/String/prototype/lastIndexOf/not-a-substring.js",
code: `return ["abc".lastIndexOf("d")]`,
expected: [-1],
labels: ["String.prototype.lastIndexOf returns -1 when searchString is shorter than this and searchString is not a substring of this."],
},
] as const
describe("Test262-adapted String search and extraction behavior", () => {
for (const item of cases) {
test(item.path, async () => {
const actual = await value(item.code)
if (!Array.isArray(actual)) throw new Error(`expected assertion values for ${item.path}`)
expect(actual.length, "adapted assertion count").toBe(item.expected.length)
item.expected.forEach((expected, index) => expect(actual[index], item.labels[index]!).toEqual(expected))
})
}
})

View file

@ -0,0 +1,77 @@
# Test262 Array Coverage
The Array tests adapt Test262 at revision `250f204f23a9249ff204be2baec29600faae7b75`. They cover CodeMode's 35
exposed instance methods and three static methods using actual arrays, accepted argument types, deterministic behavior,
and CodeMode's materialized collection conventions. Each executable case names its exact upstream source path.
`LICENSE.test262` contains the upstream BSD terms.
This is coverage of CodeMode's bounded Array surface, not a claim of ECMAScript or Test262 conformance. One upstream
file may contain both adapted and inapplicable assertions, so a cited source means only that the represented assertions
were adapted.
## Inventory
The 38 relevant upstream API directories contain 2,837 files. The executable suite adapts assertions from 83 distinct
sources.
| API | Upstream files | Adapted sources |
| ------------------------------- | -------------: | --------------: |
| `Array.prototype.map` | 216 | 3 |
| `Array.prototype.filter` | 242 | 3 |
| `Array.prototype.find` | 23 | 4 |
| `Array.prototype.findIndex` | 23 | 3 |
| `Array.prototype.findLast` | 24 | 3 |
| `Array.prototype.findLastIndex` | 24 | 3 |
| `Array.prototype.some` | 219 | 2 |
| `Array.prototype.every` | 218 | 2 |
| `Array.prototype.includes` | 30 | 2 |
| `Array.prototype.join` | 23 | 2 |
| `Array.prototype.reduce` | 260 | 3 |
| `Array.prototype.reduceRight` | 260 | 3 |
| `Array.prototype.flatMap` | 24 | 2 |
| `Array.prototype.forEach` | 190 | 2 |
| `Array.prototype.sort` | 54 | 3 |
| `Array.prototype.toSorted` | 21 | 4 |
| `Array.prototype.slice` | 71 | 1 |
| `Array.prototype.concat` | 69 | 3 |
| `Array.prototype.indexOf` | 201 | 2 |
| `Array.prototype.lastIndexOf` | 198 | 2 |
| `Array.prototype.at` | 13 | 3 |
| `Array.prototype.flat` | 19 | 2 |
| `Array.prototype.reverse` | 18 | 1 |
| `Array.prototype.toReversed` | 17 | 2 |
| `Array.prototype.with` | 21 | 2 |
| `Array.prototype.push` | 24 | 1 |
| `Array.prototype.pop` | 23 | 1 |
| `Array.prototype.shift` | 20 | 1 |
| `Array.prototype.unshift` | 22 | 1 |
| `Array.prototype.splice` | 81 | 3 |
| `Array.prototype.fill` | 22 | 3 |
| `Array.prototype.copyWithin` | 39 | 2 |
| `Array.prototype.keys` | 12 | 1 |
| `Array.prototype.values` | 12 | 1 |
| `Array.prototype.entries` | 12 | 1 |
| `Array.from` | 47 | 3 |
| `Array.isArray` | 29 | 2 |
| `Array.of` | 16 | 1 |
## Exclusions
Assertions are not adapted when they test behavior outside CodeMode's documented Array surface:
- Function metadata, property descriptors, constructibility, prototype mutation, species constructors, or cross-realm
identity.
- Generic receivers, detached methods, `.call`, `.apply`, boxed values, custom coercion objects, Symbols, BigInts,
proxies, accessors, frozen arrays, typed arrays, or ArrayBuffers.
- `Array.from` mappers, custom iterables, constructor substitution, and iterator-closing behavior.
- Native iterator identity, `.next()`, completion records, or live iterator mutation. CodeMode deliberately materializes
`keys`, `values`, and `entries` as arrays.
- Sparse-array assertions that depend on literal elisions or inherited indexed properties. CodeMode's confined data
model does not preserve those prototype and hole semantics at every boundary.
- Argument coercions outside the accepted schema-like surface. Numeric positions must be numbers and `join` separators
must be strings.
- Exact native error brands where CodeMode exposes a safe runtime error instead.
- Async/effectful callbacks, circular-data rejection, sandbox-value identity, diagnostics, and host-boundary behavior.
Those remain covered by CodeMode-specific tests.
Handwritten tests remain where they specify CodeMode behavior rather than ordinary ECMAScript Array semantics.

View file

@ -0,0 +1,69 @@
# Test262 String Coverage
The String tests adapt Test262 at revision `250f204f23a9249ff204be2baec29600faae7b75`. They cover CodeMode's 32
exposed instance methods and two static methods using primitive receivers, accepted argument types, and deterministic
behavior. Each executable case names its exact upstream source path. `LICENSE.test262` contains the upstream BSD terms.
This is coverage of CodeMode's bounded String surface, not a claim of ECMAScript or Test262 conformance. One upstream
file may contain both adapted and inapplicable assertions, so a cited source means only that the represented assertions
were adapted.
## Inventory
The relevant upstream directories contain 1,048 files: 1,009 core built-in files, 29 Annex B files for exposed methods,
and 10 Intl `localeCompare` files. The executable suite adapts assertions from 298 distinct sources.
| API | Upstream files | Adapted sources |
| --- | ---: | ---: |
| `String.fromCharCode` | 17 | 6 |
| `String.fromCodePoint` | 11 | 4 |
| `String.prototype.at` | 11 | 5 |
| `String.prototype.charAt` | 30 | 9 |
| `String.prototype.charCodeAt` | 25 | 4 |
| `String.prototype.codePointAt` | 16 | 6 |
| `String.prototype.concat` | 22 | 1 |
| `String.prototype.endsWith` | 27 | 13 |
| `String.prototype.includes` | 27 | 12 |
| `String.prototype.indexOf` | 47 | 8 |
| `String.prototype.lastIndexOf` | 25 | 1 |
| `String.prototype.localeCompare` | 23 | 1 |
| `String.prototype.match` | 52 | 9 |
| `String.prototype.matchAll` | 26 | 1 |
| `String.prototype.normalize` | 14 | 3 |
| `String.prototype.padEnd` | 13 | 4 |
| `String.prototype.padStart` | 13 | 4 |
| `String.prototype.repeat` | 16 | 4 |
| `String.prototype.replace` | 56 | 16 |
| `String.prototype.replaceAll` | 46 | 12 |
| `String.prototype.search` | 44 | 10 |
| `String.prototype.slice` | 38 | 11 |
| `String.prototype.split` | 121 | 50 |
| `String.prototype.startsWith` | 21 | 7 |
| `String.prototype.substr` | 15 | 6 |
| `String.prototype.substring` | 46 | 12 |
| `String.prototype.toLowerCase` | 30 | 5 |
| `String.prototype.toString` | 7 | 1 |
| `String.prototype.toUpperCase` | 26 | 3 |
| `String.prototype.trim` | 129 | 66 |
| `String.prototype.trimEnd` | 23 | 2 |
| `String.prototype.trimLeft` | 4 | 0 |
| `String.prototype.trimRight` | 4 | 0 |
| `String.prototype.trimStart` | 23 | 2 |
## Exclusions
Assertions are not adapted when they test behavior outside CodeMode's documented String surface:
- Function metadata, property descriptors, constructibility, prototype mutation, or cross-realm identity.
- The `trimLeft`/`trimRight` Test262 files assert prototype function identity, which CodeMode does not expose. Their
supported call behavior remains covered by CodeMode-specific tests.
- Boxed strings, generic receivers, custom coercion objects, Symbols, BigInts, or argument types CodeMode rejects.
- Symbol-based RegExp dispatch, custom matchers, species constructors, or iterator protocol details. CodeMode materializes
`matchAll` results instead of exposing iterators.
- Locale selection and options. CodeMode deliberately uses the host default locale and ignores those arguments.
- Test262 harness behavior or setup syntax unavailable in the confined interpreter.
- Function-replacer behavior that is covered by CodeMode-specific tests for sequential callbacks, async tool calls,
result coercion, diagnostics, and sandbox boundaries.
- Assertions requiring an exact native error type when CodeMode deliberately exposes only its safe runtime error.
Handwritten tests remain where they specify CodeMode behavior rather than ordinary ECMAScript String semantics.

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-support", "name": "@opencode-ai/console-support",
"version": "1.17.14", "version": "1.17.15",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {

View file

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

View file

@ -1,8 +1,10 @@
{ {
"version": "7", "version": "7",
"dialect": "sqlite", "dialect": "sqlite",
"id": "b0355fd9-bf41-42e3-9dca-76107de27ecd", "id": "beb91b32-23e2-405f-99b8-1e8763db94f8",
"prevIds": ["95328a41-789d-44de-9643-6ac6ecd6b4ec"], "prevIds": [
"00000000-0000-0000-0000-000000000000"
],
"ddl": [ "ddl": [
{ {
"name": "workspace", "name": "workspace",
@ -1016,11 +1018,11 @@
}, },
{ {
"type": "text", "type": "text",
"notNull": false, "notNull": true,
"autoincrement": false, "autoincrement": false,
"default": null, "default": null,
"generated": null, "generated": null,
"name": "prompt", "name": "data",
"entityType": "columns", "entityType": "columns",
"table": "session_input" "table": "session_input"
}, },
@ -1575,9 +1577,13 @@
"table": "session_share" "table": "session_share"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1586,9 +1592,13 @@
"table": "workspace" "table": "workspace"
}, },
{ {
"columns": ["active_account_id"], "columns": [
"active_account_id"
],
"tableTo": "account", "tableTo": "account",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "SET NULL", "onDelete": "SET NULL",
"nameExplicit": false, "nameExplicit": false,
@ -1597,9 +1607,13 @@
"table": "account_state" "table": "account_state"
}, },
{ {
"columns": ["aggregate_id"], "columns": [
"aggregate_id"
],
"tableTo": "event_sequence", "tableTo": "event_sequence",
"columnsTo": ["aggregate_id"], "columnsTo": [
"aggregate_id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1608,9 +1622,13 @@
"table": "event" "table": "event"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1619,9 +1637,13 @@
"table": "permission" "table": "permission"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1630,9 +1652,13 @@
"table": "project_directory" "table": "project_directory"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1641,9 +1667,13 @@
"table": "instruction_checkpoint" "table": "instruction_checkpoint"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1652,9 +1682,13 @@
"table": "instruction_entry" "table": "instruction_entry"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1663,9 +1697,13 @@
"table": "message" "table": "message"
}, },
{ {
"columns": ["message_id"], "columns": [
"message_id"
],
"tableTo": "message", "tableTo": "message",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1674,9 +1712,13 @@
"table": "part" "table": "part"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1685,9 +1727,13 @@
"table": "session_input" "table": "session_input"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1696,9 +1742,13 @@
"table": "session_message" "table": "session_message"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1707,9 +1757,13 @@
"table": "session" "table": "session"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1718,9 +1772,13 @@
"table": "todo" "table": "todo"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session", "tableTo": "session",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@ -1729,140 +1787,184 @@
"table": "session_share" "table": "session_share"
}, },
{ {
"columns": ["email", "url"], "columns": [
"email",
"url"
],
"nameExplicit": false, "nameExplicit": false,
"name": "control_account_pk", "name": "control_account_pk",
"entityType": "pks", "entityType": "pks",
"table": "control_account" "table": "control_account"
}, },
{ {
"columns": ["project_id", "directory"], "columns": [
"project_id",
"directory"
],
"nameExplicit": false, "nameExplicit": false,
"name": "project_directory_pk", "name": "project_directory_pk",
"entityType": "pks", "entityType": "pks",
"table": "project_directory" "table": "project_directory"
}, },
{ {
"columns": ["session_id", "key"], "columns": [
"session_id",
"key"
],
"nameExplicit": false, "nameExplicit": false,
"name": "instruction_entry_pk", "name": "instruction_entry_pk",
"entityType": "pks", "entityType": "pks",
"table": "instruction_entry" "table": "instruction_entry"
}, },
{ {
"columns": ["session_id", "position"], "columns": [
"session_id",
"position"
],
"nameExplicit": false, "nameExplicit": false,
"name": "todo_pk", "name": "todo_pk",
"entityType": "pks", "entityType": "pks",
"table": "todo" "table": "todo"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "workspace_pk", "name": "workspace_pk",
"table": "workspace", "table": "workspace",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["name"], "columns": [
"name"
],
"nameExplicit": false, "nameExplicit": false,
"name": "data_migration_pk", "name": "data_migration_pk",
"table": "data_migration", "table": "data_migration",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "account_state_pk", "name": "account_state_pk",
"table": "account_state", "table": "account_state",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "account_pk", "name": "account_pk",
"table": "account", "table": "account",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "credential_pk", "name": "credential_pk",
"table": "credential", "table": "credential",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["aggregate_id"], "columns": [
"aggregate_id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "event_sequence_pk", "name": "event_sequence_pk",
"table": "event_sequence", "table": "event_sequence",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "event_pk", "name": "event_pk",
"table": "event", "table": "event",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "permission_pk", "name": "permission_pk",
"table": "permission", "table": "permission",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "project_pk", "name": "project_pk",
"table": "project", "table": "project",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "instruction_checkpoint_pk", "name": "instruction_checkpoint_pk",
"table": "instruction_checkpoint", "table": "instruction_checkpoint",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "message_pk", "name": "message_pk",
"table": "message", "table": "message",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "part_pk", "name": "part_pk",
"table": "part", "table": "part",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_input_pk", "name": "session_input_pk",
"table": "session_input", "table": "session_input",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_message_pk", "name": "session_message_pk",
"table": "session_message", "table": "session_message",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_pk", "name": "session_pk",
"table": "session", "table": "session",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_share_pk", "name": "session_share_pk",
"table": "session_share", "table": "session_share",
@ -1994,10 +2096,6 @@
"value": "promoted_seq", "value": "promoted_seq",
"isExpression": false "isExpression": false
}, },
{
"value": "type",
"isExpression": false
},
{ {
"value": "delivery", "value": "delivery",
"isExpression": false "isExpression": false
@ -2010,7 +2108,7 @@
"isUnique": false, "isUnique": false,
"where": null, "where": null,
"origin": "manual", "origin": "manual",
"name": "session_input_session_pending_type_delivery_seq_idx", "name": "session_input_session_pending_delivery_seq_idx",
"entityType": "indexes", "entityType": "indexes",
"table": "session_input" "table": "session_input"
}, },
@ -2198,4 +2296,4 @@
} }
], ],
"renames": [] "renames": []
} }

View file

@ -119,7 +119,17 @@ export class Directory extends Schema.Class<Directory>("Config.Directory")({
path: AbsolutePath, path: AbsolutePath,
}) {} }) {}
export type Entry = Document | Directory export class AgentsDirectory extends Schema.Class<AgentsDirectory>("Config.AgentsDirectory")({
type: Schema.Literal("agents"),
path: AbsolutePath,
}) {}
export class ClaudeDirectory extends Schema.Class<ClaudeDirectory>("Config.ClaudeDirectory")({
type: Schema.Literal("claude"),
path: AbsolutePath,
}) {}
export type Entry = Document | Directory | AgentsDirectory | ClaudeDirectory
export function latest<K extends keyof Info>(entries: readonly Entry[], key: K): Info[K] | undefined { export function latest<K extends keyof Info>(entries: readonly Entry[], key: K): Info[K] | undefined {
return entries return entries
@ -176,16 +186,37 @@ const layer = Layer.effect(
const discover = Effect.fn("Config.discover")(function* () { const discover = Effect.fn("Config.discover")(function* () {
const globalDirectory = AbsolutePath.make(global.config) const globalDirectory = AbsolutePath.make(global.config)
const globalAgentsDirectory = AbsolutePath.make(path.join(global.home, ".agents"))
const globalClaudeDirectory = AbsolutePath.make(path.join(global.home, ".claude"))
const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config) const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config)
const discovered = locationIsGlobal const discovered = locationIsGlobal
? [] ? []
: yield* fs : yield* fs
.up({ .up({
targets: [".opencode", ...names.toReversed()], targets: [".opencode", ".claude", ".agents", ...names.toReversed()],
start: location.directory, start: location.directory,
stop: location.project.directory, stop: location.project.directory,
}) })
.pipe(Effect.orDie) .pipe(Effect.orDie)
// We load certain files from a few other folders in the ecosystem
const claude = [
...((yield* fs.isDir(globalClaudeDirectory))
? [new ClaudeDirectory({ type: "claude", path: globalClaudeDirectory })]
: []),
...discovered
.filter((item) => path.basename(item) === ".claude")
.map((directory) => new ClaudeDirectory({ type: "claude", path: AbsolutePath.make(directory) })),
]
const agents = [
...((yield* fs.isDir(globalAgentsDirectory))
? [new AgentsDirectory({ type: "agents", path: globalAgentsDirectory })]
: []),
...discovered
.filter((item) => path.basename(item) === ".agents")
.map((directory) => new AgentsDirectory({ type: "agents", path: AbsolutePath.make(directory) })),
]
const directories = [ const directories = [
globalDirectory, globalDirectory,
...discovered ...discovered
@ -193,15 +224,18 @@ const layer = Layer.effect(
.toReversed() .toReversed()
.map((directory) => AbsolutePath.make(directory)), .map((directory) => AbsolutePath.make(directory)),
] ]
const directPaths = discovered.filter((item) => path.basename(item) !== ".opencode").toReversed() const directPaths = discovered
.filter((item) => ![".agents", ".claude", ".opencode"].includes(path.basename(item)))
.toReversed()
const direct = yield* Effect.forEach(directPaths, loadFile).pipe( const direct = yield* Effect.forEach(directPaths, loadFile).pipe(
Effect.orDie, Effect.orDie,
Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)), Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),
) )
const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie) const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie)
return { return {
entries: [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()], entries: [...claude, ...agents, ...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()],
directories, directories: [...directories, ...claude.map((entry) => entry.path), ...agents.map((entry) => entry.path)],
files: directPaths, files: directPaths,
} }
}) })

View file

@ -27,12 +27,10 @@ export const Selection = Schema.Union([Short, Explicit])
.annotate({ identifier: "Config.ModelSelection" }) .annotate({ identifier: "Config.ModelSelection" })
function parse(input: string): Selection { function parse(input: string): Selection {
const providerEnd = input.indexOf("/") const ref = Model.Ref.parse(input)
const variantStart = input.lastIndexOf("#")
const hasVariant = variantStart > providerEnd
return { return {
providerID: Provider.ID.make(input.slice(0, providerEnd)), providerID: ref.providerID,
model: Model.ID.make(input.slice(providerEnd + 1, hasVariant ? variantStart : undefined)), model: ref.id,
...(hasVariant ? { variant: Model.VariantID.make(input.slice(variantStart + 1)) } : {}), ...(ref.variant ? { variant: ref.variant } : {}),
} }
} }

View file

@ -10,6 +10,11 @@ import { ConfigMarkdown } from "../markdown"
import { FSUtil } from "../../fs-util" import { FSUtil } from "../../fs-util"
import { ConfigAgentV1 } from "../../v1/config/agent" import { ConfigAgentV1 } from "../../v1/config/agent"
import { ConfigMigrateV1 } from "../../v1/config/migrate" import { ConfigMigrateV1 } from "../../v1/config/migrate"
import { Global } from "../../global"
import { PermissionV2 } from "../../permission"
import type { LocationMutation } from "../../location-mutation"
import type { ReadTool } from "../../tool/read"
import type { EditTool } from "../../tool/edit"
const legacySources = [ const legacySources = [
{ pattern: "{agent,agents}/**/*.md", primary: false }, { pattern: "{agent,agents}/**/*.md", primary: false },
@ -18,6 +23,11 @@ const legacySources = [
const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info) const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info) const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info)
const decodeConfig = Schema.decodeUnknownOption(Config.Info) const decodeConfig = Schema.decodeUnknownOption(Config.Info)
type PathAction =
| LocationMutation.ExternalDirectoryAuthorization["action"]
| typeof ReadTool.name
| typeof EditTool.name
const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[]
const agentKeys = new Set([ const agentKeys = new Set([
"model", "model",
"variant", "variant",
@ -37,9 +47,11 @@ export const Plugin = define({
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
const config = yield* Config.Service const config = yield* Config.Service
const fs = yield* FSUtil.Service const fs = yield* FSUtil.Service
const global = yield* Global.Service
const load = Effect.fn("ConfigAgentPlugin.load")(function* () { const load = Effect.fn("ConfigAgentPlugin.load")(function* () {
return yield* Effect.forEach(yield* config.entries(), (entry) => { return yield* Effect.forEach(yield* config.entries(), (entry) => {
if (entry.type === "document") return Effect.succeed([entry]) if (entry.type === "document") return Effect.succeed([entry])
if (entry.type !== "directory") return Effect.succeed([])
return Effect.gen(function* () { return Effect.gen(function* () {
const files = yield* discover(fs, entry.path) const files = yield* discover(fs, entry.path)
return yield* Effect.forEach(files, (file) => return yield* Effect.forEach(files, (file) =>
@ -57,11 +69,14 @@ export const Plugin = define({
}) })
const loaded = { documents: yield* load() } const loaded = { documents: yield* load() }
yield* ctx.agent.transform((draft) => { yield* ctx.agent.transform((draft) => {
const global = loaded.documents.flatMap((document) => document.info.permissions ?? []) const permissions = expandPermissions(
loaded.documents.flatMap((document) => document.info.permissions ?? []),
global.home,
)
const configuredDefault = Config.latest(loaded.documents, "default_agent") const configuredDefault = Config.latest(loaded.documents, "default_agent")
if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault)) if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
for (const current of draft.list()) { for (const current of draft.list()) {
draft.update(current.id, (agent) => agent.permissions.push(...global)) draft.update(current.id, (agent) => agent.permissions.push(...permissions))
} }
for (const document of loaded.documents) { for (const document of loaded.documents) {
@ -74,7 +89,7 @@ export const Plugin = define({
const exists = draft.get(agentID) !== undefined const exists = draft.get(agentID) !== undefined
draft.update(agentID, (agent) => { draft.update(agentID, (agent) => {
if (!exists) agent.permissions.push(...global) if (!exists) agent.permissions.push(...permissions)
if (item.model !== undefined) if (item.model !== undefined)
agent.model = { agent.model = {
id: item.model.model, id: item.model.model,
@ -91,7 +106,9 @@ export const Plugin = define({
if (item.hidden !== undefined) agent.hidden = item.hidden if (item.hidden !== undefined) agent.hidden = item.hidden
if (item.color !== undefined) agent.color = item.color if (item.color !== undefined) agent.color = item.color
if (item.steps !== undefined) agent.steps = item.steps if (item.steps !== undefined) agent.steps = item.steps
if (item.permissions !== undefined) agent.permissions.push(...item.permissions) if (item.permissions !== undefined) {
agent.permissions.push(...expandPermissions(item.permissions, global.home))
}
}) })
} }
} }
@ -109,6 +126,27 @@ export const Plugin = define({
}), }),
}) })
function expandPermissions(rules: PermissionV2.Ruleset, home: string): PermissionV2.Ruleset {
// Expand only resources tools resolve as filesystem paths. Bash resources are raw shell text:
// rewriting `$HOME/private/**` would miss `$HOME/private/key`, and safe expansion needs shell-aware parsing.
return rules.map((rule) =>
isPathAction(rule.action) ? { ...rule, resource: expandHome(rule.resource, home) } : rule,
)
}
function isPathAction(action: string): action is PathAction {
return pathActions.some((item) => item === action)
}
function expandHome(resource: string, home: string) {
if (resource.startsWith("~/")) return home + resource.slice(1)
if (resource === "~") return home
if (resource === "$HOME") return home
if (resource.startsWith("$HOME/")) return home + resource.slice(5)
if (resource.startsWith("$HOME\\")) return home + resource.slice(5)
return resource
}
function discover(fs: FSUtil.Interface, directory: string) { function discover(fs: FSUtil.Interface, directory: string) {
return Effect.forEach(legacySources, (source) => return Effect.forEach(legacySources, (source) =>
fs fs

View file

@ -19,6 +19,7 @@ export const Plugin = define({
const load = Effect.fn("ConfigCommandPlugin.load")(function* () { const load = Effect.fn("ConfigCommandPlugin.load")(function* () {
return yield* Effect.forEach(yield* config.entries(), (entry) => { return yield* Effect.forEach(yield* config.entries(), (entry) => {
if (entry.type === "document") return Effect.succeed([{ commands: entry.info.commands }]) if (entry.type === "document") return Effect.succeed([{ commands: entry.info.commands }])
if (entry.type !== "directory") return Effect.succeed([])
return loadDirectory(fs, entry.path).pipe( return loadDirectory(fs, entry.path).pipe(
Effect.map((commands) => [ Effect.map((commands) => [
{ commands: Object.fromEntries(commands.map((command) => [command.name, command.info])) }, { commands: Object.fromEntries(commands.map((command) => [command.name, command.info])) },

Some files were not shown because too many files have changed in this diff Show more