mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-18 05:13:44 +00:00
chore(observability): merge v2
This commit is contained in:
commit
f4e661f874
276 changed files with 16916 additions and 3670 deletions
61
bun.lock
61
bun.lock
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
"@dnd-kit/dom": "0.5.0",
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"bin": {
|
||||
"opencode2": "./bin/opencode2.cjs",
|
||||
},
|
||||
|
|
@ -114,10 +114,12 @@
|
|||
"effect": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "catalog:",
|
||||
"semver": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
"strip-ansi": "7.1.2",
|
||||
"uqr": "0.1.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
|
|
@ -151,7 +153,7 @@
|
|||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
|
|
@ -165,7 +167,7 @@
|
|||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
|
@ -201,7 +203,7 @@
|
|||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
|
|
@ -228,7 +230,7 @@
|
|||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
|
|
@ -250,7 +252,7 @@
|
|||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
|
@ -274,7 +276,7 @@
|
|||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
|
|
@ -294,7 +296,7 @@
|
|||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
|
|
@ -390,7 +392,7 @@
|
|||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"effect": "catalog:",
|
||||
|
|
@ -450,7 +452,7 @@
|
|||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
|
|
@ -464,7 +466,7 @@
|
|||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
|
|
@ -476,7 +478,7 @@
|
|||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
|
@ -508,7 +510,7 @@
|
|||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
|
|
@ -524,7 +526,7 @@
|
|||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
},
|
||||
|
|
@ -556,7 +558,7 @@
|
|||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
|
|
@ -575,7 +577,7 @@
|
|||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
|
|
@ -708,7 +710,7 @@
|
|||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
|
|
@ -795,7 +797,7 @@
|
|||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
|
|
@ -810,7 +812,7 @@
|
|||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
|
@ -827,7 +829,7 @@
|
|||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
|
@ -888,7 +890,7 @@
|
|||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
|
|
@ -901,7 +903,7 @@
|
|||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@opencode-ai/stats-core": "workspace:*",
|
||||
|
|
@ -934,7 +936,7 @@
|
|||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
|
|
@ -953,7 +955,7 @@
|
|||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
|
|
@ -994,7 +996,7 @@
|
|||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
|
@ -1015,6 +1017,7 @@
|
|||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
"strip-ansi": "7.1.2",
|
||||
"uqr": "0.1.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "catalog:",
|
||||
|
|
@ -1024,7 +1027,7 @@
|
|||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
|
|
@ -1075,7 +1078,7 @@
|
|||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@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=="],
|
||||
|
||||
"uqr": ["uqr@0.1.3", "", {}, "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA=="],
|
||||
|
||||
"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=="],
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"prepare": "husky",
|
||||
"random": "echo 'Random script'",
|
||||
"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"
|
||||
},
|
||||
"workspaces": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Font } from "@opencode-ai/ui/font"
|
|||
import { Splash } from "@opencode-ai/ui/logo"
|
||||
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { type BaseRouterProps, Navigate, Route, Router, 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 { Effect } from "effect"
|
||||
import {
|
||||
|
|
@ -32,7 +32,7 @@ import { CommandProvider, useCommand, type CommandOption } from "@/context/comma
|
|||
import { CommentsProvider } from "@/context/comments"
|
||||
import { FileProvider } from "@/context/file"
|
||||
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 { HighlightsProvider } from "@/context/highlights"
|
||||
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
||||
|
|
@ -52,9 +52,10 @@ import LegacyLayout from "@/pages/layout"
|
|||
import NewLayout from "@/pages/layout-new"
|
||||
import { ErrorPage } from "./pages/error"
|
||||
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"
|
||||
|
||||
const NewSession = lazy(() => import("@/pages/new-session"))
|
||||
|
|
@ -88,10 +89,14 @@ const SessionRoute = () => {
|
|||
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 global = useGlobal()
|
||||
const conn = createMemo(() => {
|
||||
|
|
@ -105,14 +110,47 @@ const TargetSessionRoute = () => {
|
|||
// re-resolves reactively instead); both rely on this key for server changes.
|
||||
<Show when={requireServerKey(params.serverKey)} keyed>
|
||||
<ServerSDKProvider server={conn}>
|
||||
<ServerSyncProvider server={conn}>
|
||||
<TargetSessionRouteContent />
|
||||
</ServerSyncProvider>
|
||||
<ServerSyncProvider server={conn}>{props.children}</ServerSyncProvider>
|
||||
</ServerSDKProvider>
|
||||
</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
|
||||
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
||||
// Notification/Models + the visual Layout) for that server.
|
||||
|
|
@ -186,7 +224,7 @@ declare global {
|
|||
deepLinks?: string[]
|
||||
}
|
||||
api?: {
|
||||
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
|
||||
setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise<void>
|
||||
exportDebugLogs?: () => Promise<string>
|
||||
}
|
||||
}
|
||||
|
|
@ -320,8 +358,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
|||
<MetaProvider>
|
||||
<Font />
|
||||
<ThemeProvider
|
||||
onThemeApplied={(_, mode) => {
|
||||
void window.api?.setTitlebar?.({ mode })
|
||||
onThemeApplied={(_, mode, scheme) => {
|
||||
void window.api?.setTitlebar?.({ mode, scheme })
|
||||
}}
|
||||
>
|
||||
<LanguageProvider locale={props.locale}>
|
||||
|
|
@ -542,7 +580,14 @@ function Routes(props: { serverScoped?: JSX.Element }) {
|
|||
<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="/" component={() => <Navigate href="session" />} />
|
||||
<Route path="/session/:id?" component={SessionRoute} />
|
||||
|
|
@ -550,15 +595,15 @@ function Routes(props: { serverScoped?: JSX.Element }) {
|
|||
</Route>
|
||||
<Show when={settings.general.newLayoutDesigns()}>
|
||||
<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>
|
||||
<Route path="/new-session" component={DraftRoute} />
|
||||
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function LegacyTargetSessionRoute() {
|
||||
function NewLayoutLegacySessionRedirect() {
|
||||
const server = useServer()
|
||||
const tabs = useTabs()
|
||||
const params = useParams<{ id: string }>()
|
||||
|
|
|
|||
325
packages/app/src/components/command-palette.ts
Normal file
325
packages/app/src/components/command-palette.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
220
packages/app/src/components/dialog-command-palette-v2.css
Normal file
220
packages/app/src/components/dialog-command-palette-v2.css
Normal 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%;
|
||||
}
|
||||
}
|
||||
221
packages/app/src/components/dialog-command-palette-v2.tsx
Normal file
221
packages/app/src/components/dialog-command-palette-v2.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,329 +1,84 @@
|
|||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||
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 { useNavigate } from "@solidjs/router"
|
||||
import { createMemo, createSignal, lazy, Match, onCleanup, Show, Switch } from "solid-js"
|
||||
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
||||
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 { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js"
|
||||
import { formatKeybind } from "@/context/command"
|
||||
import { useServerSDK } from "@/context/server-sdk"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||
import { createSessionTabs } from "@/pages/session/helpers"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { getRelativeTime } from "@/utils/time"
|
||||
import {
|
||||
createCommandPaletteFileEntry,
|
||||
createCommandPaletteFileOpener,
|
||||
createCommandPaletteModel,
|
||||
uniqueCommandPaletteEntries,
|
||||
type CommandPaletteEntry,
|
||||
} from "./command-palette"
|
||||
|
||||
const DialogSelectFileV2 = lazy(() =>
|
||||
import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
|
||||
)
|
||||
|
||||
type EntryType = "command" | "file" | "session"
|
||||
|
||||
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
|
||||
}
|
||||
const DialogCommandPaletteV2 = lazy(() =>
|
||||
import("./dialog-command-palette-v2").then((module) => ({ default: module.DialogCommandPaletteV2 })),
|
||||
)
|
||||
|
||||
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 }) {
|
||||
const command = useCommand()
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
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 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) ?? "")
|
||||
const project = createMemo(() => {
|
||||
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}`
|
||||
if (!filesOnly() && settings.general.newLayoutDesigns()) {
|
||||
return <DialogCommandPaletteV2 onOpenFile={props.onOpenFile} />
|
||||
}
|
||||
|
||||
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 query = text.trim()
|
||||
setGrouped(query.length > 0)
|
||||
|
||||
if (!query && filesOnly()) {
|
||||
const loaded = file.tree.state("")?.loaded
|
||||
const pending = loaded ? Promise.resolve() : file.tree.list("")
|
||||
const next = uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
|
||||
if (!query && props.filesOnly()) {
|
||||
const loaded = palette.file.tree.state("")?.loaded
|
||||
const pending = loaded ? Promise.resolve() : palette.file.tree.list("")
|
||||
const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
|
||||
|
||||
if (loaded || next.length > 0) {
|
||||
void pending
|
||||
|
|
@ -331,79 +86,24 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||
}
|
||||
|
||||
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()) {
|
||||
const files = await file.searchFiles(query)
|
||||
const category = language.t("palette.group.files")
|
||||
return files.map((path) => createFileEntry(path, category))
|
||||
if (props.filesOnly()) {
|
||||
const files = await palette.file.searchFiles(query)
|
||||
const category = palette.language.t("palette.group.files")
|
||||
return files.map((path) => createCommandPaletteFileEntry(path, category))
|
||||
}
|
||||
|
||||
const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))])
|
||||
const category = language.t("palette.group.files")
|
||||
const entries = files.map((path) => createFileEntry(path, category))
|
||||
return [...commandEntries.list(), ...nextSessions, ...entries]
|
||||
}
|
||||
|
||||
const handleMove = (item: Entry | undefined) => {
|
||||
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)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
const [files, nextSessions] = await Promise.all([
|
||||
palette.file.searchFiles(query),
|
||||
Promise.resolve(palette.sessions(query)),
|
||||
])
|
||||
const category = palette.language.t("palette.group.files")
|
||||
const entries = files.map((path) => createCommandPaletteFileEntry(path, category))
|
||||
return [...palette.commandEntries(), ...nextSessions, ...entries]
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -411,21 +111,21 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||
<List
|
||||
class="px-3"
|
||||
search={{
|
||||
placeholder: filesOnly()
|
||||
? language.t("session.header.searchFiles")
|
||||
: language.t("palette.search.placeholder"),
|
||||
placeholder: props.filesOnly()
|
||||
? palette.language.t("session.header.searchFiles")
|
||||
: palette.language.t("palette.search.placeholder"),
|
||||
autofocus: true,
|
||||
hideIcon: true,
|
||||
}}
|
||||
emptyMessage={language.t("palette.empty")}
|
||||
loadingMessage={language.t("common.loading")}
|
||||
emptyMessage={palette.language.t("palette.empty")}
|
||||
loadingMessage={palette.language.t("common.loading")}
|
||||
items={items}
|
||||
key={(item) => item.id}
|
||||
filterKeys={["title", "description", "category"]}
|
||||
skipFilter={(item) => item.type === "file"}
|
||||
groupBy={grouped() ? (item) => item.category : () => ""}
|
||||
onMove={handleMove}
|
||||
onSelect={handleSelect}
|
||||
onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)}
|
||||
onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)}
|
||||
>
|
||||
{(item) => (
|
||||
<Switch
|
||||
|
|
@ -452,7 +152,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||
</Show>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</Match>
|
||||
|
|
@ -479,7 +179,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||
</div>
|
||||
<Show when={item.updated}>
|
||||
<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>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-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 { useLanguage } from "@/context/language"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
|
|
@ -92,6 +93,7 @@ const ModelList: Component<{
|
|||
class="w-full"
|
||||
placement="right-start"
|
||||
gutter={12}
|
||||
openDelay={0}
|
||||
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
|
||||
>
|
||||
{node}
|
||||
|
|
@ -446,26 +448,41 @@ export function ModelSelectorPopoverV2(props: {
|
|||
<MenuV2.RadioGroup value={current()}>
|
||||
<For each={group.items}>
|
||||
{(item) => (
|
||||
<MenuV2.RadioItem
|
||||
value={modelKey(item)}
|
||||
data-option-key={modelKey(item)}
|
||||
data-selected-model={current() === modelKey(item) ? true : undefined}
|
||||
class="scroll-my-6"
|
||||
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
|
||||
onMouseEnter={() => {
|
||||
setStore("active", modelKey(item))
|
||||
setTimeout(() => searchRef?.focus())
|
||||
}}
|
||||
onSelect={() => selectModel(item)}
|
||||
<TooltipV2
|
||||
class="w-full"
|
||||
placement="right-start"
|
||||
gutter={6}
|
||||
openDelay={0}
|
||||
value={
|
||||
<ModelTooltip
|
||||
model={item}
|
||||
latest={item.latest}
|
||||
free={isFree(item.provider.id, item.cost)}
|
||||
v2
|
||||
/>
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<MenuV2.RadioItem
|
||||
value={modelKey(item)}
|
||||
data-option-key={modelKey(item)}
|
||||
data-selected-model={current() === modelKey(item) ? true : undefined}
|
||||
class="scroll-my-6 w-full"
|
||||
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
|
||||
onMouseEnter={() => {
|
||||
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>
|
||||
</MenuV2.RadioGroup>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
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 sourceName = (model: ModelInfo) => {
|
||||
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(", ")})` : ""
|
||||
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 = () => {
|
||||
if (props.model.capabilities) {
|
||||
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")
|
||||
}
|
||||
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 (
|
||||
<div class="flex flex-col gap-1 py-1">
|
||||
|
|
|
|||
|
|
@ -440,11 +440,6 @@ export const SettingsGeneral: Component = () => {
|
|||
value={(o) => o.value}
|
||||
label={(o) => o.label}
|
||||
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
theme.previewColorScheme(option.value)
|
||||
return () => theme.cancelPreview()
|
||||
}}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
triggerVariant="settings"
|
||||
|
|
@ -471,11 +466,6 @@ export const SettingsGeneral: Component = () => {
|
|||
if (!option) return
|
||||
theme.setTheme(option.id)
|
||||
}}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
theme.previewTheme(option.id)
|
||||
return () => theme.cancelPreview()
|
||||
}}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
triggerVariant="settings"
|
||||
|
|
|
|||
|
|
@ -422,11 +422,6 @@ export const SettingsGeneralV2: Component<{
|
|||
value={(o) => o.value}
|
||||
label={(o) => o.label}
|
||||
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
theme.previewColorScheme(option.value)
|
||||
return () => theme.cancelPreview()
|
||||
}}
|
||||
/>
|
||||
</SettingsRowV2>
|
||||
|
||||
|
|
@ -454,11 +449,6 @@ export const SettingsGeneralV2: Component<{
|
|||
if (!option) return
|
||||
theme.setTheme(option.id)
|
||||
}}
|
||||
onHighlight={(option) => {
|
||||
if (!option) return
|
||||
theme.previewTheme(option.id)
|
||||
return () => theme.cancelPreview()
|
||||
}}
|
||||
/>
|
||||
</SettingsRowV2>
|
||||
|
||||
|
|
|
|||
|
|
@ -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]"
|
||||
>
|
||||
<Show when={props.session()}>
|
||||
{(session) => (
|
||||
<span data-slot="project-avatar-slot">
|
||||
<span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
|
||||
<Show
|
||||
when={props.session()}
|
||||
fallback={
|
||||
<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
|
||||
}
|
||||
>
|
||||
{(session) => (
|
||||
<SessionTabAvatar
|
||||
project={project()}
|
||||
directory={session().directory}
|
||||
sessionId={session().id}
|
||||
server={props.server}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
)}
|
||||
</Show>
|
||||
</span>
|
||||
<span
|
||||
ref={(el) => {
|
||||
titleEl = el
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ function SessionTabSlot(props: {
|
|||
let ref!: HTMLDivElement
|
||||
const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
|
||||
const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
|
||||
const persisted = createMemo(() => tabs.info[props.id])
|
||||
const [loadedSession] = createResource(
|
||||
() => {
|
||||
const ctx = props.serverCtx()
|
||||
|
|
@ -71,8 +72,10 @@ function SessionTabSlot(props: {
|
|||
|
||||
createEffect(() => {
|
||||
const value = session()
|
||||
if (!value) return
|
||||
tabs.rememberSessionInfo(props.tab, value)
|
||||
const current = sdk()
|
||||
if (!value || !current) return
|
||||
if (!current) return
|
||||
createTabPromptState(tabs, props.tab, current.scope, {
|
||||
dir: base64Encode(value.directory),
|
||||
id: value.id,
|
||||
|
|
@ -86,7 +89,7 @@ function SessionTabSlot(props: {
|
|||
data-tab-key={props.id}
|
||||
data-active={props.active()}
|
||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||
classList={{ hidden: !session() && !missingSession() }}
|
||||
classList={{ hidden: !session() && !missingSession() && !persisted()?.title }}
|
||||
>
|
||||
<TabNavItem
|
||||
ref={(el) => {
|
||||
|
|
@ -95,7 +98,7 @@ function SessionTabSlot(props: {
|
|||
href={tabHref(props.tab)}
|
||||
server={props.tab.server}
|
||||
session={session}
|
||||
fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined}
|
||||
fallbackTitle={persisted()?.title ?? (missingSession() ? language.t("session.tab.unknown") : undefined)}
|
||||
onTitleChange={(title) => {
|
||||
const value = session()
|
||||
const ctx = props.serverCtx()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { upsertCommandRegistration } from "./command"
|
||||
import { resolveKeybindOption, upsertCommandRegistration } from "./command"
|
||||
|
||||
describe("upsertCommandRegistration", () => {
|
||||
test("replaces keyed registrations", () => {
|
||||
|
|
@ -23,3 +23,19 @@ describe("upsertCommandRegistration", () => {
|
|||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -82,10 +82,15 @@ export interface CommandOption {
|
|||
suggested?: boolean
|
||||
disabled?: boolean
|
||||
hidden?: boolean
|
||||
when?: (event: KeyboardEvent) => boolean
|
||||
onSelect?: (source?: "palette" | "keybind" | "slash") => 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"
|
||||
|
||||
export type CommandCatalogItem = {
|
||||
|
|
@ -334,7 +339,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
})
|
||||
|
||||
const keymap = createMemo(() => {
|
||||
const map = new Map<string, CommandOption>()
|
||||
const map = new Map<string, CommandOption[]>()
|
||||
for (const option of options()) {
|
||||
if (option.id.startsWith(SUGGESTED_PREFIX)) continue
|
||||
if (option.disabled) continue
|
||||
|
|
@ -344,8 +349,12 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
for (const kb of keybinds) {
|
||||
if (!kb.key) continue
|
||||
const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
|
||||
if (map.has(sig)) continue
|
||||
map.set(sig, option)
|
||||
const existing = map.get(sig)
|
||||
if (existing) {
|
||||
existing.push(option)
|
||||
continue
|
||||
}
|
||||
map.set(sig, [option])
|
||||
}
|
||||
}
|
||||
return map
|
||||
|
|
@ -374,7 +383,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
|
||||
const sig = signatureFromEvent(event)
|
||||
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 isTab = event.key === "Tab"
|
||||
|
||||
|
|
@ -383,17 +392,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
|
||||
if (isPalette) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
showPalette()
|
||||
return
|
||||
}
|
||||
|
||||
if (!option) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
option.onSelect?.("keybind")
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
makeEventListener(document, "keydown", handleKeyDown)
|
||||
makeEventListener(document, "keydown", handleKeyDown, { capture: true })
|
||||
})
|
||||
|
||||
function register(cb: () => CommandOption[]): void
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ export type DraftTab = {
|
|||
|
||||
export type Tab = SessionTab | DraftTab
|
||||
|
||||
export type TabInfo = {
|
||||
title?: string
|
||||
directory?: string
|
||||
}
|
||||
|
||||
type RecentTab = {
|
||||
key?: string
|
||||
}
|
||||
|
|
@ -64,6 +69,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||
createStore<Tab[]>([]),
|
||||
)
|
||||
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 params = useParams()
|
||||
|
|
@ -102,6 +108,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||
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)
|
||||
|
||||
createEffect(() => {
|
||||
|
|
@ -110,11 +125,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||
const next = store.filter((tab) => servers.has(tab.server))
|
||||
if (next.length !== store.length) {
|
||||
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)
|
||||
}
|
||||
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(() => {
|
||||
|
|
@ -150,6 +173,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||
if (nextTab) navigateTab(nextTab)
|
||||
}).finally(() => closing.delete(key))
|
||||
memory.remove(key)
|
||||
removeInfo(key)
|
||||
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)
|
||||
setStore((tabs) => tabs.filter((tab) => tab.server !== 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)
|
||||
for (const draftID of drafts) removeDraftPersisted(draftID)
|
||||
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)
|
||||
})
|
||||
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,
|
||||
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 }
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export const dict = {
|
|||
"theme.scheme.dark": "داكن",
|
||||
"command.sidebar.toggle": "تبديل الشريط الجانبي",
|
||||
"command.project.open": "فتح مشروع",
|
||||
"command.project.previous": "المشروع السابق",
|
||||
"command.project.next": "المشروع التالي",
|
||||
"command.project.index": "التبديل إلى المشروع {{index}}",
|
||||
"command.provider.connect": "اتصال بموفر",
|
||||
"command.server.switch": "تبديل الخادم",
|
||||
"command.settings.open": "فتح الإعدادات",
|
||||
|
|
@ -39,6 +42,7 @@ export const dict = {
|
|||
"command.session.new": "جلسة جديدة",
|
||||
"command.file.open": "فتح ملف",
|
||||
"command.tab.close": "إغلاق علامة التبويب",
|
||||
"command.tab.reopenClosed": "إعادة فتح علامة التبويب المغلقة",
|
||||
"command.context.addSelection": "إضافة التحديد إلى السياق",
|
||||
"command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي",
|
||||
"command.input.focus": "التركيز على حقل الإدخال",
|
||||
|
|
@ -193,6 +197,11 @@ export const dict = {
|
|||
"model.input.video": "فيديو",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "بدون استنتاج",
|
||||
"model.tooltip.context": "حد السياق {{limit}}",
|
||||
|
|
@ -202,6 +211,7 @@ export const dict = {
|
|||
"common.loading": "جارٍ التحميل",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "إلغاء",
|
||||
"common.clear": "مسح",
|
||||
"common.connect": "اتصال",
|
||||
"common.disconnect": "قطع الاتصال",
|
||||
"common.continue": "إرسال",
|
||||
|
|
@ -218,6 +228,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"prompt.mode.shell.exit": "esc للخروج",
|
||||
"session.child.promptDisabled": "لا يمكن إرسال موجّهات إلى جلسات الوكلاء الفرعيين.",
|
||||
"session.child.backToParent": "العودة إلى الجلسة الرئيسية.",
|
||||
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
|
||||
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
|
||||
"prompt.example.3": "إصلاح الاختبارات المعطلة",
|
||||
|
|
@ -314,6 +326,74 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "حذف",
|
||||
"dialog.server.current": "الخادم الحالي",
|
||||
"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.name": "الاسم",
|
||||
"dialog.project.edit.icon": "أيقونة",
|
||||
|
|
@ -370,6 +450,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
"toast.language.title": "لغة",
|
||||
|
|
@ -406,10 +487,12 @@ export const dict = {
|
|||
"toast.update.action.notYet": "ليس الآن",
|
||||
"error.page.title": "حدث خطأ ما",
|
||||
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
|
||||
"error.page.description.localServerStartup": "حدث خطأ أثناء بدء تشغيل الخادم المحلي.",
|
||||
"error.page.details.label": "تفاصيل الخطأ",
|
||||
"error.page.action.restart": "إعادة تشغيل",
|
||||
"error.page.action.report": "الإبلاغ عن الخطأ",
|
||||
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
|
||||
"error.page.action.exportLogs": "تصدير السجلات",
|
||||
"error.page.action.checking": "جارٍ التحقق...",
|
||||
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
|
||||
"error.page.action.updateTo": "تحديث إلى {{version}}",
|
||||
|
|
@ -450,16 +533,38 @@ export const dict = {
|
|||
"home.recentProjects": "المشاريع الحديثة",
|
||||
"home.empty.title": "لا توجد مشاريع حديثة",
|
||||
"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.review": "مراجعة",
|
||||
"session.tab.context": "سياق",
|
||||
"session.tab.unknown": "جلسة غير معروفة",
|
||||
"session.panel.reviewAndFiles": "المراجعة والملفات",
|
||||
"session.error.notFound": "لا يمكن العثور على هذه الجلسة",
|
||||
"session.error.notFound.description": "يشير هذا التبويب إلى جلسة لم تعد موجودة على هذا الخادم.",
|
||||
"session.error.notFound.closeTab": "إغلاق علامة التبويب",
|
||||
"session.error.serverConnection": "تعذر الاتصال بهذا الخادم",
|
||||
"session.review.filesChanged": "تم تغيير {{count}} ملفات",
|
||||
"session.review.change.one": "تغيير",
|
||||
"session.review.change.other": "تغييرات",
|
||||
"session.review.loadingChanges": "جارٍ تحميل التغييرات...",
|
||||
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
|
||||
"session.review.noChanges": "لا توجد تغييرات",
|
||||
"session.review.noUncommittedChanges": "لا توجد تغييرات غير مُودَعة بعد",
|
||||
"session.review.noBranchChanges": "لا توجد تغييرات في الفرع بعد",
|
||||
"session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات",
|
||||
"session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة",
|
||||
"session.files.selectToOpen": "اختر ملفًا لفتحه",
|
||||
|
|
@ -475,6 +580,10 @@ export const dict = {
|
|||
"session.todo.title": "المهام",
|
||||
"session.todo.collapse": "طي",
|
||||
"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.other": "{{count}} رسائل في الانتظار",
|
||||
"session.followupDock.sendNow": "إرسال الآن",
|
||||
|
|
@ -487,9 +596,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها",
|
||||
"session.revertDock.restore": "استعادة الرسالة",
|
||||
"session.new.title": "ابنِ أي شيء",
|
||||
"session.new.project.new": "مشروع جديد",
|
||||
"session.new.project.search": "البحث عن المشاريع",
|
||||
"session.new.project.add": "إضافة مشروع",
|
||||
"session.new.worktree.main": "الفرع الرئيسي",
|
||||
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
|
||||
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
|
||||
"session.new.workspace.runIn": "تشغيل الجلسة في",
|
||||
"session.new.workspace.triggerLocal": "محلي",
|
||||
"session.new.workspace.local": "المستودع المحلي",
|
||||
"session.new.workspace.existing": "مساحة عمل…",
|
||||
"session.new.lastModified": "آخر تعديل",
|
||||
"session.header.search.placeholder": "بحث {{project}}",
|
||||
"session.header.searchFiles": "بحث عن الملفات",
|
||||
|
|
@ -550,6 +666,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "الجلسات الحديثة",
|
||||
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
|
||||
"sidebar.project.clearNotifications": "مسح الإشعارات",
|
||||
"sidebar.empty.title": "لا توجد مشاريع مفتوحة",
|
||||
"sidebar.empty.description": "افتح مشروعًا للبدء",
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "سطح المكتب",
|
||||
"settings.section.server": "الخادم",
|
||||
|
|
@ -559,6 +677,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.title": "تكامل WSL",
|
||||
"settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.",
|
||||
"settings.general.section.appearance": "المظهر",
|
||||
"settings.general.section.advanced": "متقدم",
|
||||
"settings.general.section.notifications": "إشعارات النظام",
|
||||
"settings.general.section.updates": "التحديثات",
|
||||
"settings.general.section.sounds": "المؤثرات الصوتية",
|
||||
|
|
@ -566,6 +685,11 @@ export const dict = {
|
|||
"settings.general.section.display": "شاشة العرض",
|
||||
"settings.general.row.language.title": "اللغة",
|
||||
"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.description": "تخصيص كيفية ظهور OpenCode على جهازك",
|
||||
"settings.general.row.colorScheme.title": "مخطط الألوان",
|
||||
|
|
@ -574,14 +698,29 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
||||
"settings.general.row.font.title": "خط الكود",
|
||||
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "خط الطرفية",
|
||||
"settings.general.row.terminalFont.description": "خصّص الخط المستخدم في الطرفية",
|
||||
"settings.general.row.uiFont.title": "خط الواجهة",
|
||||
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
|
||||
"settings.general.row.followup.title": "سلوك المتابعة",
|
||||
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
|
||||
"settings.general.row.followup.option.queue": "قائمة انتظار",
|
||||
"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.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
|
||||
|
|
@ -590,6 +729,11 @@ export const dict = {
|
|||
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
|
||||
"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.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
|
@ -602,6 +746,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة",
|
||||
"settings.updates.action.checkNow": "تحقق الآن",
|
||||
"settings.updates.action.checking": "جارٍ التحقق...",
|
||||
"settings.updates.action.downloading": "جارٍ التنزيل...",
|
||||
"settings.updates.action.installing": "جارٍ التثبيت...",
|
||||
"settings.updates.toast.latest.title": "أنت على آخر إصدار",
|
||||
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
|
||||
"sound.option.none": "بلا",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export const dict = {
|
|||
"theme.scheme.dark": "Escuro",
|
||||
"command.sidebar.toggle": "Alternar barra lateral",
|
||||
"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.server.switch": "Trocar servidor",
|
||||
"command.settings.open": "Abrir configurações",
|
||||
|
|
@ -39,6 +42,7 @@ export const dict = {
|
|||
"command.session.new": "Nova sessão",
|
||||
"command.file.open": "Abrir arquivo",
|
||||
"command.tab.close": "Fechar aba",
|
||||
"command.tab.reopenClosed": "Reabrir aba fechada",
|
||||
"command.context.addSelection": "Adicionar seleção ao contexto",
|
||||
"command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual",
|
||||
"command.input.focus": "Focar entrada",
|
||||
|
|
@ -193,6 +197,11 @@ export const dict = {
|
|||
"model.input.video": "vídeo",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Sem raciocínio",
|
||||
"model.tooltip.context": "Limite de contexto {{limit}}",
|
||||
|
|
@ -202,6 +211,7 @@ export const dict = {
|
|||
"common.loading": "Carregando",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Cancelar",
|
||||
"common.clear": "Limpar",
|
||||
"common.connect": "Conectar",
|
||||
"common.disconnect": "Desconectar",
|
||||
"common.continue": "Enviar",
|
||||
|
|
@ -218,6 +228,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.2": "Qual é a stack tecnológica deste projeto?",
|
||||
"prompt.example.3": "Corrigir testes quebrados",
|
||||
|
|
@ -314,6 +326,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Excluir",
|
||||
"dialog.server.current": "Servidor atual",
|
||||
"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.name": "Nome",
|
||||
"dialog.project.edit.icon": "Ícone",
|
||||
|
|
@ -371,6 +452,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
"toast.language.title": "Idioma",
|
||||
|
|
@ -407,10 +489,12 @@ export const dict = {
|
|||
"toast.update.action.notYet": "Agora não",
|
||||
"error.page.title": "Algo deu errado",
|
||||
"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.action.restart": "Reiniciar",
|
||||
"error.page.action.report": "Reportar erro",
|
||||
"error.page.action.reported": "Erro reportado",
|
||||
"error.page.action.exportLogs": "Exportar logs",
|
||||
"error.page.action.checking": "Verificando...",
|
||||
"error.page.action.checkUpdates": "Verificar atualizações",
|
||||
"error.page.action.updateTo": "Atualizar para {{version}}",
|
||||
|
|
@ -453,10 +537,30 @@ export const dict = {
|
|||
"home.recentProjects": "Projetos recentes",
|
||||
"home.empty.title": "Nenhum projeto recente",
|
||||
"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.review": "Revisão",
|
||||
"session.tab.context": "Contexto",
|
||||
"session.tab.unknown": "Sessão desconhecida",
|
||||
"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.change.one": "Alteração",
|
||||
"session.review.change.other": "Alterações",
|
||||
|
|
@ -466,6 +570,8 @@ export const dict = {
|
|||
"session.review.noSnapshot":
|
||||
"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.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.all": "Todos os arquivos",
|
||||
"session.files.empty": "Nenhum arquivo",
|
||||
|
|
@ -479,6 +585,10 @@ export const dict = {
|
|||
"session.todo.title": "Tarefas",
|
||||
"session.todo.collapse": "Recolher",
|
||||
"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.other": "{{count}} mensagens na fila",
|
||||
"session.followupDock.sendNow": "Enviar agora",
|
||||
|
|
@ -491,9 +601,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "Expandir mensagens revertidas",
|
||||
"session.revertDock.restore": "Restaurar mensagem",
|
||||
"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.mainWithBranch": "Branch principal ({{branch}})",
|
||||
"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.header.search.placeholder": "Buscar {{project}}",
|
||||
"session.header.searchFiles": "Buscar arquivos",
|
||||
|
|
@ -557,15 +674,18 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Sessões recentes",
|
||||
"sidebar.project.viewAllSessions": "Ver todas as sessõ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",
|
||||
"settings.section.desktop": "Desktop",
|
||||
"settings.section.server": "Servidor",
|
||||
"settings.tab.general": "Geral",
|
||||
"settings.tab.shortcuts": "Atalhos",
|
||||
"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.general.section.appearance": "Aparência",
|
||||
"settings.general.section.advanced": "Avançado",
|
||||
"settings.general.section.notifications": "Notificações do sistema",
|
||||
"settings.general.section.updates": "Atualizações",
|
||||
"settings.general.section.sounds": "Efeitos sonoros",
|
||||
|
|
@ -573,6 +693,11 @@ export const dict = {
|
|||
"settings.general.section.display": "Tela",
|
||||
"settings.general.row.language.title": "Idioma",
|
||||
"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.description": "Personalize como o OpenCode aparece no seu dispositivo",
|
||||
"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.font.title": "Fonte 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.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Fonte do terminal",
|
||||
"settings.general.row.terminalFont.description": "Personalize a fonte usada no terminal",
|
||||
"settings.general.row.uiFont.title": "Fonte da interface",
|
||||
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
|
||||
"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",
|
||||
"settings.general.row.followup.option.queue": "Fila",
|
||||
"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.description": "Exibir resumos de raciocínio do modelo na linha do tempo",
|
||||
"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.description":
|
||||
"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.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
||||
"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.action.checkNow": "Verificar agora",
|
||||
"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.description": "Você está usando a versão mais recente do OpenCode.",
|
||||
"sound.option.none": "Nenhum",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "Prikaži/sakrij bočnu traku",
|
||||
"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.server.switch": "Promijeni server",
|
||||
"command.settings.open": "Otvori postavke",
|
||||
|
|
@ -45,6 +48,7 @@ export const dict = {
|
|||
"command.session.new": "Nova sesija",
|
||||
"command.file.open": "Otvori datoteku",
|
||||
"command.tab.close": "Zatvori karticu",
|
||||
"command.tab.reopenClosed": "Ponovo otvori zatvorenu karticu",
|
||||
"command.context.addSelection": "Dodaj odabir u kontekst",
|
||||
"command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke",
|
||||
"command.input.focus": "Fokusiraj polje za unos",
|
||||
|
|
@ -209,6 +213,11 @@ export const dict = {
|
|||
"model.input.video": "video",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Bez rasuđivanja",
|
||||
"model.tooltip.context": "Limit konteksta {{limit}}",
|
||||
|
|
@ -219,6 +228,7 @@ export const dict = {
|
|||
"common.loading": "Učitavanje",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Otkaži",
|
||||
"common.clear": "Očisti",
|
||||
"common.connect": "Poveži",
|
||||
"common.disconnect": "Prekini vezu",
|
||||
"common.continue": "Pošalji",
|
||||
|
|
@ -236,6 +246,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.2": "Koji je tehnološki stack ovog projekta?",
|
||||
|
|
@ -342,6 +354,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Izbriši",
|
||||
"dialog.server.current": "Trenutni server",
|
||||
"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.name": "Naziv",
|
||||
|
|
@ -405,6 +486,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -453,10 +535,12 @@ export const dict = {
|
|||
|
||||
"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.localServerStartup": "Došlo je do greške pri pokretanju lokalnog servera.",
|
||||
"error.page.details.label": "Detalji greške",
|
||||
"error.page.action.restart": "Restartuj",
|
||||
"error.page.action.report": "Prijavi grešku",
|
||||
"error.page.action.reported": "Greška prijavljena",
|
||||
"error.page.action.exportLogs": "Izvezi logove",
|
||||
"error.page.action.checking": "Provjera...",
|
||||
"error.page.action.checkUpdates": "Provjeri ažuriranja",
|
||||
"error.page.action.updateTo": "Ažuriraj na {{version}}",
|
||||
|
|
@ -504,11 +588,31 @@ export const dict = {
|
|||
"home.recentProjects": "Nedavni projekti",
|
||||
"home.empty.title": "Nema nedavnih projekata",
|
||||
"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.review": "Pregled",
|
||||
"session.tab.context": "Kontekst",
|
||||
"session.tab.unknown": "Nepoznata sesija",
|
||||
"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.change.one": "Izmjena",
|
||||
"session.review.change.other": "Izmjene",
|
||||
|
|
@ -518,6 +622,8 @@ export const dict = {
|
|||
"session.review.noSnapshot":
|
||||
"Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne",
|
||||
"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.all": "Sve datoteke",
|
||||
|
|
@ -534,6 +640,10 @@ export const dict = {
|
|||
"session.todo.title": "Zadaci",
|
||||
"session.todo.collapse": "Sažmi",
|
||||
"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.other": "{{count}} poruka na čekanju",
|
||||
"session.followupDock.sendNow": "Pošalji sada",
|
||||
|
|
@ -547,9 +657,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "Vrati poruku",
|
||||
|
||||
"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.mainWithBranch": "Glavna grana ({{branch}})",
|
||||
"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.header.search.placeholder": "Pretraži {{project}}",
|
||||
|
|
@ -618,6 +735,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Nedavne sesije",
|
||||
"sidebar.project.viewAllSessions": "Prikaži sve sesije",
|
||||
"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",
|
||||
|
||||
|
|
@ -630,6 +749,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.",
|
||||
|
||||
"settings.general.section.appearance": "Izgled",
|
||||
"settings.general.section.advanced": "Napredno",
|
||||
"settings.general.section.notifications": "Sistemske obavijesti",
|
||||
"settings.general.section.updates": "Ažuriranja",
|
||||
"settings.general.section.sounds": "Zvučni efekti",
|
||||
|
|
@ -638,6 +758,11 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "Jezik",
|
||||
"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.description": "Prilagodi kako OpenCode izgleda na tvom uređaju",
|
||||
"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.font.title": "Font za kod",
|
||||
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Font terminala",
|
||||
"settings.general.row.terminalFont.description": "Prilagodite font koji se koristi u terminalu",
|
||||
"settings.general.row.uiFont.title": "UI font",
|
||||
"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.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.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.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.description":
|
||||
"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.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
||||
"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.action.checkNow": "Provjeri sada",
|
||||
"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.description": "Koristiš najnoviju verziju OpenCode-a.",
|
||||
"sound.option.none": "Nijedan",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "Skift sidebjælke",
|
||||
"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.server.switch": "Skift server",
|
||||
"command.settings.open": "Åbn indstillinger",
|
||||
|
|
@ -45,6 +48,7 @@ export const dict = {
|
|||
"command.session.new": "Ny session",
|
||||
"command.file.open": "Åbn fil",
|
||||
"command.tab.close": "Luk fane",
|
||||
"command.tab.reopenClosed": "Åbn lukket fane igen",
|
||||
"command.context.addSelection": "Tilføj markering til kontekst",
|
||||
"command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil",
|
||||
"command.input.focus": "Fokuser inputfelt",
|
||||
|
|
@ -208,6 +212,11 @@ export const dict = {
|
|||
"model.input.video": "video",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Ingen tænkning",
|
||||
"model.tooltip.context": "Kontekstgrænse {{limit}}",
|
||||
|
|
@ -217,6 +226,7 @@ export const dict = {
|
|||
"common.loading": "Indlæser",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Annuller",
|
||||
"common.clear": "Ryd",
|
||||
"common.connect": "Forbind",
|
||||
"common.disconnect": "Frakobl",
|
||||
"common.continue": "Indsend",
|
||||
|
|
@ -234,6 +244,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.2": "Hvad er teknologistakken for dette projekt?",
|
||||
|
|
@ -340,6 +352,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Slet",
|
||||
"dialog.server.current": "Nuværende server",
|
||||
"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.name": "Navn",
|
||||
|
|
@ -403,6 +484,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -450,10 +532,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "Noget gik galt",
|
||||
"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.action.restart": "Genstart",
|
||||
"error.page.action.report": "Rapportér fejl",
|
||||
"error.page.action.reported": "Fejl rapporteret",
|
||||
"error.page.action.exportLogs": "Eksportér logfiler",
|
||||
"error.page.action.checking": "Tjekker...",
|
||||
"error.page.action.checkUpdates": "Tjek for opdateringer",
|
||||
"error.page.action.updateTo": "Opdater til {{version}}",
|
||||
|
|
@ -501,11 +585,31 @@ export const dict = {
|
|||
"home.recentProjects": "Seneste projekter",
|
||||
"home.empty.title": "Ingen seneste projekter",
|
||||
"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.review": "Gennemgang",
|
||||
"session.tab.context": "Kontekst",
|
||||
"session.tab.unknown": "Ukendt session",
|
||||
"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.change.one": "Ændring",
|
||||
"session.review.change.other": "Ændringer",
|
||||
|
|
@ -515,6 +619,8 @@ export const dict = {
|
|||
"session.review.noSnapshot":
|
||||
"Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige",
|
||||
"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.all": "Alle filer",
|
||||
"session.files.empty": "Ingen filer",
|
||||
|
|
@ -529,6 +635,10 @@ export const dict = {
|
|||
"session.todo.title": "Opgaver",
|
||||
"session.todo.collapse": "Skjul",
|
||||
"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.other": "{{count}} beskeder i kø",
|
||||
"session.followupDock.sendNow": "Send nu",
|
||||
|
|
@ -542,9 +652,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "Gendan besked",
|
||||
|
||||
"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.mainWithBranch": "Hovedgren ({{branch}})",
|
||||
"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.header.search.placeholder": "Søg {{project}}",
|
||||
|
|
@ -614,6 +731,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Seneste sessioner",
|
||||
"sidebar.project.viewAllSessions": "Vis alle sessioner",
|
||||
"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",
|
||||
"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.general.section.appearance": "Udseende",
|
||||
"settings.general.section.advanced": "Avanceret",
|
||||
"settings.general.section.notifications": "Systemmeddelelser",
|
||||
"settings.general.section.updates": "Opdateringer",
|
||||
"settings.general.section.sounds": "Lydeffekter",
|
||||
|
|
@ -633,6 +753,11 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "Sprog",
|
||||
"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.description": "Tilpas hvordan OpenCode ser ud på din enhed",
|
||||
"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.font.title": "Kode-skrifttype",
|
||||
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Terminalskrifttype",
|
||||
"settings.general.row.terminalFont.description": "Tilpas den skrifttype, der bruges i terminalen",
|
||||
"settings.general.row.uiFont.title": "UI-skrifttype",
|
||||
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
|
||||
"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.option.queue": "Kø",
|
||||
"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.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.description":
|
||||
"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.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
||||
"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.action.checkNow": "Tjek nu",
|
||||
"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.description": "Du kører den nyeste version af OpenCode.",
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ export const dict = {
|
|||
"theme.scheme.dark": "Dunkel",
|
||||
"command.sidebar.toggle": "Seitenleiste umschalten",
|
||||
"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.server.switch": "Server wechseln",
|
||||
"command.settings.open": "Einstellungen öffnen",
|
||||
|
|
@ -43,6 +46,7 @@ export const dict = {
|
|||
"command.session.new": "Neue Sitzung",
|
||||
"command.file.open": "Datei öffnen",
|
||||
"command.tab.close": "Tab schließen",
|
||||
"command.tab.reopenClosed": "Geschlossenen Tab wieder öffnen",
|
||||
"command.context.addSelection": "Auswahl zum Kontext hinzufügen",
|
||||
"command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen",
|
||||
"command.input.focus": "Eingabefeld fokussieren",
|
||||
|
|
@ -198,6 +202,11 @@ export const dict = {
|
|||
"model.input.video": "Video",
|
||||
"model.input.pdf": "PDF",
|
||||
"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.none": "Kein Reasoning",
|
||||
"model.tooltip.context": "Kontextlimit {{limit}}",
|
||||
|
|
@ -207,6 +216,7 @@ export const dict = {
|
|||
"common.loading": "Laden",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Abbrechen",
|
||||
"common.clear": "Leeren",
|
||||
"common.connect": "Verbinden",
|
||||
"common.disconnect": "Trennen",
|
||||
"common.continue": "Absenden",
|
||||
|
|
@ -223,6 +233,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.2": "Was ist der Tech-Stack dieses Projekts?",
|
||||
"prompt.example.3": "Fehlerhafte Tests beheben",
|
||||
|
|
@ -320,6 +332,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Löschen",
|
||||
"dialog.server.current": "Aktueller Server",
|
||||
"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.name": "Name",
|
||||
"dialog.project.edit.icon": "Icon",
|
||||
|
|
@ -378,6 +459,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
"toast.language.title": "Sprache",
|
||||
|
|
@ -414,10 +496,12 @@ export const dict = {
|
|||
"toast.update.action.notYet": "Noch nicht",
|
||||
"error.page.title": "Etwas ist schiefgelaufen",
|
||||
"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.action.restart": "Neustart",
|
||||
"error.page.action.report": "Fehler melden",
|
||||
"error.page.action.reported": "Fehler gemeldet",
|
||||
"error.page.action.exportLogs": "Protokolle exportieren",
|
||||
"error.page.action.checking": "Prüfen...",
|
||||
"error.page.action.checkUpdates": "Nach Updates suchen",
|
||||
"error.page.action.updateTo": "Auf {{version}} aktualisieren",
|
||||
|
|
@ -461,10 +545,31 @@ export const dict = {
|
|||
"home.recentProjects": "Letzte Projekte",
|
||||
"home.empty.title": "Keine letzten Projekte",
|
||||
"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.review": "Überprüfung",
|
||||
"session.tab.context": "Kontext",
|
||||
"session.tab.unknown": "Unbekannte Sitzung",
|
||||
"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.change.one": "Änderung",
|
||||
"session.review.change.other": "Änderungen",
|
||||
|
|
@ -474,6 +579,8 @@ export const dict = {
|
|||
"session.review.noSnapshot":
|
||||
"Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar",
|
||||
"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.all": "Alle Dateien",
|
||||
"session.files.empty": "Keine Dateien",
|
||||
|
|
@ -487,6 +594,10 @@ export const dict = {
|
|||
"session.todo.title": "Aufgaben",
|
||||
"session.todo.collapse": "Einklappen",
|
||||
"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.other": "{{count}} Nachrichten in der Warteschlange",
|
||||
"session.followupDock.sendNow": "Jetzt senden",
|
||||
|
|
@ -499,9 +610,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen",
|
||||
"session.revertDock.restore": "Nachricht wiederherstellen",
|
||||
"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.mainWithBranch": "Haupt-Branch ({{branch}})",
|
||||
"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.header.search.placeholder": "{{project}} durchsuchen",
|
||||
"session.header.searchFiles": "Dateien suchen",
|
||||
|
|
@ -566,6 +684,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Letzte Sitzungen",
|
||||
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
|
||||
"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",
|
||||
"settings.section.desktop": "Desktop",
|
||||
"settings.section.server": "Server",
|
||||
|
|
@ -575,6 +695,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.title": "WSL-Integration",
|
||||
"settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.",
|
||||
"settings.general.section.appearance": "Erscheinungsbild",
|
||||
"settings.general.section.advanced": "Erweitert",
|
||||
"settings.general.section.notifications": "Systembenachrichtigungen",
|
||||
"settings.general.section.updates": "Updates",
|
||||
"settings.general.section.sounds": "Soundeffekte",
|
||||
|
|
@ -582,6 +703,11 @@ export const dict = {
|
|||
"settings.general.section.display": "Anzeige",
|
||||
"settings.general.row.language.title": "Sprache",
|
||||
"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.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
|
||||
"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.font.title": "Code-Schriftart",
|
||||
"settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Terminalschriftart",
|
||||
"settings.general.row.terminalFont.description": "Passe die im Terminal verwendete Schriftart an",
|
||||
"settings.general.row.uiFont.title": "UI-Schriftart",
|
||||
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
|
||||
"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",
|
||||
"settings.general.row.followup.option.queue": "Warteschlange",
|
||||
"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.description":
|
||||
"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.description":
|
||||
"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.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
||||
"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.action.checkNow": "Jetzt prüfen",
|
||||
"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.description": "Du verwendest die aktuelle Version von OpenCode.",
|
||||
"sound.option.none": "Keine",
|
||||
|
|
|
|||
|
|
@ -212,6 +212,11 @@ export const dict = {
|
|||
"model.input.video": "video",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "No reasoning",
|
||||
"model.tooltip.context": "Context limit {{limit}}",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "Alternar barra lateral",
|
||||
"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.server.switch": "Cambiar servidor",
|
||||
"command.settings.open": "Abrir ajustes",
|
||||
|
|
@ -45,6 +48,7 @@ export const dict = {
|
|||
"command.session.new": "Nueva sesión",
|
||||
"command.file.open": "Abrir archivo",
|
||||
"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.description": "Añadir las líneas seleccionadas del archivo actual",
|
||||
"command.input.focus": "Enfocar entrada",
|
||||
|
|
@ -208,6 +212,11 @@ export const dict = {
|
|||
"model.input.video": "video",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Sin razonamiento",
|
||||
"model.tooltip.context": "Límite de contexto {{limit}}",
|
||||
|
|
@ -218,6 +227,7 @@ export const dict = {
|
|||
"common.loading": "Cargando",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Cancelar",
|
||||
"common.clear": "Limpiar",
|
||||
"common.connect": "Conectar",
|
||||
"common.disconnect": "Desconectar",
|
||||
"common.continue": "Enviar",
|
||||
|
|
@ -235,6 +245,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.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.current": "Servidor actual",
|
||||
"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.name": "Nombre",
|
||||
|
|
@ -405,6 +486,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -453,10 +535,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "Algo salió mal",
|
||||
"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.action.restart": "Reiniciar",
|
||||
"error.page.action.report": "Informar error",
|
||||
"error.page.action.reported": "Error informado",
|
||||
"error.page.action.exportLogs": "Exportar registros",
|
||||
"error.page.action.checking": "Comprobando...",
|
||||
"error.page.action.checkUpdates": "Buscar actualizaciones",
|
||||
"error.page.action.updateTo": "Actualizar a {{version}}",
|
||||
|
|
@ -505,11 +589,31 @@ export const dict = {
|
|||
"home.recentProjects": "Proyectos recientes",
|
||||
"home.empty.title": "Sin proyectos recientes",
|
||||
"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.review": "Revisión",
|
||||
"session.tab.context": "Contexto",
|
||||
"session.tab.unknown": "Sesión desconocida",
|
||||
"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.change.one": "Cambio",
|
||||
"session.review.change.other": "Cambios",
|
||||
|
|
@ -519,6 +623,8 @@ export const dict = {
|
|||
"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",
|
||||
"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.all": "Todos los archivos",
|
||||
|
|
@ -535,6 +641,10 @@ export const dict = {
|
|||
"session.todo.title": "Tareas",
|
||||
"session.todo.collapse": "Contraer",
|
||||
"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.other": "{{count}} mensajes en cola",
|
||||
"session.followupDock.sendNow": "Enviar ahora",
|
||||
|
|
@ -548,9 +658,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "Restaurar mensaje",
|
||||
|
||||
"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.mainWithBranch": "Rama principal ({{branch}})",
|
||||
"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.header.search.placeholder": "Buscar {{project}}",
|
||||
|
|
@ -621,6 +738,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Sesiones recientes",
|
||||
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
|
||||
"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",
|
||||
|
||||
|
|
@ -633,6 +752,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Apariencia",
|
||||
"settings.general.section.advanced": "Avanzado",
|
||||
"settings.general.section.notifications": "Notificaciones del sistema",
|
||||
"settings.general.section.updates": "Actualizaciones",
|
||||
"settings.general.section.sounds": "Efectos de sonido",
|
||||
|
|
@ -641,6 +761,11 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "Idioma",
|
||||
"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.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
|
||||
"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.font.title": "Fuente 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.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Fuente del 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.description": "Personaliza la fuente usada en toda la interfaz",
|
||||
"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",
|
||||
"settings.general.row.followup.option.queue": "Cola",
|
||||
"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.description":
|
||||
"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.description":
|
||||
"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.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
||||
"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.action.checkNow": "Buscar ahora",
|
||||
"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.description": "Estás usando la última versión de OpenCode.",
|
||||
"sound.option.none": "Ninguno",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export const dict = {
|
|||
"theme.scheme.dark": "Sombre",
|
||||
"command.sidebar.toggle": "Basculer la barre latérale",
|
||||
"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.server.switch": "Changer de serveur",
|
||||
"command.settings.open": "Ouvrir les paramètres",
|
||||
|
|
@ -39,6 +42,7 @@ export const dict = {
|
|||
"command.session.new": "Nouvelle session",
|
||||
"command.file.open": "Ouvrir un fichier",
|
||||
"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.description": "Ajouter les lignes sélectionnées du fichier actuel",
|
||||
"command.input.focus": "Focus sur l'entrée",
|
||||
|
|
@ -193,6 +197,11 @@ export const dict = {
|
|||
"model.input.video": "vidéo",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Sans raisonnement",
|
||||
"model.tooltip.context": "Limite de contexte {{limit}}",
|
||||
|
|
@ -202,6 +211,7 @@ export const dict = {
|
|||
"common.loading": "Chargement",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Annuler",
|
||||
"common.clear": "Effacer",
|
||||
"common.connect": "Connecter",
|
||||
"common.disconnect": "Déconnecter",
|
||||
"common.continue": "Soumettre",
|
||||
|
|
@ -218,6 +228,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.2": "Quelle est la pile technique de ce projet ?",
|
||||
"prompt.example.3": "Réparer les tests échoués",
|
||||
|
|
@ -315,6 +327,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Supprimer",
|
||||
"dialog.server.current": "Serveur actuel",
|
||||
"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.name": "Nom",
|
||||
"dialog.project.edit.icon": "Icône",
|
||||
|
|
@ -373,6 +454,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
"toast.language.title": "Langue",
|
||||
|
|
@ -410,10 +492,12 @@ export const dict = {
|
|||
"toast.update.action.notYet": "Pas encore",
|
||||
"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.localServerStartup": "Une erreur s'est produite lors du démarrage du serveur local.",
|
||||
"error.page.details.label": "Détails de l'erreur",
|
||||
"error.page.action.restart": "Redémarrer",
|
||||
"error.page.action.report": "Signaler l'erreur",
|
||||
"error.page.action.reported": "Erreur signalée",
|
||||
"error.page.action.exportLogs": "Exporter les journaux",
|
||||
"error.page.action.checking": "Vérification...",
|
||||
"error.page.action.checkUpdates": "Vérifier les mises à jour",
|
||||
"error.page.action.updateTo": "Mettre à jour vers {{version}}",
|
||||
|
|
@ -458,16 +542,38 @@ export const dict = {
|
|||
"home.recentProjects": "Projets récents",
|
||||
"home.empty.title": "Aucun projet récent",
|
||||
"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.review": "Revue",
|
||||
"session.tab.context": "Contexte",
|
||||
"session.tab.unknown": "Session inconnue",
|
||||
"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.change.one": "Modification",
|
||||
"session.review.change.other": "Modifications",
|
||||
"session.review.loadingChanges": "Chargement des modifications...",
|
||||
"session.review.empty": "Aucune modification dans cette session pour l'instant",
|
||||
"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.noSnapshot":
|
||||
"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.collapse": "Réduire",
|
||||
"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.other": "{{count}} messages en file d'attente",
|
||||
"session.followupDock.sendNow": "Envoyer maintenant",
|
||||
|
|
@ -496,9 +606,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "Développer les messages annulés",
|
||||
"session.revertDock.restore": "Restaurer le message",
|
||||
"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.mainWithBranch": "Branche principale ({{branch}})",
|
||||
"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.header.search.placeholder": "Rechercher {{project}}",
|
||||
"session.header.searchFiles": "Rechercher des fichiers",
|
||||
|
|
@ -564,6 +681,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Sessions récentes",
|
||||
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
|
||||
"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",
|
||||
"settings.section.desktop": "Bureau",
|
||||
"settings.section.server": "Serveur",
|
||||
|
|
@ -573,6 +692,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.title": "Intégration WSL",
|
||||
"settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.",
|
||||
"settings.general.section.appearance": "Apparence",
|
||||
"settings.general.section.advanced": "Avancé",
|
||||
"settings.general.section.notifications": "Notifications système",
|
||||
"settings.general.section.updates": "Mises à jour",
|
||||
"settings.general.section.sounds": "Effets sonores",
|
||||
|
|
@ -580,6 +700,11 @@ export const dict = {
|
|||
"settings.general.section.display": "Affichage",
|
||||
"settings.general.row.language.title": "Langue",
|
||||
"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.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
|
||||
"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.font.title": "Police 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.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Police du 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.description": "Personnaliser la police utilisée dans toute l'interface",
|
||||
"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",
|
||||
"settings.general.row.followup.option.queue": "File d'attente",
|
||||
"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.description":
|
||||
"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.description":
|
||||
"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.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
||||
"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.action.checkNow": "Vérifier maintenant",
|
||||
"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.description": "Vous utilisez la dernière version d'OpenCode.",
|
||||
"sound.option.none": "Aucun",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export const dict = {
|
|||
"theme.scheme.dark": "ダーク",
|
||||
"command.sidebar.toggle": "サイドバーの切り替え",
|
||||
"command.project.open": "プロジェクトを開く",
|
||||
"command.project.previous": "前のプロジェクト",
|
||||
"command.project.next": "次のプロジェクト",
|
||||
"command.project.index": "プロジェクト{{index}}に切り替え",
|
||||
"command.provider.connect": "プロバイダーに接続",
|
||||
"command.server.switch": "サーバーの切り替え",
|
||||
"command.settings.open": "設定を開く",
|
||||
|
|
@ -39,6 +42,7 @@ export const dict = {
|
|||
"command.session.new": "新しいセッション",
|
||||
"command.file.open": "ファイルを開く",
|
||||
"command.tab.close": "タブを閉じる",
|
||||
"command.tab.reopenClosed": "閉じたタブを再度開く",
|
||||
"command.context.addSelection": "選択範囲をコンテキストに追加",
|
||||
"command.context.addSelection.description": "現在のファイルから選択した行を追加",
|
||||
"command.input.focus": "入力欄にフォーカス",
|
||||
|
|
@ -149,7 +153,7 @@ export const dict = {
|
|||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア",
|
||||
"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.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "APIキー",
|
||||
|
|
@ -192,6 +196,11 @@ export const dict = {
|
|||
"model.input.video": "動画",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "推論なし",
|
||||
"model.tooltip.context": "コンテキスト上限 {{limit}}",
|
||||
|
|
@ -201,6 +210,7 @@ export const dict = {
|
|||
"common.loading": "読み込み中",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "キャンセル",
|
||||
"common.clear": "クリア",
|
||||
"common.connect": "接続",
|
||||
"common.disconnect": "切断",
|
||||
"common.continue": "送信",
|
||||
|
|
@ -217,6 +227,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "シェル",
|
||||
"prompt.mode.normal": "プロンプト",
|
||||
"prompt.mode.shell.exit": "escで終了",
|
||||
"session.child.promptDisabled": "サブエージェントのセッションにはプロンプトを送信できません。",
|
||||
"session.child.backToParent": "メインセッションに戻る。",
|
||||
"prompt.example.1": "コードベースのTODOを修正",
|
||||
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
|
||||
"prompt.example.3": "壊れたテストを修正",
|
||||
|
|
@ -313,6 +325,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "削除",
|
||||
"dialog.server.current": "現在のサーバー",
|
||||
"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.name": "名前",
|
||||
"dialog.project.edit.icon": "アイコン",
|
||||
|
|
@ -370,6 +451,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
"toast.language.title": "言語",
|
||||
|
|
@ -406,10 +488,12 @@ export const dict = {
|
|||
"toast.update.action.notYet": "今はしない",
|
||||
"error.page.title": "問題が発生しました",
|
||||
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
|
||||
"error.page.description.localServerStartup": "ローカルサーバーの起動中にエラーが発生しました。",
|
||||
"error.page.details.label": "エラー詳細",
|
||||
"error.page.action.restart": "再起動",
|
||||
"error.page.action.report": "エラーを報告",
|
||||
"error.page.action.reported": "エラーを報告しました",
|
||||
"error.page.action.exportLogs": "ログをエクスポート",
|
||||
"error.page.action.checking": "確認中...",
|
||||
"error.page.action.checkUpdates": "アップデートを確認",
|
||||
"error.page.action.updateTo": "{{version}}にアップデート",
|
||||
|
|
@ -451,10 +535,30 @@ export const dict = {
|
|||
"home.recentProjects": "最近のプロジェクト",
|
||||
"home.empty.title": "最近のプロジェクトはありません",
|
||||
"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.review": "レビュー",
|
||||
"session.tab.context": "コンテキスト",
|
||||
"session.tab.unknown": "不明なセッション",
|
||||
"session.panel.reviewAndFiles": "レビューとファイル",
|
||||
"session.error.notFound": "このセッションが見つかりません",
|
||||
"session.error.notFound.description": "このタブが参照するセッションは、このサーバーには存在しません。",
|
||||
"session.error.notFound.closeTab": "タブを閉じる",
|
||||
"session.error.serverConnection": "このサーバーに接続できません",
|
||||
"session.review.filesChanged": "{{count}} ファイル変更",
|
||||
"session.review.change.one": "変更",
|
||||
"session.review.change.other": "変更",
|
||||
|
|
@ -463,6 +567,8 @@ export const dict = {
|
|||
"session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません",
|
||||
"session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません",
|
||||
"session.review.noChanges": "変更なし",
|
||||
"session.review.noUncommittedChanges": "コミットされていない変更はまだありません",
|
||||
"session.review.noBranchChanges": "ブランチの変更はまだありません",
|
||||
"session.files.selectToOpen": "開くファイルを選択",
|
||||
"session.files.all": "すべてのファイル",
|
||||
"session.files.empty": "ファイルなし",
|
||||
|
|
@ -476,6 +582,10 @@ export const dict = {
|
|||
"session.todo.title": "ToDo",
|
||||
"session.todo.collapse": "折りたたむ",
|
||||
"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.other": "{{count}} 件のメッセージが待機中",
|
||||
"session.followupDock.sendNow": "今すぐ送信",
|
||||
|
|
@ -488,9 +598,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "ロールバックされたメッセージを展開",
|
||||
"session.revertDock.restore": "メッセージを復元",
|
||||
"session.new.title": "何でも作る",
|
||||
"session.new.project.new": "新しいプロジェクト",
|
||||
"session.new.project.search": "プロジェクトを検索",
|
||||
"session.new.project.add": "プロジェクトを追加",
|
||||
"session.new.worktree.main": "メインブランチ",
|
||||
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
|
||||
"session.new.worktree.create": "新しいワークツリーを作成",
|
||||
"session.new.workspace.runIn": "セッションの実行先",
|
||||
"session.new.workspace.triggerLocal": "ローカル",
|
||||
"session.new.workspace.local": "ローカルリポジトリ",
|
||||
"session.new.workspace.existing": "ワークスペース…",
|
||||
"session.new.lastModified": "最終更新",
|
||||
"session.header.search.placeholder": "{{project}}を検索",
|
||||
"session.header.searchFiles": "ファイルを検索",
|
||||
|
|
@ -554,6 +671,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "最近のセッション",
|
||||
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
|
||||
"sidebar.project.clearNotifications": "通知をクリア",
|
||||
"sidebar.empty.title": "開いているプロジェクトはありません",
|
||||
"sidebar.empty.description": "プロジェクトを開いて始めましょう",
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "デスクトップ",
|
||||
"settings.section.server": "サーバー",
|
||||
|
|
@ -563,6 +682,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.title": "WSL連携",
|
||||
"settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。",
|
||||
"settings.general.section.appearance": "外観",
|
||||
"settings.general.section.advanced": "詳細設定",
|
||||
"settings.general.section.notifications": "システム通知",
|
||||
"settings.general.section.updates": "アップデート",
|
||||
"settings.general.section.sounds": "効果音",
|
||||
|
|
@ -570,6 +690,11 @@ export const dict = {
|
|||
"settings.general.section.display": "ディスプレイ",
|
||||
"settings.general.row.language.title": "言語",
|
||||
"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.description": "デバイスでのOpenCodeの表示をカスタマイズします",
|
||||
"settings.general.row.colorScheme.title": "配色",
|
||||
|
|
@ -578,8 +703,8 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
||||
"settings.general.row.font.title": "コードフォント",
|
||||
"settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "ターミナルのフォント",
|
||||
"settings.general.row.terminalFont.description": "ターミナルで使用するフォントをカスタマイズ",
|
||||
"settings.general.row.uiFont.title": "UIフォント",
|
||||
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
|
||||
"settings.general.row.followup.title": "フォローアップの動作",
|
||||
|
|
@ -587,6 +712,21 @@ export const dict = {
|
|||
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
|
||||
"settings.general.row.followup.option.queue": "キューに追加",
|
||||
"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.description": "タイムラインにモデルの推論の要約を表示します",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
|
||||
|
|
@ -595,6 +735,12 @@ export const dict = {
|
|||
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"タイムラインで 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.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
|
@ -607,6 +753,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします",
|
||||
"settings.updates.action.checkNow": "今すぐ確認",
|
||||
"settings.updates.action.checking": "確認中...",
|
||||
"settings.updates.action.downloading": "ダウンロード中...",
|
||||
"settings.updates.action.installing": "インストール中...",
|
||||
"settings.updates.toast.latest.title": "最新です",
|
||||
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
|
||||
"sound.option.none": "なし",
|
||||
|
|
|
|||
|
|
@ -575,8 +575,8 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
||||
"settings.general.row.font.title": "코드 글꼴",
|
||||
"settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "터미널 글꼴",
|
||||
"settings.general.row.terminalFont.description": "터미널에서 사용할 글꼴을 설정합니다",
|
||||
"settings.general.row.uiFont.title": "UI 글꼴",
|
||||
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
|
||||
"settings.general.row.followup.title": "후속 조치 동작",
|
||||
|
|
@ -848,4 +848,155 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패",
|
||||
"error.childStore.storeCreateFailed": "저장소 생성 실패",
|
||||
"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": "설치 중...",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export const dict = {
|
|||
"command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk",
|
||||
"command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser",
|
||||
"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.description": "Angre siste melding",
|
||||
"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?",
|
||||
|
||||
"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.causedBy": "Forårsaket av:",
|
||||
|
|
@ -649,8 +649,8 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
|
||||
"settings.general.row.font.title": "Kodefont",
|
||||
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Terminalskrift",
|
||||
"settings.general.row.terminalFont.description": "Tilpass skrifttypen som brukes i terminalen",
|
||||
"settings.general.row.uiFont.title": "UI-skrift",
|
||||
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
|
||||
"settings.general.row.followup.title": "Oppfølgingsadferd",
|
||||
|
|
@ -936,4 +936,163 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon",
|
||||
"error.childStore.storeCreateFailed": "Kunne ikke opprette lager",
|
||||
"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>>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,118 @@
|
|||
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 keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const
|
||||
const appLocales = [
|
||||
"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", () => {
|
||||
test("non-English locales translate targeted unseen session keys", () => {
|
||||
for (const locale of locales) {
|
||||
for (const key of keys) {
|
||||
expect(locale[key]).toBeDefined()
|
||||
expect(locale[key]).not.toBe(en[key])
|
||||
test("non-English locales have every English key", 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 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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export const dict = {
|
|||
"theme.scheme.dark": "Ciemny",
|
||||
"command.sidebar.toggle": "Przełącz pasek boczny",
|
||||
"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.server.switch": "Przełącz serwer",
|
||||
"command.settings.open": "Otwórz ustawienia",
|
||||
|
|
@ -39,6 +42,7 @@ export const dict = {
|
|||
"command.session.new": "Nowa sesja",
|
||||
"command.file.open": "Otwórz plik",
|
||||
"command.tab.close": "Zamknij kartę",
|
||||
"command.tab.reopenClosed": "Otwórz ponownie zamkniętą kartę",
|
||||
"command.context.addSelection": "Dodaj zaznaczenie do kontekstu",
|
||||
"command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku",
|
||||
"command.input.focus": "Fokus na pole wejściowe",
|
||||
|
|
@ -194,6 +198,11 @@ export const dict = {
|
|||
"model.input.video": "wideo",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Brak wnioskowania",
|
||||
"model.tooltip.context": "Limit kontekstu {{limit}}",
|
||||
|
|
@ -203,6 +212,7 @@ export const dict = {
|
|||
"common.loading": "Ładowanie",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Anuluj",
|
||||
"common.clear": "Wyczyść",
|
||||
"common.connect": "Połącz",
|
||||
"common.disconnect": "Rozłącz",
|
||||
"common.continue": "Prześlij",
|
||||
|
|
@ -219,6 +229,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Terminal",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"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.2": "Jaki jest stos technologiczny tego projektu?",
|
||||
"prompt.example.3": "Napraw zepsute testy",
|
||||
|
|
@ -315,6 +327,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Usuń",
|
||||
"dialog.server.current": "Obecny serwer",
|
||||
"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.name": "Nazwa",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
|
|
@ -324,7 +405,8 @@ export const dict = {
|
|||
"dialog.project.edit.color": "Kolor",
|
||||
"dialog.project.edit.color.select": "Wybierz kolor {{color}}",
|
||||
"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",
|
||||
"context.breakdown.title": "Podział kontekstu",
|
||||
"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.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
"toast.language.title": "Język",
|
||||
|
|
@ -407,10 +490,12 @@ export const dict = {
|
|||
"toast.update.action.notYet": "Jeszcze nie",
|
||||
"error.page.title": "Coś poszło nie tak",
|
||||
"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.action.restart": "Restartuj",
|
||||
"error.page.action.report": "Zgłoś błąd",
|
||||
"error.page.action.reported": "Błąd zgłoszony",
|
||||
"error.page.action.exportLogs": "Eksportuj dzienniki",
|
||||
"error.page.action.checking": "Sprawdzanie...",
|
||||
"error.page.action.checkUpdates": "Sprawdź aktualizacje",
|
||||
"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.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
|
||||
"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.providerInitFailed":
|
||||
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
|
||||
|
|
@ -452,10 +538,30 @@ export const dict = {
|
|||
"home.recentProjects": "Ostatnie projekty",
|
||||
"home.empty.title": "Brak ostatnich projektów",
|
||||
"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.review": "Przegląd",
|
||||
"session.tab.context": "Kontekst",
|
||||
"session.tab.unknown": "Nieznana sesja",
|
||||
"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.change.one": "Zmiana",
|
||||
"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.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne",
|
||||
"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.all": "Wszystkie pliki",
|
||||
"session.files.empty": "Brak plików",
|
||||
|
|
@ -477,6 +585,10 @@ export const dict = {
|
|||
"session.todo.title": "Zadania",
|
||||
"session.todo.collapse": "Zwiń",
|
||||
"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.other": "{{count}} wiadomości w kolejce",
|
||||
"session.followupDock.sendNow": "Wyślij teraz",
|
||||
|
|
@ -489,9 +601,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "Rozwiń cofnięte wiadomości",
|
||||
"session.revertDock.restore": "Przywróć wiadomość",
|
||||
"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.mainWithBranch": "Główna gałąź ({{branch}})",
|
||||
"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.header.search.placeholder": "Szukaj {{project}}",
|
||||
"session.header.searchFiles": "Szukaj plików",
|
||||
|
|
@ -555,15 +674,18 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Ostatnie sesje",
|
||||
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
|
||||
"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",
|
||||
"settings.section.desktop": "Pulpit",
|
||||
"settings.section.server": "Serwer",
|
||||
"settings.tab.general": "Ogólne",
|
||||
"settings.tab.shortcuts": "Skróty",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integration",
|
||||
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
|
||||
"settings.desktop.wsl.title": "Integracja z WSL",
|
||||
"settings.desktop.wsl.description": "Uruchamiaj serwer OpenCode wewnątrz WSL w systemie Windows.",
|
||||
"settings.general.section.appearance": "Wygląd",
|
||||
"settings.general.section.advanced": "Zaawansowane",
|
||||
"settings.general.section.notifications": "Powiadomienia systemowe",
|
||||
"settings.general.section.updates": "Aktualizacje",
|
||||
"settings.general.section.sounds": "Efekty dźwiękowe",
|
||||
|
|
@ -571,6 +693,11 @@ export const dict = {
|
|||
"settings.general.section.display": "Ekran",
|
||||
"settings.general.row.language.title": "Język",
|
||||
"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.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
|
||||
"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.font.title": "Czcionka kodu",
|
||||
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Czcionka terminala",
|
||||
"settings.general.row.terminalFont.description": "Dostosuj czcionkę używaną w terminalu",
|
||||
"settings.general.row.uiFont.title": "Czcionka interfejsu",
|
||||
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
|
||||
"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.option.queue": "Kolejka",
|
||||
"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.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu",
|
||||
"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.description":
|
||||
"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.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
||||
"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.action.checkNow": "Sprawdź teraz",
|
||||
"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.description": "Korzystasz z najnowszej wersji OpenCode.",
|
||||
"sound.option.none": "Brak",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "Переключить боковую панель",
|
||||
"command.project.open": "Открыть проект",
|
||||
"command.project.previous": "Предыдущий проект",
|
||||
"command.project.next": "Следующий проект",
|
||||
"command.project.index": "Переключиться на проект {{index}}",
|
||||
"command.provider.connect": "Подключить провайдера",
|
||||
"command.server.switch": "Переключить сервер",
|
||||
"command.settings.open": "Открыть настройки",
|
||||
|
|
@ -45,6 +48,7 @@ export const dict = {
|
|||
"command.session.new": "Новая сессия",
|
||||
"command.file.open": "Открыть файл",
|
||||
"command.tab.close": "Закрыть вкладку",
|
||||
"command.tab.reopenClosed": "Повторно открыть закрытую вкладку",
|
||||
"command.context.addSelection": "Добавить выделение в контекст",
|
||||
"command.context.addSelection.description": "Добавить выбранные строки из текущего файла",
|
||||
"command.input.focus": "Фокус на поле ввода",
|
||||
|
|
@ -208,6 +212,11 @@ export const dict = {
|
|||
"model.input.video": "видео",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Без рассуждения",
|
||||
"model.tooltip.context": "Лимит контекста {{limit}}",
|
||||
|
|
@ -218,6 +227,7 @@ export const dict = {
|
|||
"common.loading": "Загрузка",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Отмена",
|
||||
"common.clear": "Очистить",
|
||||
"common.connect": "Подключить",
|
||||
"common.disconnect": "Отключить",
|
||||
"common.continue": "Отправить",
|
||||
|
|
@ -235,6 +245,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Оболочка",
|
||||
"prompt.mode.normal": "Промпт",
|
||||
"prompt.mode.shell.exit": "esc для выхода",
|
||||
"session.child.promptDisabled": "В сессиях субагентов нельзя отправлять запросы.",
|
||||
"session.child.backToParent": "Вернуться к основной сессии.",
|
||||
|
||||
"prompt.example.1": "Исправить TODO в коде",
|
||||
"prompt.example.2": "Какой технологический стек этого проекта?",
|
||||
|
|
@ -341,6 +353,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Удалить",
|
||||
"dialog.server.current": "Текущий сервер",
|
||||
"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.name": "Название",
|
||||
|
|
@ -405,6 +486,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -452,10 +534,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "Что-то пошло не так",
|
||||
"error.page.description": "Произошла ошибка при загрузке приложения.",
|
||||
"error.page.description.localServerStartup": "Произошла ошибка при запуске локального сервера.",
|
||||
"error.page.details.label": "Детали ошибки",
|
||||
"error.page.action.restart": "Перезапустить",
|
||||
"error.page.action.report": "Сообщить об ошибке",
|
||||
"error.page.action.reported": "Об ошибке сообщено",
|
||||
"error.page.action.exportLogs": "Экспортировать журналы",
|
||||
"error.page.action.checking": "Проверка...",
|
||||
"error.page.action.checkUpdates": "Проверить обновления",
|
||||
"error.page.action.updateTo": "Обновить до {{version}}",
|
||||
|
|
@ -505,11 +589,31 @@ export const dict = {
|
|||
"home.recentProjects": "Недавние проекты",
|
||||
"home.empty.title": "Нет недавних проектов",
|
||||
"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.review": "Обзор",
|
||||
"session.tab.context": "Контекст",
|
||||
"session.tab.unknown": "Неизвестная сессия",
|
||||
"session.panel.reviewAndFiles": "Обзор и файлы",
|
||||
"session.error.notFound": "Эта сессия не найдена",
|
||||
"session.error.notFound.description": "Эта вкладка указывает на сессию, которой больше нет на этом сервере.",
|
||||
"session.error.notFound.closeTab": "Закрыть вкладку",
|
||||
"session.error.serverConnection": "Не удаётся подключиться к этому серверу",
|
||||
"session.review.filesChanged": "{{count}} файлов изменено",
|
||||
"session.review.change.one": "Изменение",
|
||||
"session.review.change.other": "Изменения",
|
||||
|
|
@ -518,6 +622,8 @@ export const dict = {
|
|||
"session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются",
|
||||
"session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны",
|
||||
"session.review.noChanges": "Нет изменений",
|
||||
"session.review.noUncommittedChanges": "Пока нет незафиксированных изменений",
|
||||
"session.review.noBranchChanges": "Пока нет изменений в ветке",
|
||||
"session.files.selectToOpen": "Выберите файл, чтобы открыть",
|
||||
"session.files.all": "Все файлы",
|
||||
"session.files.empty": "Нет файлов",
|
||||
|
|
@ -532,6 +638,10 @@ export const dict = {
|
|||
"session.todo.title": "Задачи",
|
||||
"session.todo.collapse": "Свернуть",
|
||||
"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.other": "{{count}} сообщений в очереди",
|
||||
"session.followupDock.sendNow": "Отправить сейчас",
|
||||
|
|
@ -545,9 +655,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "Восстановить сообщение",
|
||||
|
||||
"session.new.title": "Создавайте что угодно",
|
||||
"session.new.project.new": "Новый проект",
|
||||
"session.new.project.search": "Поиск проектов",
|
||||
"session.new.project.add": "Добавить проект",
|
||||
"session.new.worktree.main": "Основная ветка",
|
||||
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
|
||||
"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.header.search.placeholder": "Поиск {{project}}",
|
||||
|
|
@ -619,6 +736,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Недавние сессии",
|
||||
"sidebar.project.viewAllSessions": "Посмотреть все сессии",
|
||||
"sidebar.project.clearNotifications": "Очистить уведомления",
|
||||
"sidebar.empty.title": "Нет открытых проектов",
|
||||
"sidebar.empty.description": "Откройте проект, чтобы начать",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "Приложение",
|
||||
|
|
@ -630,6 +749,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Внешний вид",
|
||||
"settings.general.section.advanced": "Дополнительно",
|
||||
"settings.general.section.notifications": "Системные уведомления",
|
||||
"settings.general.section.updates": "Обновления",
|
||||
"settings.general.section.sounds": "Звуковые эффекты",
|
||||
|
|
@ -638,6 +758,11 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "Язык",
|
||||
"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.description": "Настройте как OpenCode выглядит на вашем устройстве",
|
||||
"settings.general.row.colorScheme.title": "Цветовая схема",
|
||||
|
|
@ -646,8 +771,8 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "Настройте оформление OpenCode.",
|
||||
"settings.general.row.font.title": "Шрифт кода",
|
||||
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Шрифт терминала",
|
||||
"settings.general.row.terminalFont.description": "Настройте шрифт, используемый в терминале",
|
||||
"settings.general.row.uiFont.title": "Шрифт интерфейса",
|
||||
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
|
||||
"settings.general.row.followup.title": "Поведение уточняющих вопросов",
|
||||
|
|
@ -655,6 +780,22 @@ export const dict = {
|
|||
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
|
||||
"settings.general.row.followup.option.queue": "Очередь",
|
||||
"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.description": "Отображать сводки рассуждений модели в ленте",
|
||||
|
||||
|
|
@ -664,6 +805,12 @@ export const dict = {
|
|||
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
|
||||
"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.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
|
|
@ -678,6 +825,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны",
|
||||
"settings.updates.action.checkNow": "Проверить сейчас",
|
||||
"settings.updates.action.checking": "Проверка...",
|
||||
"settings.updates.action.downloading": "Загрузка...",
|
||||
"settings.updates.action.installing": "Установка...",
|
||||
"settings.updates.toast.latest.title": "У вас последняя версия",
|
||||
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
|
||||
"sound.option.none": "Нет",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "สลับแถบข้าง",
|
||||
"command.project.open": "เปิดโปรเจกต์",
|
||||
"command.project.previous": "โปรเจกต์ก่อนหน้า",
|
||||
"command.project.next": "โปรเจกต์ถัดไป",
|
||||
"command.project.index": "สลับไปยังโปรเจกต์ {{index}}",
|
||||
"command.provider.connect": "เชื่อมต่อผู้ให้บริการ",
|
||||
"command.server.switch": "สลับเซิร์ฟเวอร์",
|
||||
"command.settings.open": "เปิดการตั้งค่า",
|
||||
|
|
@ -32,7 +35,7 @@ export const dict = {
|
|||
"command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป",
|
||||
"command.session.archive": "จัดเก็บเซสชัน",
|
||||
|
||||
"command.palette": "คำสั่งค้นหา",
|
||||
"command.palette": "ชุดคำสั่ง",
|
||||
|
||||
"command.theme.cycle": "เปลี่ยนธีม",
|
||||
"command.theme.set": "ใช้ธีม: {{theme}}",
|
||||
|
|
@ -45,6 +48,7 @@ export const dict = {
|
|||
"command.session.new": "เซสชันใหม่",
|
||||
"command.file.open": "เปิดไฟล์",
|
||||
"command.tab.close": "ปิดแท็บ",
|
||||
"command.tab.reopenClosed": "เปิดแท็บที่ปิดไปอีกครั้ง",
|
||||
"command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท",
|
||||
"command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน",
|
||||
"command.input.focus": "โฟกัสช่องป้อนข้อมูล",
|
||||
|
|
@ -163,7 +167,7 @@ export const dict = {
|
|||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
|
||||
"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.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "คีย์ API",
|
||||
|
|
@ -208,6 +212,11 @@ export const dict = {
|
|||
"model.input.video": "วิดีโอ",
|
||||
"model.input.pdf": "PDF",
|
||||
"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.none": "ไม่มีการใช้เหตุผล",
|
||||
"model.tooltip.context": "ขีดจำกัดบริบท {{limit}}",
|
||||
|
|
@ -218,6 +227,7 @@ export const dict = {
|
|||
"common.loading": "กำลังโหลด",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "ยกเลิก",
|
||||
"common.clear": "ล้าง",
|
||||
"common.connect": "เชื่อมต่อ",
|
||||
"common.disconnect": "ยกเลิกการเชื่อมต่อ",
|
||||
"common.continue": "ส่ง",
|
||||
|
|
@ -235,6 +245,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "เชลล์",
|
||||
"prompt.mode.normal": "พรอมต์",
|
||||
"prompt.mode.shell.exit": "กด esc เพื่อออก",
|
||||
"session.child.promptDisabled": "ไม่สามารถส่งพรอมต์ในเซสชันเอเจนต์ย่อยได้.",
|
||||
"session.child.backToParent": "กลับไปยังเซสชันหลัก.",
|
||||
|
||||
"prompt.example.1": "แก้ไข TODO ในโค้ดเบส",
|
||||
"prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
|
||||
|
|
@ -341,6 +353,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "ลบ",
|
||||
"dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน",
|
||||
"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.name": "ชื่อ",
|
||||
|
|
@ -403,6 +484,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -451,10 +533,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "เกิดข้อผิดพลาด",
|
||||
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
|
||||
"error.page.description.localServerStartup": "เกิดข้อผิดพลาดขณะเริ่มเซิร์ฟเวอร์ในเครื่อง.",
|
||||
"error.page.details.label": "รายละเอียดข้อผิดพลาด",
|
||||
"error.page.action.restart": "รีสตาร์ท",
|
||||
"error.page.action.report": "รายงานข้อผิดพลาด",
|
||||
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
|
||||
"error.page.action.exportLogs": "ส่งออกบันทึก",
|
||||
"error.page.action.checking": "กำลังตรวจสอบ...",
|
||||
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
|
||||
"error.page.action.updateTo": "อัปเดตเป็น {{version}}",
|
||||
|
|
@ -500,11 +584,31 @@ export const dict = {
|
|||
"home.recentProjects": "โปรเจกต์ล่าสุด",
|
||||
"home.empty.title": "ไม่มีโปรเจกต์ล่าสุด",
|
||||
"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.review": "ตรวจสอบ",
|
||||
"session.tab.context": "บริบท",
|
||||
"session.tab.unknown": "เซสชันที่ไม่รู้จัก",
|
||||
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
|
||||
"session.error.notFound": "ไม่พบเซสชันนี้",
|
||||
"session.error.notFound.description": "แท็บนี้อ้างถึงเซสชันที่ไม่มีอยู่บนเซิร์ฟเวอร์นี้แล้ว.",
|
||||
"session.error.notFound.closeTab": "ปิดแท็บ",
|
||||
"session.error.serverConnection": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้",
|
||||
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
|
||||
"session.review.change.one": "การเปลี่ยนแปลง",
|
||||
"session.review.change.other": "การเปลี่ยนแปลง",
|
||||
|
|
@ -513,6 +617,8 @@ export const dict = {
|
|||
"session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง",
|
||||
"session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน",
|
||||
"session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
|
||||
"session.review.noUncommittedChanges": "ยังไม่มีการเปลี่ยนแปลงที่รอคอมมิต",
|
||||
"session.review.noBranchChanges": "ยังไม่มีการเปลี่ยนแปลงในสาขา",
|
||||
|
||||
"session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
|
||||
"session.files.empty": "ไม่มีไฟล์",
|
||||
|
|
@ -529,6 +635,10 @@ export const dict = {
|
|||
"session.todo.title": "สิ่งที่ต้องทำ",
|
||||
"session.todo.collapse": "ย่อ",
|
||||
"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.other": "{{count}} ข้อความในคิว",
|
||||
"session.followupDock.sendNow": "ส่งทันที",
|
||||
|
|
@ -542,9 +652,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "กู้คืนข้อความ",
|
||||
|
||||
"session.new.title": "สร้างอะไรก็ได้",
|
||||
"session.new.project.new": "โปรเจกต์ใหม่",
|
||||
"session.new.project.search": "ค้นหาโปรเจกต์",
|
||||
"session.new.project.add": "เพิ่มโปรเจกต์",
|
||||
"session.new.worktree.main": "สาขาหลัก",
|
||||
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
|
||||
"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.header.search.placeholder": "ค้นหา {{project}}",
|
||||
|
|
@ -612,6 +729,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "เซสชันล่าสุด",
|
||||
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
|
||||
"sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
|
||||
"sidebar.empty.title": "ไม่มีโปรเจกต์ที่เปิดอยู่",
|
||||
"sidebar.empty.description": "เปิดโปรเจกต์เพื่อเริ่มต้น",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
|
||||
|
|
@ -624,6 +743,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows",
|
||||
|
||||
"settings.general.section.appearance": "รูปลักษณ์",
|
||||
"settings.general.section.advanced": "ขั้นสูง",
|
||||
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
||||
"settings.general.section.updates": "การอัปเดต",
|
||||
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
||||
|
|
@ -632,6 +752,11 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "ภาษา",
|
||||
"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.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
|
||||
"settings.general.row.colorScheme.title": "โทนสี",
|
||||
|
|
@ -640,14 +765,29 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
|
||||
"settings.general.row.font.title": "ฟอนต์โค้ด",
|
||||
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "ฟอนต์เทอร์มินัล",
|
||||
"settings.general.row.terminalFont.description": "ปรับแต่งฟอนต์ที่ใช้ในเทอร์มินัล",
|
||||
"settings.general.row.uiFont.title": "ฟอนต์ UI",
|
||||
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
|
||||
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
|
||||
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
|
||||
"settings.general.row.followup.option.queue": "คิว",
|
||||
"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.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
|
||||
|
|
@ -655,6 +795,11 @@ export const dict = {
|
|||
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"แสดงส่วนเครื่องมือ 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.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
||||
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
||||
|
|
@ -668,6 +813,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี",
|
||||
"settings.updates.action.checkNow": "ตรวจสอบทันที",
|
||||
"settings.updates.action.checking": "กำลังตรวจสอบ...",
|
||||
"settings.updates.action.downloading": "กำลังดาวน์โหลด...",
|
||||
"settings.updates.action.installing": "กำลังติดตั้ง...",
|
||||
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
|
||||
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
|
||||
|
||||
|
|
@ -857,10 +1004,10 @@ export const dict = {
|
|||
"dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
|
||||
"dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
|
||||
"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.actionLoading": "กำลังสร้าง Git repository...",
|
||||
"session.review.noVcs.createGit.action": "สร้าง Git repository",
|
||||
"session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git รีโพซิทอรี...",
|
||||
"session.review.noVcs.createGit.action": "สร้าง Git รีโพซิทอรี",
|
||||
"session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ",
|
||||
"session.question.progress": "{{current}} จาก {{total}} คำถาม",
|
||||
"session.header.open.finder": "Finder",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
|
||||
"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.server.switch": "Sunucu değiştir",
|
||||
"command.settings.open": "Ayarları aç",
|
||||
|
|
@ -49,6 +52,7 @@ export const dict = {
|
|||
"command.session.new": "Yeni oturum",
|
||||
"command.file.open": "Dosya aç",
|
||||
"command.tab.close": "Sekmeyi kapat",
|
||||
"command.tab.reopenClosed": "Kapatılan sekmeyi yeniden aç",
|
||||
"command.context.addSelection": "Seçimi bağlama ekle",
|
||||
"command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
|
||||
"command.input.focus": "Girişi odakla",
|
||||
|
|
@ -132,7 +136,7 @@ export const dict = {
|
|||
"provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
|
||||
"provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
|
||||
"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.placeholder": "API anahtarı",
|
||||
"provider.connect.apiKey.required": "API anahtarı gerekli",
|
||||
|
|
@ -143,10 +147,10 @@ export const dict = {
|
|||
"provider.connect.opencodeZen.visit.prefix": "",
|
||||
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
|
||||
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
|
||||
"provider.connect.oauth.code.visit.prefix":
|
||||
"Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ",
|
||||
"provider.connect.oauth.code.visit.prefix": "Yetkilendirme kodunuzu almak için ",
|
||||
"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.placeholder": "Yetkilendirme kodu",
|
||||
"provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
|
||||
|
|
@ -213,6 +217,11 @@ export const dict = {
|
|||
"model.input.video": "video",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Akıl yürütme yok",
|
||||
"model.tooltip.context": "Bağlam limiti {{limit}}",
|
||||
|
|
@ -223,6 +232,7 @@ export const dict = {
|
|||
"common.loading": "Yükleniyor",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "İptal",
|
||||
"common.clear": "Temizle",
|
||||
"common.connect": "Bağlan",
|
||||
"common.disconnect": "Bağlantı Kes",
|
||||
"common.continue": "Gönder",
|
||||
|
|
@ -240,6 +250,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Kabuk",
|
||||
"prompt.mode.normal": "Komut",
|
||||
"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.2": "Bu projenin teknoloji yığını nedir?",
|
||||
|
|
@ -346,6 +358,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Sil",
|
||||
"dialog.server.current": "Mevcut Sunucu",
|
||||
"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.name": "Ad",
|
||||
|
|
@ -408,6 +489,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -456,10 +538,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "Bir şeyler yanlış gitti",
|
||||
"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.action.restart": "Yeniden Başlat",
|
||||
"error.page.action.report": "Hatayı Bildir",
|
||||
"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.checkUpdates": "Güncellemeleri kontrol et",
|
||||
"error.page.action.updateTo": "{{version}} sürümüne güncelle",
|
||||
|
|
@ -509,11 +593,31 @@ export const dict = {
|
|||
"home.recentProjects": "Son projeler",
|
||||
"home.empty.title": "Son proje yok",
|
||||
"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.review": "İnceleme",
|
||||
"session.tab.context": "Bağlam",
|
||||
"session.tab.unknown": "Bilinmeyen Oturum",
|
||||
"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.change.one": "Değişiklik",
|
||||
"session.review.change.other": "Değişiklik",
|
||||
|
|
@ -523,6 +627,8 @@ export const dict = {
|
|||
"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",
|
||||
"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.all": "Tüm dosyalar",
|
||||
|
|
@ -539,6 +645,10 @@ export const dict = {
|
|||
"session.todo.title": "Görevler",
|
||||
"session.todo.collapse": "Daralt",
|
||||
"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.other": "{{count}} sıradaki mesaj",
|
||||
"session.followupDock.sendNow": "Şimdi gönder",
|
||||
|
|
@ -552,9 +662,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "Mesajı geri yükle",
|
||||
|
||||
"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.mainWithBranch": "Ana dal ({{branch}})",
|
||||
"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.header.search.placeholder": "{{project}} ara",
|
||||
|
|
@ -624,6 +741,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "Son oturumlar",
|
||||
"sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
|
||||
"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ü",
|
||||
|
||||
|
|
@ -636,6 +755,7 @@ export const dict = {
|
|||
"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.advanced": "Gelişmiş",
|
||||
"settings.general.section.notifications": "Sistem bildirimleri",
|
||||
"settings.general.section.updates": "Güncellemeler",
|
||||
"settings.general.section.sounds": "Ses efektleri",
|
||||
|
|
@ -644,6 +764,11 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "Dil",
|
||||
"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.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
|
||||
"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.font.title": "Kod Yazı Tipi",
|
||||
"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.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "Terminal yazı tipi",
|
||||
"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.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
|
||||
"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",
|
||||
"settings.general.row.followup.option.queue": "Sıra",
|
||||
"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.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",
|
||||
|
|
@ -670,6 +810,12 @@ export const dict = {
|
|||
"settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"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.description":
|
||||
|
|
@ -686,6 +832,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
|
||||
"settings.updates.action.checkNow": "Şimdi kontrol et",
|
||||
"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.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export const dict = {
|
|||
"command.session.new": "Нова сесія",
|
||||
"command.file.open": "Відкрити файл",
|
||||
"command.tab.close": "Закрити вкладку",
|
||||
"command.tab.reopenClosed": "Повторно відкрити закриту вкладку",
|
||||
"command.context.addSelection": "Додати виділення до контексту",
|
||||
"command.context.addSelection.description": "Додати вибрані рядки з поточного файлу",
|
||||
"command.input.focus": "Фокус на полі введення",
|
||||
|
|
@ -212,6 +213,11 @@ export const dict = {
|
|||
"model.input.video": "відео",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "Без мислення",
|
||||
"model.tooltip.context": "Ліміт контексту {{limit}}",
|
||||
|
|
@ -222,6 +228,7 @@ export const dict = {
|
|||
"common.loading": "Завантаження",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Скасувати",
|
||||
"common.clear": "Очистити",
|
||||
"common.open": "Відкрити",
|
||||
"common.connect": "Підключити",
|
||||
"common.disconnect": "Відключити",
|
||||
|
|
@ -354,6 +361,75 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "Видалити",
|
||||
"dialog.server.current": "Поточний сервер",
|
||||
"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": "без імені користувача",
|
||||
|
||||
"dialog.project.edit.title": "Редагувати проєкт",
|
||||
|
|
@ -473,10 +549,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "Щось пішло не так",
|
||||
"error.page.description": "Під час завантаження програми сталася помилка.",
|
||||
"error.page.description.localServerStartup": "Під час запуску локального сервера сталася помилка.",
|
||||
"error.page.details.label": "Деталі помилки",
|
||||
"error.page.action.restart": "Перезапустити",
|
||||
"error.page.action.report": "Повідомити про помилку",
|
||||
"error.page.action.reported": "Помилку повідомлено",
|
||||
"error.page.action.exportLogs": "Експортувати журнали",
|
||||
"error.page.action.checking": "Перевірка...",
|
||||
"error.page.action.checkUpdates": "Перевірити оновлення",
|
||||
"error.page.action.updateTo": "Оновити до {{version}}",
|
||||
|
|
@ -533,11 +611,31 @@ export const dict = {
|
|||
"home.recentProjects": "Нещодавні проєкти",
|
||||
"home.empty.title": "Немає нещодавніх проєктів",
|
||||
"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.review": "Огляд",
|
||||
"session.tab.context": "Контекст",
|
||||
"session.tab.unknown": "Невідома сесія",
|
||||
"session.panel.reviewAndFiles": "Огляд і файли",
|
||||
"session.error.notFound": "Цю сесію не знайдено",
|
||||
"session.error.notFound.description": "Ця вкладка посилається на сесію, якої більше немає на цьому сервері.",
|
||||
"session.error.notFound.closeTab": "Закрити вкладку",
|
||||
"session.error.serverConnection": "Не вдається підключитися до цього сервера",
|
||||
"session.review.filesChanged": "Змінено файлів: {{count}}",
|
||||
"session.review.change.one": "Зміна",
|
||||
"session.review.change.other": "Зміни",
|
||||
|
|
@ -570,6 +668,10 @@ export const dict = {
|
|||
"session.todo.expand": "Розгорнути",
|
||||
"session.todo.progress": "Виконано {{done}} з {{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.other": "{{count}} повідомлень у черзі",
|
||||
"session.followupDock.sendNow": "Надіслати зараз",
|
||||
|
|
@ -583,9 +685,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "Відновити повідомлення",
|
||||
|
||||
"session.new.title": "Створити що завгодно",
|
||||
"session.new.project.new": "Новий проєкт",
|
||||
"session.new.project.search": "Пошук проєктів",
|
||||
"session.new.project.add": "Додати проєкт",
|
||||
"session.new.worktree.main": "Основна гілка",
|
||||
"session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})",
|
||||
"session.new.worktree.create": "Створити нове робоче дерево",
|
||||
"session.new.workspace.runIn": "Запустити сесію в",
|
||||
"session.new.workspace.triggerLocal": "Локально",
|
||||
"session.new.workspace.local": "Локальний репозиторій",
|
||||
"session.new.workspace.existing": "Робоча область…",
|
||||
"session.new.lastModified": "Востаннє змінено",
|
||||
|
||||
"session.header.search.placeholder": "Пошук {{project}}",
|
||||
|
|
@ -773,6 +882,11 @@ export const dict = {
|
|||
"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.description": "Відображати підсумки мислення моделі на часовій шкалі",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки",
|
||||
|
|
@ -781,6 +895,12 @@ export const dict = {
|
|||
"settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування",
|
||||
"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.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.",
|
||||
|
|
@ -796,6 +916,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні",
|
||||
"settings.updates.action.checkNow": "Перевірити зараз",
|
||||
"settings.updates.action.checking": "Перевірка...",
|
||||
"settings.updates.action.downloading": "Завантаження...",
|
||||
"settings.updates.action.installing": "Встановлення...",
|
||||
"settings.updates.toast.latest.title": "У вас актуальна версія",
|
||||
"settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.",
|
||||
"sound.option.none": "Немає",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ export const dict = {
|
|||
"command.sidebar.toggle": "切换侧边栏",
|
||||
|
||||
"command.project.open": "打开项目",
|
||||
"command.project.previous": "上一个项目",
|
||||
"command.project.next": "下一个项目",
|
||||
"command.project.index": "切换到项目 {{index}}",
|
||||
|
||||
"command.provider.connect": "连接提供商",
|
||||
|
||||
|
|
@ -56,6 +59,7 @@ export const dict = {
|
|||
"command.file.open": "打开文件",
|
||||
|
||||
"command.tab.close": "关闭标签页",
|
||||
"command.tab.reopenClosed": "重新打开已关闭的标签页",
|
||||
|
||||
"command.context.addSelection": "将所选内容添加到上下文",
|
||||
"command.context.addSelection.description": "添加当前文件中选中的行",
|
||||
|
|
@ -230,6 +234,11 @@ export const dict = {
|
|||
"model.input.video": "视频",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "不支持推理",
|
||||
"model.tooltip.context": "上下文上限 {{limit}}",
|
||||
|
|
@ -240,6 +249,7 @@ export const dict = {
|
|||
"common.loading": "加载中",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "取消",
|
||||
"common.clear": "清除",
|
||||
"common.connect": "连接",
|
||||
"common.disconnect": "断开连接",
|
||||
"common.continue": "提交",
|
||||
|
|
@ -257,6 +267,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"prompt.mode.shell.exit": "按 esc 退出",
|
||||
"session.child.promptDisabled": "无法向子智能体会话发送提示词。",
|
||||
"session.child.backToParent": "返回主会话。",
|
||||
"prompt.example.1": "修复代码库中的一个 TODO",
|
||||
"prompt.example.2": "这个项目的技术栈是什么?",
|
||||
"prompt.example.3": "修复失败的测试",
|
||||
|
|
@ -359,6 +371,73 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "删除",
|
||||
"dialog.server.current": "当前服务器",
|
||||
"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 需要 WSL(Windows 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.name": "名称",
|
||||
|
|
@ -418,6 +497,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -456,10 +536,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "出了点问题",
|
||||
"error.page.description": "加载应用程序时发生错误。",
|
||||
"error.page.description.localServerStartup": "启动本地服务器时发生错误。",
|
||||
"error.page.details.label": "错误详情",
|
||||
"error.page.action.restart": "重启",
|
||||
"error.page.action.report": "上报错误",
|
||||
"error.page.action.reported": "错误已上报",
|
||||
"error.page.action.exportLogs": "导出日志",
|
||||
"error.page.action.checking": "检查中...",
|
||||
"error.page.action.checkUpdates": "检查更新",
|
||||
"error.page.action.updateTo": "更新到 {{version}}",
|
||||
|
|
@ -506,6 +588,9 @@ export const dict = {
|
|||
"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": "会话",
|
||||
|
|
@ -519,7 +604,12 @@ export const dict = {
|
|||
"session.tab.session": "会话",
|
||||
"session.tab.review": "审查",
|
||||
"session.tab.context": "上下文",
|
||||
"session.tab.unknown": "未知会话",
|
||||
"session.panel.reviewAndFiles": "审查和文件",
|
||||
"session.error.notFound": "找不到此会话",
|
||||
"session.error.notFound.description": "此标签页指向的会话已不存在于此服务器上。",
|
||||
"session.error.notFound.closeTab": "关闭标签页",
|
||||
"session.error.serverConnection": "无法连接到此服务器",
|
||||
"session.review.filesChanged": "{{count}} 个文件变更",
|
||||
"session.review.change.one": "更改",
|
||||
"session.review.change.other": "更改",
|
||||
|
|
@ -528,6 +618,8 @@ export const dict = {
|
|||
"session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改",
|
||||
"session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用",
|
||||
"session.review.noChanges": "无更改",
|
||||
"session.review.noUncommittedChanges": "尚无未提交的更改",
|
||||
"session.review.noBranchChanges": "尚无分支更改",
|
||||
"session.files.selectToOpen": "选择要打开的文件",
|
||||
"session.files.all": "所有文件",
|
||||
"session.files.empty": "无文件",
|
||||
|
|
@ -541,6 +633,10 @@ export const dict = {
|
|||
"session.todo.title": "待办事项",
|
||||
"session.todo.collapse": "折叠",
|
||||
"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.other": "{{count}} 条排队消息",
|
||||
"session.followupDock.sendNow": "立即发送",
|
||||
|
|
@ -553,9 +649,16 @@ export const dict = {
|
|||
"session.revertDock.expand": "展开已回滚消息",
|
||||
"session.revertDock.restore": "恢复消息",
|
||||
"session.new.title": "构建任何东西",
|
||||
"session.new.project.new": "新建项目",
|
||||
"session.new.project.search": "搜索项目",
|
||||
"session.new.project.add": "添加项目",
|
||||
"session.new.worktree.main": "主分支",
|
||||
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
|
||||
"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.header.search.placeholder": "搜索 {{project}}",
|
||||
"session.header.searchFiles": "搜索文件",
|
||||
|
|
@ -623,6 +726,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "最近会话",
|
||||
"sidebar.project.viewAllSessions": "查看全部会话",
|
||||
"sidebar.project.clearNotifications": "清除通知",
|
||||
"sidebar.empty.title": "没有打开的项目",
|
||||
"sidebar.empty.description": "打开一个项目以开始使用",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
|
||||
|
|
@ -637,6 +742,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。",
|
||||
|
||||
"settings.general.section.appearance": "外观",
|
||||
"settings.general.section.advanced": "高级",
|
||||
"settings.general.section.notifications": "系统通知",
|
||||
"settings.general.section.updates": "更新",
|
||||
"settings.general.section.sounds": "音效",
|
||||
|
|
@ -644,6 +750,10 @@ export const dict = {
|
|||
"settings.general.section.display": "显示",
|
||||
"settings.general.row.language.title": "语言",
|
||||
"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.description": "自定义 OpenCode 在你的设备上的外观",
|
||||
"settings.general.row.colorScheme.title": "配色方案",
|
||||
|
|
@ -652,20 +762,38 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
||||
"settings.general.row.font.title": "代码字体",
|
||||
"settings.general.row.font.description": "自定义代码块使用的字体",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "终端字体",
|
||||
"settings.general.row.terminalFont.description": "自定义终端使用的字体",
|
||||
"settings.general.row.uiFont.title": "界面字体",
|
||||
"settings.general.row.uiFont.description": "自定义整个界面使用的字体",
|
||||
"settings.general.row.followup.title": "跟进消息行为",
|
||||
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
|
||||
"settings.general.row.followup.option.queue": "排队",
|
||||
"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.description": "在时间线中显示模型推理摘要",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
|
||||
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
|
||||
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
|
||||
"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.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
||||
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
||||
|
|
@ -678,6 +806,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "手动检查更新并在有更新时安装",
|
||||
"settings.updates.action.checkNow": "立即检查",
|
||||
"settings.updates.action.checking": "正在检查...",
|
||||
"settings.updates.action.downloading": "正在下载...",
|
||||
"settings.updates.action.installing": "正在安装...",
|
||||
"settings.updates.toast.latest.title": "已是最新版本",
|
||||
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ export const dict = {
|
|||
|
||||
"command.sidebar.toggle": "切換側邊欄",
|
||||
"command.project.open": "開啟專案",
|
||||
"command.project.previous": "上一個專案",
|
||||
"command.project.next": "下一個專案",
|
||||
"command.project.index": "切換至專案 {{index}}",
|
||||
"command.provider.connect": "連接提供者",
|
||||
"command.server.switch": "切換伺服器",
|
||||
"command.settings.open": "開啟設定",
|
||||
|
|
@ -49,6 +52,7 @@ export const dict = {
|
|||
"command.session.new": "新增工作階段",
|
||||
"command.file.open": "開啟檔案",
|
||||
"command.tab.close": "關閉分頁",
|
||||
"command.tab.reopenClosed": "重新開啟已關閉的分頁",
|
||||
"command.context.addSelection": "將選取內容加入上下文",
|
||||
"command.context.addSelection.description": "加入目前檔案中選取的行",
|
||||
"command.input.focus": "聚焦輸入框",
|
||||
|
|
@ -209,6 +213,11 @@ export const dict = {
|
|||
"model.input.video": "影片",
|
||||
"model.input.pdf": "pdf",
|
||||
"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.none": "不支援推理",
|
||||
"model.tooltip.context": "上下文上限 {{limit}}",
|
||||
|
|
@ -218,6 +227,7 @@ export const dict = {
|
|||
"common.loading": "載入中",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "取消",
|
||||
"common.clear": "清除",
|
||||
"common.connect": "連線",
|
||||
"common.disconnect": "中斷連線",
|
||||
"common.continue": "提交",
|
||||
|
|
@ -235,6 +245,8 @@ export const dict = {
|
|||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.normal": "Prompt",
|
||||
"prompt.mode.shell.exit": "按 esc 退出",
|
||||
"session.child.promptDisabled": "無法在子代理程式工作階段中輸入提示詞。",
|
||||
"session.child.backToParent": "返回主要工作階段。",
|
||||
|
||||
"prompt.example.1": "修復程式碼庫中的一個 TODO",
|
||||
"prompt.example.2": "這個專案的技術堆疊是什麼?",
|
||||
|
|
@ -340,6 +352,73 @@ export const dict = {
|
|||
"dialog.server.menu.delete": "刪除",
|
||||
"dialog.server.current": "目前伺服器",
|
||||
"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 伺服器前,必須先安裝 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.name": "名稱",
|
||||
|
|
@ -402,6 +481,7 @@ export const dict = {
|
|||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.uk": "Українська",
|
||||
"language.th": "ไทย",
|
||||
"language.tr": "Türkçe",
|
||||
|
||||
|
|
@ -449,10 +529,12 @@ export const dict = {
|
|||
|
||||
"error.page.title": "出了點問題",
|
||||
"error.page.description": "載入應用程式時發生錯誤。",
|
||||
"error.page.description.localServerStartup": "啟動本機伺服器時發生錯誤。",
|
||||
"error.page.details.label": "錯誤詳情",
|
||||
"error.page.action.restart": "重新啟動",
|
||||
"error.page.action.report": "回報錯誤",
|
||||
"error.page.action.reported": "已回報錯誤",
|
||||
"error.page.action.exportLogs": "匯出日誌",
|
||||
"error.page.action.checking": "檢查中...",
|
||||
"error.page.action.checkUpdates": "檢查更新",
|
||||
"error.page.action.updateTo": "更新到 {{version}}",
|
||||
|
|
@ -498,17 +580,39 @@ export const dict = {
|
|||
"home.recentProjects": "最近專案",
|
||||
"home.empty.title": "沒有最近專案",
|
||||
"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.review": "審查",
|
||||
"session.tab.context": "上下文",
|
||||
"session.tab.unknown": "未知的工作階段",
|
||||
"session.panel.reviewAndFiles": "審查與檔案",
|
||||
"session.error.notFound": "找不到此工作階段",
|
||||
"session.error.notFound.description": "此分頁指向的工作階段已不存在於此伺服器。",
|
||||
"session.error.notFound.closeTab": "關閉分頁",
|
||||
"session.error.serverConnection": "無法連線至此伺服器",
|
||||
"session.review.filesChanged": "{{count}} 個檔案變更",
|
||||
"session.review.change.one": "變更",
|
||||
"session.review.change.other": "變更",
|
||||
"session.review.loadingChanges": "正在載入變更...",
|
||||
"session.review.empty": "此工作階段暫無變更",
|
||||
"session.review.noChanges": "沒有變更",
|
||||
"session.review.noUncommittedChanges": "尚無未提交的變更",
|
||||
"session.review.noBranchChanges": "尚無分支變更",
|
||||
"session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更",
|
||||
"session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更",
|
||||
"session.files.selectToOpen": "選取要開啟的檔案",
|
||||
|
|
@ -525,6 +629,10 @@ export const dict = {
|
|||
"session.todo.title": "待辦事項",
|
||||
"session.todo.collapse": "折疊",
|
||||
"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.other": "{{count}} 則佇列訊息",
|
||||
"session.followupDock.sendNow": "立即傳送",
|
||||
|
|
@ -538,9 +646,16 @@ export const dict = {
|
|||
"session.revertDock.restore": "還原訊息",
|
||||
|
||||
"session.new.title": "建構任何東西",
|
||||
"session.new.project.new": "新專案",
|
||||
"session.new.project.search": "搜尋專案",
|
||||
"session.new.project.add": "新增專案",
|
||||
"session.new.worktree.main": "主分支",
|
||||
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
|
||||
"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.header.search.placeholder": "搜尋 {{project}}",
|
||||
|
|
@ -608,6 +723,8 @@ export const dict = {
|
|||
"sidebar.project.recentSessions": "最近工作階段",
|
||||
"sidebar.project.viewAllSessions": "查看全部工作階段",
|
||||
"sidebar.project.clearNotifications": "清除通知",
|
||||
"sidebar.empty.title": "未開啟任何專案",
|
||||
"sidebar.empty.description": "開啟專案以開始使用",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "桌面",
|
||||
|
|
@ -619,6 +736,7 @@ export const dict = {
|
|||
"settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。",
|
||||
|
||||
"settings.general.section.appearance": "外觀",
|
||||
"settings.general.section.advanced": "進階",
|
||||
"settings.general.section.notifications": "系統通知",
|
||||
"settings.general.section.updates": "更新",
|
||||
"settings.general.section.sounds": "音效",
|
||||
|
|
@ -627,6 +745,10 @@ export const dict = {
|
|||
|
||||
"settings.general.row.language.title": "語言",
|
||||
"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.description": "自訂 OpenCode 在你的裝置上的外觀",
|
||||
"settings.general.row.colorScheme.title": "配色方案",
|
||||
|
|
@ -635,14 +757,28 @@ export const dict = {
|
|||
"settings.general.row.theme.description": "自訂 OpenCode 的主題。",
|
||||
"settings.general.row.font.title": "程式碼字型",
|
||||
"settings.general.row.font.description": "自訂程式碼區塊使用的字型",
|
||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
||||
"settings.general.row.terminalFont.title": "終端機字型",
|
||||
"settings.general.row.terminalFont.description": "自訂終端機使用的字型",
|
||||
"settings.general.row.uiFont.title": "介面字型",
|
||||
"settings.general.row.uiFont.description": "自訂整個介面使用的字型",
|
||||
"settings.general.row.followup.title": "後續追問行為",
|
||||
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
|
||||
"settings.general.row.followup.option.queue": "佇列",
|
||||
"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.description": "在時間軸中顯示模型推理摘要",
|
||||
|
||||
|
|
@ -650,6 +786,10 @@ export const dict = {
|
|||
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
|
||||
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
|
||||
"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.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
||||
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
||||
|
|
@ -663,6 +803,8 @@ export const dict = {
|
|||
"settings.updates.row.check.description": "手動檢查更新並在有更新時安裝",
|
||||
"settings.updates.action.checkNow": "立即檢查",
|
||||
"settings.updates.action.checking": "檢查中...",
|
||||
"settings.updates.action.downloading": "下載中...",
|
||||
"settings.updates.action.installing": "安裝中...",
|
||||
"settings.updates.toast.latest.title": "已是最新版本",
|
||||
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,26 @@
|
|||
|
||||
[data-slot="model-selector-scroll"]::before {
|
||||
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 {
|
||||
|
|
@ -239,7 +258,26 @@
|
|||
|
||||
[data-slot="model-selector-scroll"]::after {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import { usePlatform } from "@/context/platform"
|
|||
import { DateTime } from "luxon"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
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 { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
||||
import { ServerConnection, serverName, useServer } from "@/context/server"
|
||||
|
|
@ -267,7 +267,7 @@ export function NewHome() {
|
|||
const command = useCommand()
|
||||
const notification = useNotification()
|
||||
const marked = useMarked()
|
||||
const openSettings = useSettingsCommand()
|
||||
const openSettings = useSettingsDialog()
|
||||
let focusSessionSearch: (() => void) | undefined
|
||||
const [state, setState] = createStore({
|
||||
search: "",
|
||||
|
|
@ -1278,7 +1278,7 @@ function HomeSessionSearch(props: {
|
|||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/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" />
|
||||
<input
|
||||
ref={input}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
|||
import { usePlatform } from "@/context/platform"
|
||||
import { setNavigate } from "@/utils/notification-click"
|
||||
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||
|
||||
export default function NewLayout(props: ParentProps) {
|
||||
const platform = usePlatform()
|
||||
const navigate = useNavigate()
|
||||
setNavigate(navigate)
|
||||
useSettingsCommand()
|
||||
|
||||
createEffect(() => setV2Toast(true))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { createMemo, type Accessor } from "solid-js"
|
|||
import { useGlobal } from "@/context/global"
|
||||
import { useNotification } from "@/context/notification"
|
||||
import { usePermission } from "@/context/permission"
|
||||
import { sessionPermissionRequest, sessionQuestionRequest } from "@/pages/session/composer/session-request-tree"
|
||||
import { ServerConnection } from "@/context/server"
|
||||
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
|
||||
|
||||
export function useSessionTabAvatarState(
|
||||
server: Accessor<ServerConnection.Key>,
|
||||
|
|
@ -26,15 +26,20 @@ export function useSessionTabAvatarState(
|
|||
return !permission.autoResponds(item, directory())
|
||||
})
|
||||
})
|
||||
const unread = createMemo(() => {
|
||||
if (hasPermissions()) return true
|
||||
if (!connection()) return false
|
||||
return notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0
|
||||
const hasQuestions = createMemo(() => {
|
||||
const serverSync = sync()
|
||||
if (!serverSync) return false
|
||||
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 serverSync = sync()
|
||||
if (!serverSync) return false
|
||||
if (hasPermissions()) return false
|
||||
if (needsAttention()) return false
|
||||
return serverSync.session.data.session_working(sessionId())
|
||||
})
|
||||
return { unread, loading }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|||
import { NewSessionDesignView } from "@/components/session"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
import { StatusPopoverV2 } from "@/components/status-popover"
|
||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||
import {
|
||||
PromptProjectAddButton,
|
||||
PromptProjectSelector,
|
||||
|
|
@ -45,7 +44,6 @@ export default function NewSessionPage() {
|
|||
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
|
||||
|
||||
useComposerCommands()
|
||||
useSettingsCommand()
|
||||
|
||||
let inputRef: HTMLDivElement | undefined
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b
|
|||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { showToast } from "@/utils/toast"
|
||||
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 { ErrorPage } from "@/pages/error"
|
||||
import { CommentsProvider, useComments } from "@/context/comments"
|
||||
|
|
@ -56,7 +56,6 @@ import { useSync } from "@/context/sync"
|
|||
import { useTabs } from "@/context/tabs"
|
||||
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||
import {
|
||||
createPromptInputController,
|
||||
|
|
@ -153,7 +152,7 @@ export function TargetSessionRouteContent() {
|
|||
)
|
||||
}
|
||||
|
||||
function SessionRouteErrorBoundary(
|
||||
export function SessionRouteErrorBoundary(
|
||||
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
||||
) {
|
||||
const settings = useSettings()
|
||||
|
|
@ -221,7 +220,6 @@ function SessionErrorFallback(props: { error: unknown; sessionID?: string; serve
|
|||
|
||||
function ResolvedTargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
const settings = useSettings()
|
||||
const tabs = useTabs()
|
||||
const sync = useServerSync()
|
||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||
|
|
@ -248,16 +246,11 @@ function ResolvedTargetSessionRoute() {
|
|||
the terminal. Same-workspace tab switches keep it open because warm
|
||||
targets resolve synchronously from the sync cache. */}
|
||||
<Show when={directory()}>
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
||||
>
|
||||
<SDKProvider directory={targetDirectory}>
|
||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||
<TargetSessionPage />
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</Show>
|
||||
<SDKProvider directory={targetDirectory}>
|
||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||
<TargetSessionPage />
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</Show>
|
||||
</TargetServerScopedProviders>
|
||||
)
|
||||
|
|
@ -1021,7 +1014,6 @@ export default function Page() {
|
|||
}
|
||||
|
||||
useComposerCommands()
|
||||
useSettingsCommand()
|
||||
useSessionCommands({
|
||||
navigateMessageByOffset,
|
||||
setActiveMessage,
|
||||
|
|
|
|||
|
|
@ -160,8 +160,10 @@ function TimelineDiffSummaryRow(props: { diffs: SummaryDiff[] }) {
|
|||
>
|
||||
<div data-slot="session-turn-diffs-header">
|
||||
<span data-slot="session-turn-diffs-label">
|
||||
{props.diffs.length} {language.t("ui.sessionTurn.diffs.changed")}{" "}
|
||||
{language.t(props.diffs.length === 1 ? "ui.common.file.one" : "ui.common.file.other")}
|
||||
{language.t(
|
||||
props.diffs.length === 1 ? "ui.sessionTurn.diffs.changed.one" : "ui.sessionTurn.diffs.changed.other",
|
||||
{ count: String(props.diffs.length) },
|
||||
)}
|
||||
</span>
|
||||
<DiffChanges changes={props.diffs} />
|
||||
<Show when={overflow() > 0}>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ import { createSessionTabs } from "@/pages/session/helpers"
|
|||
import { extractPromptFromParts } from "@/utils/prompt"
|
||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||
import { useTabs } from "@/context/tabs"
|
||||
import { requireServerKey } from "@/utils/session-route"
|
||||
import { createSessionOwnership } from "./session-ownership"
|
||||
|
||||
export type SessionCommandContext = {
|
||||
|
|
@ -48,7 +46,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
const settings = useSettings()
|
||||
const sync = useSync()
|
||||
const terminal = useTerminal()
|
||||
const sessionTabs = useTabs()
|
||||
const layout = useLayout()
|
||||
const navigate = useNavigate()
|
||||
const { params, sessionKey, tabs, view } = useSessionLayout()
|
||||
|
|
@ -269,6 +266,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
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 = () => {
|
||||
void openDialog(
|
||||
() => import("@/components/dialog-select-mcp"),
|
||||
|
|
@ -411,9 +416,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
title: language.t("command.session.new"),
|
||||
keybind: "mod+shift+s",
|
||||
slash: "new",
|
||||
onSelect: () => {
|
||||
if (params.serverKey) {
|
||||
sessionTabs.newDraft({ server: requireServerKey(params.serverKey), directory: sdk().directory })
|
||||
onSelect: (source) => {
|
||||
if (settings.general.newLayoutDesigns()) {
|
||||
command.trigger("tab.new", source)
|
||||
return
|
||||
}
|
||||
navigate(`/${params.dir}/session`)
|
||||
|
|
@ -518,6 +523,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
]
|
||||
|
||||
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({
|
||||
id: "terminal.new",
|
||||
title: language.t("command.terminal.new"),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
|
@ -46,10 +46,12 @@
|
|||
"effect": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "catalog:",
|
||||
"semver": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
"strip-ansi": "7.1.2"
|
||||
"strip-ansi": "7.1.2",
|
||||
"uqr": "0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -3,27 +3,13 @@ import { Spec } from "../framework/spec"
|
|||
|
||||
declare const OPENCODE_CLI_NAME: string | undefined
|
||||
|
||||
const MiniParams = {
|
||||
continue: Flag.boolean("continue").pipe(
|
||||
Flag.withAlias("c"),
|
||||
Flag.withDescription("Continue the last session"),
|
||||
const ServerParams = {
|
||||
standalone: Flag.boolean("standalone").pipe(
|
||||
Flag.withDescription("Run with a private server instead of the background service"),
|
||||
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"),
|
||||
server: Flag.string("server").pipe(
|
||||
Flag.withDescription("Connect to a server URL instead of the background service"),
|
||||
Flag.optional,
|
||||
),
|
||||
}
|
||||
|
|
@ -31,18 +17,11 @@ const MiniParams = {
|
|||
export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", {
|
||||
description: "OpenCode 2.0 preview command line interface",
|
||||
params: {
|
||||
...ServerParams,
|
||||
directory: Argument.string("directory").pipe(
|
||||
Argument.withDescription("Directory to start OpenCode in"),
|
||||
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(
|
||||
Flag.withAlias("c"),
|
||||
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",
|
||||
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", {
|
||||
description: "Manage MCP (Model Context Protocol) servers",
|
||||
commands: [
|
||||
|
|
@ -116,10 +106,28 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
|||
Spec.make("mini", {
|
||||
description: "Start the minimal interactive interface",
|
||||
params: {
|
||||
...MiniParams,
|
||||
project: Argument.string("project").pipe(
|
||||
Argument.withDescription("Path to start OpenCode in"),
|
||||
Argument.optional,
|
||||
...ServerParams,
|
||||
continue: Flag.boolean("continue").pipe(
|
||||
Flag.withAlias("c"),
|
||||
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(
|
||||
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),
|
||||
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),
|
||||
},
|
||||
}),
|
||||
Spec.make("run", {
|
||||
description: "Run OpenCode with a message",
|
||||
params: {
|
||||
...ServerParams,
|
||||
message: Argument.string("message").pipe(
|
||||
Argument.withDescription("Message to send"),
|
||||
Argument.variadic({ min: 0 }),
|
||||
|
|
@ -158,7 +163,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
|||
),
|
||||
model: Flag.string("model").pipe(
|
||||
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,
|
||||
),
|
||||
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),
|
||||
),
|
||||
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(
|
||||
Flag.withDescription("Show thinking blocks"),
|
||||
Flag.withDefault(false),
|
||||
|
|
@ -187,10 +186,6 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
|||
Flag.withDefault(false),
|
||||
),
|
||||
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", {
|
||||
|
|
@ -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", {
|
||||
description: "Start the v2 API server",
|
||||
params: {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ export default Runtime.handler(
|
|||
Effect.fn("cli.api")(function* (input) {
|
||||
const options = yield* ServiceConfig.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 request = yield* resolveRequest(transport, input.request, params)
|
||||
const headers = new Headers(transport.headers)
|
||||
const request = yield* resolveRequest(endpoint, input.request, params)
|
||||
const headers = new Headers(Service.headers(endpoint))
|
||||
for (const header of input.header) {
|
||||
const index = header.indexOf(":")
|
||||
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")
|
||||
|
||||
const response = yield* Effect.tryPromise(() =>
|
||||
fetch(new URL(request.path, transport.url), {
|
||||
fetch(new URL(request.path, endpoint.url), {
|
||||
method: request.method,
|
||||
headers,
|
||||
body,
|
||||
|
|
@ -60,7 +60,7 @@ export function rawRequest(input: readonly string[]) {
|
|||
}
|
||||
|
||||
function resolveRequest(
|
||||
transport: Service.Transport,
|
||||
endpoint: Service.Endpoint,
|
||||
input: readonly string[],
|
||||
params: Record<string, string>,
|
||||
) {
|
||||
|
|
@ -68,7 +68,7 @@ function resolveRequest(
|
|||
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"))
|
||||
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}`)
|
||||
return resolveOperation((await response.json()) as OpenApi, input[0], params)
|
||||
})
|
||||
|
|
|
|||
117
packages/cli/src/commands/handlers/console/login.ts
Normal file
117
packages/cli/src/commands/handlers/console/login.ts
Normal 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)
|
||||
}
|
||||
|
|
@ -11,8 +11,8 @@ export default Runtime.handler(
|
|||
Effect.fn("cli.debug.agents")(function* () {
|
||||
const options = yield* ServiceConfig.options()
|
||||
const found = yield* Service.discover(options)
|
||||
const transport = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
||||
const endpoint = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const response = yield* Effect.promise(() => client.v2.agent.list({ location: { directory: process.cwd() } }))
|
||||
process.stdout.write(
|
||||
JSON.stringify(
|
||||
|
|
|
|||
|
|
@ -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 { Runtime } from "../../framework/runtime"
|
||||
import { Effect, Option, Redacted } from "effect"
|
||||
import { Service } from "@opencode-ai/client/effect"
|
||||
import { Env } from "../../env"
|
||||
import { ServiceConfig } from "../../services/service-config"
|
||||
import { Standalone } from "../../services/standalone"
|
||||
import { Effect, Option } from "effect"
|
||||
import { Server } from "../../services/server"
|
||||
import { Updater } from "../../services/updater"
|
||||
|
||||
export default Runtime.handler(Commands, (input) =>
|
||||
Effect.gen(function* () {
|
||||
const directory = Option.getOrUndefined(input.directory)
|
||||
if (directory !== undefined) process.chdir(directory)
|
||||
const requestedDirectory = Option.getOrUndefined(input.directory)
|
||||
if (requestedDirectory !== undefined) process.chdir(requestedDirectory)
|
||||
const updater = yield* Updater.Service
|
||||
yield* updater.check().pipe(Effect.forkScoped)
|
||||
const server = Option.getOrUndefined(input.server)
|
||||
if (server !== undefined && input.standalone)
|
||||
return yield* Effect.fail(new Error("--server and --standalone cannot be combined"))
|
||||
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 server = yield* Server.resolve({
|
||||
server: Option.getOrUndefined(input.server),
|
||||
standalone: input.standalone,
|
||||
})
|
||||
const { runTui } = yield* Effect.promise(() => import("../../tui"))
|
||||
// The TUI re-runs discover whenever its event stream drops. For an explicit
|
||||
// --server or a standalone child the transport is fixed, so reconnects
|
||||
// retry the same address; for the managed service discovery re-reads the
|
||||
// registration and may start a replacement.
|
||||
const serviceOptions = server === undefined && !input.standalone ? yield* ServiceConfig.options() : undefined
|
||||
// Only startup enforces the CLI version. A reconnect must accept a server
|
||||
// replaced by another client or the two clients will restart it forever.
|
||||
const reconnectOptions = serviceOptions ? { ...serviceOptions, version: undefined } : undefined
|
||||
const discover = reconnectOptions
|
||||
? () =>
|
||||
Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const found = yield* Service.discover(reconnectOptions)
|
||||
return found ?? (yield* Service.start(reconnectOptions))
|
||||
}).pipe(Effect.provide(NodeFileSystem.layer)),
|
||||
)
|
||||
: () => Promise.resolve(transport)
|
||||
// Restart the managed service in place; start() resolves once the
|
||||
// replacement is healthy and the reconnect loop reattaches on its own.
|
||||
// Only meaningful in service mode: --server is not ours to restart and a
|
||||
// standalone child cannot be respawned.
|
||||
const reload = serviceOptions
|
||||
? () =>
|
||||
Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
yield* Service.stop(serviceOptions)
|
||||
yield* Service.start(serviceOptions)
|
||||
}).pipe(Effect.provide(NodeFileSystem.layer)),
|
||||
)
|
||||
: undefined
|
||||
yield* runTui(
|
||||
transport,
|
||||
{ continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
|
||||
discover,
|
||||
reload,
|
||||
)
|
||||
const config = TuiConfig.resolve({}, { terminalSuspend: false })
|
||||
let disposeSlots: (() => void) | undefined
|
||||
const runFork = Effect.runForkWith(yield* Effect.context())
|
||||
yield* run({
|
||||
server,
|
||||
args: { continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
|
||||
config,
|
||||
log: (level, message, tags) => {
|
||||
const effect =
|
||||
level === "debug"
|
||||
? Effect.logDebug(message, tags)
|
||||
: level === "warn"
|
||||
? Effect.logWarning(message, tags)
|
||||
: level === "error"
|
||||
? Effect.logError(message, tags)
|
||||
: Effect.logInfo(message, tags)
|
||||
runFork(effect)
|
||||
},
|
||||
pluginHost: {
|
||||
async start(pluginInput) {
|
||||
disposeSlots = await loadBuiltinPlugins(pluginInput.api, pluginInput.runtime)
|
||||
},
|
||||
async dispose() {
|
||||
disposeSlots?.()
|
||||
},
|
||||
},
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
43
packages/cli/src/commands/handlers/link.ts
Normal file
43
packages/cli/src/commands/handlers/link.ts
Normal 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}`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -19,8 +19,8 @@ export default Runtime.handler(
|
|||
Effect.fn("cli.mcp.auth")(function* (input) {
|
||||
const options = yield* ServiceConfig.options()
|
||||
const found = yield* Service.discover(options)
|
||||
const transport = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
||||
const endpoint = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
|
||||
const integration = yield* resolveIntegration(client, input.name, location)
|
||||
if (!integration)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ export default Runtime.handler(
|
|||
Effect.fn("cli.mcp.list")(function* () {
|
||||
const options = yield* ServiceConfig.options()
|
||||
const found = yield* Service.discover(options)
|
||||
const transport = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
||||
const endpoint = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
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))
|
||||
if (servers.length === 0) {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ export default Runtime.handler(
|
|||
Effect.fn("cli.mcp.logout")(function* (input) {
|
||||
const options = yield* ServiceConfig.options()
|
||||
const found = yield* Service.discover(options)
|
||||
const transport = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
||||
const endpoint = found ?? (yield* Service.start(options))
|
||||
const client = createOpencodeClient({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
|
||||
const integration = yield* resolveIntegration(client, input.name, location)
|
||||
if (!integration) {
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
import { Effect, Option, Redacted } from "effect"
|
||||
import path from "node:path"
|
||||
import { Effect, Option } from "effect"
|
||||
import { Commands } from "../commands"
|
||||
import { Env } from "../../env"
|
||||
import { Runtime } from "../../framework/runtime"
|
||||
import { Server } from "../../services/server"
|
||||
|
||||
export default Runtime.handler(Commands.commands.mini, (input) =>
|
||||
Effect.gen(function* () {
|
||||
const { runMini } = yield* Effect.promise(() => import("../../mini"))
|
||||
const project = Option.getOrUndefined(input.project)
|
||||
const server = Option.getOrUndefined(input.server)
|
||||
const password = yield* Env.password
|
||||
const { runMini, validateMiniTerminal } = yield* Effect.promise(() => import("../../mini"))
|
||||
yield* Effect.promise(async () => validateMiniTerminal())
|
||||
const serverURL = Option.getOrUndefined(input.server)
|
||||
const server = yield* Server.resolve({ server: serverURL, standalone: input.standalone })
|
||||
yield* Effect.promise(() =>
|
||||
runMini({
|
||||
attach: server,
|
||||
password: password ? Redacted.value(password) : undefined,
|
||||
directory:
|
||||
server !== undefined
|
||||
? project
|
||||
: project === undefined
|
||||
? process.cwd()
|
||||
: path.resolve(process.env.PWD ?? process.cwd(), project),
|
||||
server,
|
||||
continue: input.continue,
|
||||
session: Option.getOrUndefined(input.session),
|
||||
fork: input.fork,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import { Effect, Option, Redacted } from "effect"
|
||||
import { Effect, Option } from "effect"
|
||||
import { Commands } from "../commands"
|
||||
import { Env } from "../../env"
|
||||
import { Runtime } from "../../framework/runtime"
|
||||
import { Server } from "../../services/server"
|
||||
|
||||
export default Runtime.handler(Commands.commands.run, (input) =>
|
||||
Effect.gen(function* () {
|
||||
const { runNonInteractive } = yield* Effect.promise(() => import("../../mini"))
|
||||
const password = yield* Env.password
|
||||
const separator = process.argv.indexOf("--", 2)
|
||||
const server = yield* Server.resolve({
|
||||
server: Option.getOrUndefined(input.server),
|
||||
standalone: input.standalone,
|
||||
})
|
||||
yield* Effect.promise(() =>
|
||||
runNonInteractive({
|
||||
server,
|
||||
message: [...input.message, ...(separator === -1 ? [] : process.argv.slice(separator + 1))],
|
||||
continue: input.continue,
|
||||
session: Option.getOrUndefined(input.session),
|
||||
|
|
@ -19,12 +23,8 @@ export default Runtime.handler(Commands.commands.run, (input) =>
|
|||
format: input.format,
|
||||
file: [...input.file],
|
||||
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,
|
||||
dangerouslySkipPermissions: input.auto || input.yolo || input.dangerouslySkipPermissions,
|
||||
auto: input.auto || input.yolo,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -24,6 +24,9 @@ const Handlers = Runtime.handlers(Commands, {
|
|||
debug: {
|
||||
agents: () => import("./commands/handlers/debug/agents"),
|
||||
},
|
||||
console: {
|
||||
login: () => import("./commands/handlers/console/login"),
|
||||
},
|
||||
mcp: {
|
||||
list: () => import("./commands/handlers/mcp/list"),
|
||||
add: () => import("./commands/handlers/mcp/add"),
|
||||
|
|
@ -33,6 +36,7 @@ const Handlers = Runtime.handlers(Commands, {
|
|||
migrate: () => import("./commands/handlers/migrate"),
|
||||
mini: () => import("./commands/handlers/mini"),
|
||||
run: () => import("./commands/handlers/run"),
|
||||
link: () => import("./commands/handlers/link"),
|
||||
service: {
|
||||
start: () => import("./commands/handlers/service/start"),
|
||||
restart: () => import("./commands/handlers/service/restart"),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
export { runMini, mergeInput as mergeInteractiveInput, type MiniCommandInput } from "./mini"
|
||||
export { runMini, validateMiniTerminal, mergeInput as mergeInteractiveInput, type MiniCommandInput } from "./mini"
|
||||
export {
|
||||
runNonInteractive,
|
||||
mergeInput as mergeNonInteractiveInput,
|
||||
pickRunModel,
|
||||
parseRunModel,
|
||||
type RunCommandInput,
|
||||
} from "./run"
|
||||
|
|
|
|||
|
|
@ -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 { Global } from "@opencode-ai/core/global"
|
||||
import { Effect } from "effect"
|
||||
import path from "node:path"
|
||||
import { Daemon } from "../daemon"
|
||||
import { Server } from "../services/server"
|
||||
import { waitForCatalogReady } from "./catalog.shared"
|
||||
import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./runtime.stdin"
|
||||
import type { RunInput, RunTuiConfig } from "./types"
|
||||
|
||||
export type MiniCommandInput = {
|
||||
directory?: string
|
||||
attach?: string
|
||||
password?: string
|
||||
username?: string
|
||||
server: Server.Resolved
|
||||
continue?: boolean
|
||||
session?: string
|
||||
fork?: boolean
|
||||
|
|
@ -22,58 +16,46 @@ export type MiniCommandInput = {
|
|||
replay?: boolean
|
||||
replayLimit?: number
|
||||
demo?: boolean
|
||||
serverCommand?: ReadonlyArray<string>
|
||||
tuiConfig?: RunTuiConfig | Promise<RunTuiConfig>
|
||||
}
|
||||
|
||||
type Session = Awaited<ReturnType<OpenCodeClient["session"]["get"]>>
|
||||
type Transport = { readonly url: string; readonly headers?: HeadersInit }
|
||||
|
||||
export async function runMini(input: MiniCommandInput) {
|
||||
validate(input)
|
||||
const initialInput = mergeInput(process.stdin.isTTY ? undefined : await Bun.stdin.text(), input.prompt)
|
||||
const runtimeTask = import("./runtime")
|
||||
const directory = input.attach ? input.directory : localDirectory(input.directory)
|
||||
const transportTask = startTransport(input)
|
||||
void transportTask.catch(() => {})
|
||||
const directory = localDirectory()
|
||||
|
||||
try {
|
||||
if (input.attach) await transportTask
|
||||
const sdk = OpenCode.make({
|
||||
baseUrl: "http://opencode.pending",
|
||||
fetch: deferredFetch(transportTask),
|
||||
baseUrl: input.server.endpoint.url,
|
||||
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)
|
||||
let agentTask: Promise<string | undefined> | undefined
|
||||
const resolveAgent = () => {
|
||||
agentTask ??= validateAgent(sdk, resolvedDirectory, input.agent, input.attach)
|
||||
agentTask ??= validateAgent(sdk, directory, input.agent)
|
||||
return agentTask
|
||||
}
|
||||
const resolveSession = async () => {
|
||||
const [agent, selected] = await Promise.all([
|
||||
resolveAgent(),
|
||||
selectSession(sdk, resolvedDirectory, input, attachedSession),
|
||||
selectSession(sdk, directory, input),
|
||||
])
|
||||
const readyModel =
|
||||
model ?? (selected?.model ? { providerID: selected.model.providerID, modelID: selected.model.id } : undefined)
|
||||
if (readyModel) await waitForCatalogReady({ sdk, directory: resolvedDirectory, model: readyModel })
|
||||
const session = selected ?? (await createSession(sdk, resolvedDirectory, agent, model))
|
||||
if (readyModel) await waitForCatalogReady({ sdk, directory, model: readyModel })
|
||||
const session = selected ?? (await createSession(sdk, directory, agent, model))
|
||||
return { id: session.id, title: session.title, resume: selected !== undefined }
|
||||
}
|
||||
const create = (
|
||||
_sdk: OpenCodeClient,
|
||||
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
|
||||
await runtime.runInteractiveDeferredMode({
|
||||
sdk,
|
||||
directory: resolvedDirectory,
|
||||
directory,
|
||||
resolveAgent,
|
||||
session: resolveSession,
|
||||
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. */
|
||||
export function mergeInput(piped: string | undefined, prompt: string | undefined) {
|
||||
if (!prompt) return piped || undefined
|
||||
|
|
@ -102,7 +88,7 @@ export function mergeInput(piped: string | undefined, prompt: string | undefined
|
|||
}
|
||||
|
||||
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)) {
|
||||
fail("--replay-limit must be a positive integer")
|
||||
}
|
||||
|
|
@ -110,56 +96,16 @@ function validate(input: MiniCommandInput) {
|
|||
resolveInteractiveStdin().cleanup?.()
|
||||
}
|
||||
|
||||
function localDirectory(directory?: string): string {
|
||||
function localDirectory(): string {
|
||||
const root = process.env.PWD ?? process.cwd()
|
||||
try {
|
||||
process.chdir(directory ? (path.isAbsolute(directory) ? directory : path.join(root, directory)) : root)
|
||||
process.chdir(root)
|
||||
return process.cwd()
|
||||
} 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"] {
|
||||
if (!value) return
|
||||
const [providerID, ...rest] = value.split("/")
|
||||
|
|
@ -168,7 +114,7 @@ function parseModel(value?: string): RunInput["model"] {
|
|||
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
|
||||
const deadline = Date.now() + 5_000
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
warning(`agent "${name}" not found. Falling back to default agent`)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type Input = {
|
|||
variant?: string
|
||||
thinking: boolean
|
||||
format: "default" | "json"
|
||||
dangerouslySkipPermissions: boolean
|
||||
auto: boolean
|
||||
/** True when the client is attached to a shared server rather than an exclusive in-process one. */
|
||||
attached: boolean
|
||||
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> }) => {
|
||||
if (!input.dangerouslySkipPermissions) {
|
||||
if (!input.auto) {
|
||||
permissionRejected = true
|
||||
UI.println(
|
||||
UI.Style.TEXT_WARNING_BOLD + "!",
|
||||
|
|
@ -103,10 +103,10 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
.reply({
|
||||
sessionID: input.sessionID,
|
||||
requestID: request.id,
|
||||
reply: input.dangerouslySkipPermissions ? "once" : "reject",
|
||||
reply: input.auto ? "once" : "reject",
|
||||
})
|
||||
.catch(() => {})
|
||||
if (!input.dangerouslySkipPermissions) {
|
||||
if (!input.auto) {
|
||||
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
|
||||
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) {
|
||||
promoted = true
|
||||
continue
|
||||
|
|
@ -409,10 +409,8 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
{
|
||||
sessionID: input.sessionID,
|
||||
id: messageID,
|
||||
prompt: {
|
||||
text: [input.message, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
|
||||
files: prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])),
|
||||
},
|
||||
text: [input.message, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
|
||||
files: prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])),
|
||||
delivery: "steer",
|
||||
},
|
||||
{ signal: admission.signal },
|
||||
|
|
|
|||
|
|
@ -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 { Global } from "@opencode-ai/core/global"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Model } from "@opencode-ai/schema/model"
|
||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
||||
import { Effect } from "effect"
|
||||
import { open } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { Daemon } from "../daemon"
|
||||
import { Standalone } from "../services/standalone"
|
||||
import { Server } from "../services/server"
|
||||
import { loadRunAgents, waitForCatalogReady } from "./catalog.shared"
|
||||
import { runNonInteractivePrompt } from "./noninteractive"
|
||||
import { toolInlineInfo } from "./tool"
|
||||
import { UI } from "./ui"
|
||||
|
||||
export type RunCommandInput = {
|
||||
server: Server.Resolved
|
||||
message: string[]
|
||||
continue?: boolean
|
||||
session?: string
|
||||
|
|
@ -23,14 +22,8 @@ export type RunCommandInput = {
|
|||
format: "default" | "json"
|
||||
file: string[]
|
||||
title?: string
|
||||
server?: string
|
||||
password?: string
|
||||
username?: string
|
||||
directory?: string
|
||||
variant?: string
|
||||
thinking?: boolean
|
||||
dangerouslySkipPermissions?: boolean
|
||||
standaloneCommand?: ReadonlyArray<string>
|
||||
auto?: boolean
|
||||
}
|
||||
|
||||
type FilePart = {
|
||||
|
|
@ -39,8 +32,6 @@ type FilePart = {
|
|||
mime: string
|
||||
}
|
||||
|
||||
type Transport = { readonly url: string; readonly headers?: HeadersInit }
|
||||
|
||||
type Prepared = {
|
||||
directory?: string
|
||||
message: string
|
||||
|
|
@ -56,34 +47,24 @@ export function runNonInteractive(input: RunCommandInput) {
|
|||
async function run(input: RunCommandInput) {
|
||||
if (input.fork && !input.continue && !input.session) fail("--fork requires --continue or --session")
|
||||
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())
|
||||
if (!message?.trim()) fail("You must provide a message")
|
||||
const files = await Promise.all(
|
||||
input.file.map((file) => prepareFile(file, input.server ? root : (directory ?? root), input.server !== undefined)),
|
||||
)
|
||||
const files = await Promise.all(input.file.map((file) => prepareFile(file, root)))
|
||||
const prepared = { directory, message, files }
|
||||
if (input.standaloneCommand)
|
||||
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)
|
||||
return execute(input, prepared, input.server.endpoint)
|
||||
}
|
||||
|
||||
async function execute(input: RunCommandInput, prepared: Prepared, transport: Transport) {
|
||||
const client = OpenCode.make({ baseUrl: transport.url, headers: transport.headers })
|
||||
async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Service.Endpoint) {
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const requestedDirectory = prepared.directory ?? (await client.location.get()).directory
|
||||
if (!requestedDirectory) fail("Failed to resolve server directory")
|
||||
const session = await selectSession(client, requestedDirectory, input)
|
||||
const cwd = session?.location.directory ?? requestedDirectory
|
||||
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 defaultModel =
|
||||
!explicitModel && !sessionModel
|
||||
|
|
@ -91,8 +72,8 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
|
|||
.default({ location: { directory: cwd, workspace } })
|
||||
.then((result) => (result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined))
|
||||
: undefined
|
||||
const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel)
|
||||
if (input.variant && !model)
|
||||
const model = pickRunModel(explicitModel, variant, sessionModel, defaultModel)
|
||||
if (variant && !model)
|
||||
return reportError(input, "Cannot select a variant before selecting a model", session?.id)
|
||||
if (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))
|
||||
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 =
|
||||
session ??
|
||||
(await client.session.create({
|
||||
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 },
|
||||
}))
|
||||
if (!session && input.title !== undefined) {
|
||||
|
|
@ -122,11 +103,11 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
|
|||
files: prepared.files,
|
||||
agent,
|
||||
model,
|
||||
variant: input.variant,
|
||||
variant,
|
||||
thinking: input.thinking ?? false,
|
||||
format: input.format,
|
||||
dangerouslySkipPermissions: input.dangerouslySkipPermissions ?? false,
|
||||
attached: !input.standaloneCommand,
|
||||
auto: input.auto ?? false,
|
||||
attached: true,
|
||||
renderTool,
|
||||
renderToolError,
|
||||
}).catch((error) => reportError(input, error instanceof Error ? error.message : String(error), selected.id))
|
||||
|
|
@ -154,47 +135,29 @@ function formatMessage(message: string[]) {
|
|||
return value || undefined
|
||||
}
|
||||
|
||||
function localDirectory(directory: string | undefined, root: string) {
|
||||
function localDirectory(root: string) {
|
||||
try {
|
||||
process.chdir(directory ? (path.isAbsolute(directory) ? directory : path.join(root, directory)) : root)
|
||||
process.chdir(root)
|
||||
return process.cwd()
|
||||
} catch {
|
||||
fail(`Failed to change directory to ${directory}`)
|
||||
fail(`Failed to change directory to ${root}`)
|
||||
}
|
||||
}
|
||||
|
||||
function startTransport(input: RunCommandInput) {
|
||||
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) {
|
||||
export function parseRunModel(value?: string) {
|
||||
if (!value) return
|
||||
const [providerID, ...rest] = value.split("/")
|
||||
const modelID = rest.join("/")
|
||||
if (!providerID || !modelID) fail("--model must use the format provider/model")
|
||||
return { providerID, modelID }
|
||||
const ref = Model.Ref.parse(value)
|
||||
return {
|
||||
model: { providerID: ref.providerID, modelID: ref.id },
|
||||
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
|
||||
const agents = await loadRunAgents(client, directory).catch(() => undefined)
|
||||
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
|
||||
}
|
||||
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 })
|
||||
}
|
||||
|
||||
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 handle = await open(file, "r").catch(() => fail(`File not found: ${input}`))
|
||||
try {
|
||||
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)
|
||||
fail(`Cannot attach a directory, special file, or file larger than 10 MiB: ${input}`)
|
||||
const content = Buffer.alloc(Number(stat.size))
|
||||
|
|
|
|||
|
|
@ -457,12 +457,13 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
}
|
||||
|
||||
const reduce = (child: ChildState, event: V2Event) => {
|
||||
if (event.type === "session.prompt.admitted") {
|
||||
child.prompts.set(event.data.inputID, event.data.prompt.text)
|
||||
if (event.type === "session.input.admitted") {
|
||||
if (event.data.input.type === "user") child.prompts.set(event.data.inputID, event.data.input.data.text)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.prompt.promoted") {
|
||||
const prompt = child.prompts.get(event.data.inputID) ?? ""
|
||||
if (event.type === "session.input.promoted") {
|
||||
const prompt = child.prompts.get(event.data.inputID)
|
||||
if (prompt === undefined) return
|
||||
child.prompts.delete(event.data.inputID)
|
||||
if (userFrame(child, event.data.inputID, prompt)) {
|
||||
touch(child, event.created)
|
||||
|
|
|
|||
|
|
@ -562,7 +562,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
}
|
||||
input.trace?.write("recv.event", 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
|
||||
state.messageIDs.add(event.data.inputID)
|
||||
write([], { phase: "running", status: "waiting for assistant" })
|
||||
|
|
@ -1052,11 +1052,9 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
{
|
||||
sessionID: input.sessionID,
|
||||
id: messageID,
|
||||
prompt: {
|
||||
text: [next.prompt.text, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
|
||||
files: attachments.length ? attachments : undefined,
|
||||
agents: agents.length ? agents : undefined,
|
||||
},
|
||||
text: [next.prompt.text, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
|
||||
files: attachments.length ? attachments : undefined,
|
||||
agents: agents.length ? agents : undefined,
|
||||
delivery: "steer",
|
||||
},
|
||||
{ signal: next.signal },
|
||||
|
|
|
|||
77
packages/cli/src/services/server.ts
Normal file
77
packages/cli/src/services/server.ts
Normal 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"
|
||||
|
|
@ -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 { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
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) {
|
||||
const password = randomBytes(32).toString("base64url")
|
||||
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)
|
||||
if (!output) return yield* Effect.fail(new Error("Standalone server exited before reporting readiness"))
|
||||
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)),
|
||||
)
|
||||
|
||||
export function transport(options: Options = {}) {
|
||||
return makeTransport(options)
|
||||
export function start(options: Options = {}) {
|
||||
return makeEndpoint(options)
|
||||
}
|
||||
|
||||
export * as Standalone from "./standalone"
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
242
packages/cli/src/ui/timeline.tsx
Normal file
242
packages/cli/src/ui/timeline.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { Service } from "@opencode-ai/client/effect"
|
||||
import path from "node:path"
|
||||
import { Standalone } from "../../src/services/standalone"
|
||||
|
||||
|
|
@ -7,9 +8,11 @@ process.argv[1] = path.join(import.meta.dir, "../../src/index.ts")
|
|||
await Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const transport = yield* Standalone.transport()
|
||||
const response = yield* Effect.promise(() => fetch(new URL("/api/health", transport.url), { headers: transport.headers }))
|
||||
console.log(`${transport.pid} ${transport.url} ${response.status}`)
|
||||
const endpoint = yield* Standalone.start()
|
||||
const response = yield* Effect.promise(() =>
|
||||
fetch(new URL("/api/health", endpoint.url), { headers: Service.headers(endpoint) }),
|
||||
)
|
||||
console.log(`${endpoint.pid} ${endpoint.url} ${response.status}`)
|
||||
return yield* Effect.never
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
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[]) {
|
||||
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" })
|
||||
})
|
||||
|
||||
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 () => {
|
||||
const result = await cli(["--help"])
|
||||
|
||||
|
|
@ -52,6 +58,7 @@ describe("mini command", () => {
|
|||
|
||||
expect(result.exitCode).toBe(0)
|
||||
expect(result.stdout).toContain("--server string")
|
||||
expect(result.stdout).not.toContain("--variant")
|
||||
expect(result.stdout).not.toContain("--attach")
|
||||
expect(result.stdout).not.toContain("--command")
|
||||
})
|
||||
|
|
@ -71,6 +78,8 @@ describe("mini command", () => {
|
|||
const url = new URL(request.url)
|
||||
if (url.pathname === "/api/health")
|
||||
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") {
|
||||
modelRequests++
|
||||
return Response.json({
|
||||
|
|
@ -87,8 +96,6 @@ describe("mini command", () => {
|
|||
"run",
|
||||
"--server",
|
||||
server.url.toString(),
|
||||
"--dir",
|
||||
process.cwd(),
|
||||
"--model",
|
||||
"definitely/missing",
|
||||
"hi",
|
||||
|
|
@ -104,7 +111,8 @@ describe("mini command", () => {
|
|||
test("reports pre-admission errors as JSON", async () => {
|
||||
const server = Bun.serve({
|
||||
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 })
|
||||
},
|
||||
})
|
||||
|
|
@ -116,7 +124,7 @@ describe("mini command", () => {
|
|||
expect(JSON.parse(result.stdout)).toMatchObject({
|
||||
type: "error",
|
||||
sessionID: "",
|
||||
error: { type: "unknown", message: "Failed to resolve server directory" },
|
||||
error: { type: "unknown", message: "UnexpectedStatus" },
|
||||
})
|
||||
} finally {
|
||||
server.stop(true)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -11,9 +11,13 @@ import { join } from "node:path"
|
|||
// is all a client needs to connect. The daemon's own configuration (port,
|
||||
// persisted password) is CLI-owned and never read here.
|
||||
|
||||
export type Transport = {
|
||||
export type Endpoint = {
|
||||
readonly url: string
|
||||
readonly headers?: RequestInit["headers"]
|
||||
readonly auth?: {
|
||||
readonly type: "basic"
|
||||
readonly username: string
|
||||
readonly password: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Options = {
|
||||
|
|
@ -31,7 +35,7 @@ export type Options = {
|
|||
// Read-only lookup: registration file plus health check and version gate.
|
||||
// Never spawns; escalation to start() is the caller's policy.
|
||||
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) {
|
||||
|
|
@ -72,7 +76,7 @@ export const start = Effect.fn("service.start")(function* (options: Options = {}
|
|||
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.mapError(() => new Error("Failed to start server")),
|
||||
)
|
||||
|
|
@ -90,8 +94,9 @@ function fallback() {
|
|||
return join(state, "opencode", "service.json")
|
||||
}
|
||||
|
||||
function auth(password: string): RequestInit["headers"] {
|
||||
return { authorization: "Basic " + btoa("opencode:" + password) }
|
||||
export function headers(endpoint: Endpoint): RequestInit["headers"] {
|
||||
if (endpoint.auth === undefined) return undefined
|
||||
return { authorization: "Basic " + btoa(endpoint.auth.username + ":" + endpoint.auth.password) }
|
||||
}
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
|
|
@ -120,14 +125,20 @@ const read = Effect.fnUntraced(function* (file?: string) {
|
|||
|
||||
type LocalService = {
|
||||
readonly info: Info
|
||||
readonly transport: Transport
|
||||
readonly endpoint: Endpoint
|
||||
}
|
||||
|
||||
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(() =>
|
||||
fetch(new URL("/api/health", info.url), {
|
||||
headers,
|
||||
headers: headers(endpoint),
|
||||
signal: AbortSignal.timeout(2_000),
|
||||
}),
|
||||
).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 (info.version !== undefined && health.value.version !== info.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 (
|
||||
!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))
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type {
|
||||
HealthGetOutput,
|
||||
ServerGetOutput,
|
||||
LocationGetInput,
|
||||
LocationGetOutput,
|
||||
AgentListInput,
|
||||
|
|
@ -328,6 +329,13 @@ export function make(options: ClientOptions) {
|
|||
requestOptions,
|
||||
),
|
||||
},
|
||||
server: {
|
||||
get: (requestOptions?: RequestOptions) =>
|
||||
request<ServerGetOutput>(
|
||||
{ method: "GET", path: `/api/server`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
location: {
|
||||
get: (input?: LocationGetInput, requestOptions?: RequestOptions) =>
|
||||
request<LocationGetOutput>(
|
||||
|
|
@ -508,7 +516,15 @@ export function make(options: ClientOptions) {
|
|||
{
|
||||
method: "POST",
|
||||
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,
|
||||
declaredStatuses: [409, 400, 404, 401],
|
||||
empty: false,
|
||||
|
|
@ -550,22 +566,24 @@ export function make(options: ClientOptions) {
|
|||
requestOptions,
|
||||
),
|
||||
synthetic: (input: SessionSyntheticInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionSyntheticOutput>(
|
||||
request<{ readonly data: SessionSyntheticOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`,
|
||||
body: {
|
||||
id: input["id"],
|
||||
text: input["text"],
|
||||
description: input["description"],
|
||||
metadata: input["metadata"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
},
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: true,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [409, 404, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
).then((value) => value.data),
|
||||
shell: (input: SessionShellInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionShellOutput>(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 ServerGetOutput = { readonly urls: ReadonlyArray<string> }
|
||||
|
||||
export type LocationGetInput = {
|
||||
readonly location?: {
|
||||
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 id?: {
|
||||
readonly id?: string | null
|
||||
readonly prompt: {
|
||||
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 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
|
||||
}["id"]
|
||||
readonly prompt: {
|
||||
readonly text: {
|
||||
readonly id?: string | null
|
||||
readonly prompt: {
|
||||
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 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
|
||||
}["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 id?: string | null
|
||||
readonly prompt: {
|
||||
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 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
|
||||
}["delivery"]
|
||||
readonly resume?: {
|
||||
readonly id?: string | null
|
||||
readonly prompt: {
|
||||
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 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
|
||||
}["resume"]
|
||||
|
|
@ -618,7 +667,10 @@ export type SessionPromptOutput = {
|
|||
readonly admittedSeq: number
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly prompt: {
|
||||
readonly timeCreated: number
|
||||
readonly promotedSeq?: number
|
||||
readonly type: "user"
|
||||
readonly data: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly data: string
|
||||
|
|
@ -632,10 +684,9 @@ export type SessionPromptOutput = {
|
|||
readonly name: string
|
||||
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
readonly timeCreated: number
|
||||
readonly promotedSeq?: number
|
||||
}
|
||||
}["data"]
|
||||
|
||||
|
|
@ -819,7 +870,10 @@ export type SessionCommandOutput = {
|
|||
readonly admittedSeq: number
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly prompt: {
|
||||
readonly timeCreated: number
|
||||
readonly promotedSeq?: number
|
||||
readonly type: "user"
|
||||
readonly data: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly data: string
|
||||
|
|
@ -833,10 +887,9 @@ export type SessionCommandOutput = {
|
|||
readonly name: string
|
||||
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
readonly timeCreated: number
|
||||
readonly promotedSeq?: number
|
||||
}
|
||||
}["data"]
|
||||
|
||||
|
|
@ -863,33 +916,72 @@ export type SessionSkillOutput = void
|
|||
|
||||
export type SessionSyntheticInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly text: {
|
||||
readonly id?: {
|
||||
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
|
||||
}["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
|
||||
}["text"]
|
||||
readonly description?: {
|
||||
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
|
||||
}["description"]
|
||||
readonly metadata?: {
|
||||
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
|
||||
}["metadata"]
|
||||
readonly resume?: {
|
||||
readonly delivery?: {
|
||||
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
|
||||
}["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
|
||||
}["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 = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
|
|
@ -906,11 +998,11 @@ export type SessionCompactInput = {
|
|||
|
||||
export type SessionCompactOutput = {
|
||||
readonly data: {
|
||||
readonly type: "compaction"
|
||||
readonly admittedSeq: number
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly timeCreated: number
|
||||
readonly type: "compaction"
|
||||
readonly handledSeq?: number
|
||||
}
|
||||
}["data"]
|
||||
|
|
@ -1227,7 +1319,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly inputID: string }
|
||||
|
|
@ -1236,28 +1328,42 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly inputID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly data: string
|
||||
readonly mime: string
|
||||
readonly source: { readonly type: "inline" } | { readonly type: "uri"; 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 delivery: "steer" | "queue"
|
||||
readonly input:
|
||||
| {
|
||||
readonly type: "user"
|
||||
readonly data: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly data: string
|
||||
readonly mime: string
|
||||
readonly source: { readonly type: "inline" } | { readonly type: "uri"; 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]: 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 sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form"
|
||||
readonly fields: ReadonlyArray<
|
||||
|
|
@ -2695,7 +2801,7 @@ export type FormRequestListOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "url"
|
||||
readonly url: string
|
||||
|
|
@ -2710,7 +2816,7 @@ export type FormListOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form"
|
||||
readonly fields: ReadonlyArray<
|
||||
|
|
@ -2807,7 +2913,7 @@ export type FormListOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "url"
|
||||
readonly url: string
|
||||
|
|
@ -2819,7 +2925,7 @@ export type FormCreateInput = {
|
|||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form" | "url"
|
||||
readonly fields?: ReadonlyArray<
|
||||
|
|
@ -2914,9 +3020,9 @@ export type FormCreateInput = {
|
|||
> | null
|
||||
readonly url?: string | null
|
||||
}["id"]
|
||||
readonly title?: {
|
||||
readonly title: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form" | "url"
|
||||
readonly fields?: ReadonlyArray<
|
||||
|
|
@ -3013,7 +3119,7 @@ export type FormCreateInput = {
|
|||
}["title"]
|
||||
readonly metadata?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form" | "url"
|
||||
readonly fields?: ReadonlyArray<
|
||||
|
|
@ -3110,7 +3216,7 @@ export type FormCreateInput = {
|
|||
}["metadata"]
|
||||
readonly mode: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form" | "url"
|
||||
readonly fields?: ReadonlyArray<
|
||||
|
|
@ -3207,7 +3313,7 @@ export type FormCreateInput = {
|
|||
}["mode"]
|
||||
readonly fields?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form" | "url"
|
||||
readonly fields?: ReadonlyArray<
|
||||
|
|
@ -3304,7 +3410,7 @@ export type FormCreateInput = {
|
|||
}["fields"]
|
||||
readonly url?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form" | "url"
|
||||
readonly fields?: ReadonlyArray<
|
||||
|
|
@ -3406,7 +3512,7 @@ export type FormCreateOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form"
|
||||
readonly fields: ReadonlyArray<
|
||||
|
|
@ -3503,7 +3609,7 @@ export type FormCreateOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "url"
|
||||
readonly url: string
|
||||
|
|
@ -3520,7 +3626,7 @@ export type FormGetOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "form"
|
||||
readonly fields: ReadonlyArray<
|
||||
|
|
@ -3617,7 +3723,7 @@ export type FormGetOutput = {
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly mode: "url"
|
||||
readonly url: string
|
||||
|
|
@ -4586,7 +4692,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly inputID: string }
|
||||
|
|
@ -4595,28 +4701,42 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly inputID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly data: string
|
||||
readonly mime: string
|
||||
readonly source: { readonly type: "inline" } | { readonly type: "uri"; 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 delivery: "steer" | "queue"
|
||||
readonly input:
|
||||
| {
|
||||
readonly type: "user"
|
||||
readonly data: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly data: string
|
||||
readonly mime: string
|
||||
readonly source: { readonly type: "inline" } | { readonly type: "uri"; 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]: 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 sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly mode: "form"
|
||||
readonly fields: ReadonlyArray<
|
||||
|
|
@ -5436,7 +5556,7 @@ export type EventSubscribeOutput =
|
|||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly title?: string
|
||||
readonly title: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly mode: "url"
|
||||
readonly url: string
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
|
|||
expect(ProjectV2.Current).toBe(Project.Current)
|
||||
expect(ProjectV2.Directory).toBe(Project.Directory)
|
||||
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(Api.groups["server.session"].identifier).toBe("server.session")
|
||||
expect(Api.groups["server.project"].identifier).toBe("server.project")
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
|
|||
})
|
||||
const admitted = yield* client.session.prompt({
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
prompt: Prompt.make({ text: "Hello" }),
|
||||
text: "Hello",
|
||||
resume: false,
|
||||
})
|
||||
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(result.created.id).toBe("ses_test")
|
||||
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(result.context).toEqual([])
|
||||
expect(logQueries[0]).toEqual({ after: "0" })
|
||||
|
|
@ -259,7 +259,8 @@ const admission = {
|
|||
admittedSeq: 0,
|
||||
id: "msg_test",
|
||||
sessionID: "ses_test",
|
||||
prompt: { text: "Hello" },
|
||||
type: "user",
|
||||
data: { text: "Hello" },
|
||||
delivery: "steer",
|
||||
timeCreated: 1_717_171_717_000,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ test("exposes every standard HTTP API group", () => {
|
|||
|
||||
expect(Object.keys(client)).toEqual([
|
||||
"health",
|
||||
"server",
|
||||
"location",
|
||||
"agent",
|
||||
"plugin",
|
||||
|
|
@ -45,6 +46,21 @@ test("exposes every standard HTTP API group", () => {
|
|||
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 () => {
|
||||
let request: Request | undefined
|
||||
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("/synthetic")) return Response.json(syntheticAdmission)
|
||||
if (url.endsWith("/compact")) return Response.json(compactionAdmission)
|
||||
if (url.includes("/context")) return Response.json({ data: [] })
|
||||
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({
|
||||
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,
|
||||
})
|
||||
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(created.id).toBe("ses_test")
|
||||
expect(admitted.id).toBe("msg_test")
|
||||
expect(synthetic).toMatchObject({ type: "synthetic", data: { text: "Completed" }, delivery: "queue" })
|
||||
expect(context).toEqual([])
|
||||
expect(log).toEqual([modelSwitchedEvent, synced])
|
||||
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/model"],
|
||||
["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/wait"],
|
||||
["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
|
||||
if (typeof body !== "string") throw new Error("Expected JSON request body")
|
||||
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,
|
||||
})
|
||||
})
|
||||
|
|
@ -376,12 +408,25 @@ const admission = {
|
|||
admittedSeq: 0,
|
||||
id: "msg_test",
|
||||
sessionID: "ses_test",
|
||||
prompt: { text: "Hello" },
|
||||
type: "user",
|
||||
data: { text: "Hello" },
|
||||
delivery: "steer",
|
||||
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 = {
|
||||
data: {
|
||||
type: "compaction",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
28
packages/codemode/test/LICENSE.test262
Normal file
28
packages/codemode/test/LICENSE.test262
Normal 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
|
||||
325
packages/codemode/test/array-callbacks-test262.test.ts
Normal file
325
packages/codemode/test/array-callbacks-test262.test.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
})
|
||||
323
packages/codemode/test/array-core-test262.test.ts
Normal file
323
packages/codemode/test/array-core-test262.test.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -42,11 +42,6 @@ describe("H2: string property access reads as undefined (not a throw)", () => {
|
|||
test("unknown property on a number is undefined", async () => {
|
||||
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)", () => {
|
||||
|
|
@ -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)
|
||||
})
|
||||
|
||||
test("supported array methods and indexing still work", async () => {
|
||||
expect(await value(`return [1,2,3].map(x => x + 1)`)).toEqual([2, 3, 4])
|
||||
test("array indexing still works", async () => {
|
||||
expect(await value(`return [1,2,3][9] === undefined`)).toBe(true)
|
||||
expect(await value(`return [1,2,3][9]`)).toBeNull()
|
||||
})
|
||||
|
|
@ -202,9 +196,6 @@ describe("Error values and instanceof", () => {
|
|||
"TypeError",
|
||||
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([
|
||||
"SyntaxError",
|
||||
true,
|
||||
|
|
@ -263,55 +254,18 @@ describe("Error values and instanceof", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
|
||||
test("sort and reverse mutate and return the receiver", async () => {
|
||||
describe("CodeMode-specific array behavior", () => {
|
||||
test("sort with a comparator mutates and returns the receiver", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const sorted = [3, 1, 2]
|
||||
const sortResult = sorted.sort((a, b) => a - b)
|
||||
const reversed = [1, 2, 3]
|
||||
const reverseResult = reversed.reverse()
|
||||
return { sorted, sameSort: sorted === sortResult, reversed, sameReverse: reversed === reverseResult }
|
||||
const input = [3, 1, 2]
|
||||
const result = input.sort((a, b) => a - b)
|
||||
return { input, same: input === result }
|
||||
`),
|
||||
).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 () => {
|
||||
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 () => {
|
||||
test("splice can replace and insert elements", 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 = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({
|
||||
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 () => {
|
||||
const err = await error(`const a = [1]; a.splice(0, 0, [a]); return a`)
|
||||
expect(err.kind).toBe("InvalidDataValue")
|
||||
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 () => {
|
||||
expect(await value(`return [...["x","y","z"].keys()]`)).toEqual([0, 1, 2])
|
||||
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 () => {
|
||||
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 () => {
|
||||
|
|
@ -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"])
|
||||
})
|
||||
|
||||
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 () => {
|
||||
const err = await error(`return [1,2,3].map(42)`)
|
||||
expect(err.message).toContain("callback")
|
||||
|
|
|
|||
|
|
@ -154,9 +154,7 @@ describe("RegExp", () => {
|
|||
).toEqual(["1", "22"])
|
||||
})
|
||||
|
||||
test("string match: non-global carries index, global lists all matches", 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"])
|
||||
test("an unmatched string pattern returns null", async () => {
|
||||
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"])
|
||||
})
|
||||
|
||||
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 () => {
|
||||
expect(
|
||||
await value(`
|
||||
|
|
@ -236,12 +227,6 @@ describe("RegExp", () => {
|
|||
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 () => {
|
||||
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")
|
||||
|
|
@ -650,9 +635,7 @@ describe("stdlib integration", () => {
|
|||
true,
|
||||
)
|
||||
expect(
|
||||
await value(
|
||||
`try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`,
|
||||
),
|
||||
await value(`try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
|
|
|
|||
580
packages/codemode/test/string-core-test262.test.ts
Normal file
580
packages/codemode/test/string-core-test262.test.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
})
|
||||
625
packages/codemode/test/string-regexp-test262.test.ts
Normal file
625
packages/codemode/test/string-regexp-test262.test.ts
Normal 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𠮷bc".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],
|
||||
},
|
||||
])
|
||||
794
packages/codemode/test/string-search-test262.test.ts
Normal file
794
packages/codemode/test/string-search-test262.test.ts
Normal 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))
|
||||
})
|
||||
}
|
||||
})
|
||||
77
packages/codemode/test/test262-array.md
Normal file
77
packages/codemode/test/test262-array.md
Normal 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.
|
||||
69
packages/codemode/test/test262-string.md
Normal file
69
packages/codemode/test/test262-string.md
Normal 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.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.14",
|
||||
"version": "1.17.15",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "b0355fd9-bf41-42e3-9dca-76107de27ecd",
|
||||
"prevIds": ["95328a41-789d-44de-9643-6ac6ecd6b4ec"],
|
||||
"id": "beb91b32-23e2-405f-99b8-1e8763db94f8",
|
||||
"prevIds": [
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
],
|
||||
"ddl": [
|
||||
{
|
||||
"name": "workspace",
|
||||
|
|
@ -1016,11 +1018,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "prompt",
|
||||
"name": "data",
|
||||
"entityType": "columns",
|
||||
"table": "session_input"
|
||||
},
|
||||
|
|
@ -1575,9 +1577,13 @@
|
|||
"table": "session_share"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1586,9 +1592,13 @@
|
|||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"columns": ["active_account_id"],
|
||||
"columns": [
|
||||
"active_account_id"
|
||||
],
|
||||
"tableTo": "account",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "SET NULL",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1597,9 +1607,13 @@
|
|||
"table": "account_state"
|
||||
},
|
||||
{
|
||||
"columns": ["aggregate_id"],
|
||||
"columns": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"tableTo": "event_sequence",
|
||||
"columnsTo": ["aggregate_id"],
|
||||
"columnsTo": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1608,9 +1622,13 @@
|
|||
"table": "event"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1619,9 +1637,13 @@
|
|||
"table": "permission"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1630,9 +1652,13 @@
|
|||
"table": "project_directory"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1641,9 +1667,13 @@
|
|||
"table": "instruction_checkpoint"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1652,9 +1682,13 @@
|
|||
"table": "instruction_entry"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1663,9 +1697,13 @@
|
|||
"table": "message"
|
||||
},
|
||||
{
|
||||
"columns": ["message_id"],
|
||||
"columns": [
|
||||
"message_id"
|
||||
],
|
||||
"tableTo": "message",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1674,9 +1712,13 @@
|
|||
"table": "part"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1685,9 +1727,13 @@
|
|||
"table": "session_input"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1696,9 +1742,13 @@
|
|||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1707,9 +1757,13 @@
|
|||
"table": "session"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1718,9 +1772,13 @@
|
|||
"table": "todo"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1729,140 +1787,184 @@
|
|||
"table": "session_share"
|
||||
},
|
||||
{
|
||||
"columns": ["email", "url"],
|
||||
"columns": [
|
||||
"email",
|
||||
"url"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "control_account_pk",
|
||||
"entityType": "pks",
|
||||
"table": "control_account"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id", "directory"],
|
||||
"columns": [
|
||||
"project_id",
|
||||
"directory"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "project_directory_pk",
|
||||
"entityType": "pks",
|
||||
"table": "project_directory"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id", "key"],
|
||||
"columns": [
|
||||
"session_id",
|
||||
"key"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "instruction_entry_pk",
|
||||
"entityType": "pks",
|
||||
"table": "instruction_entry"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id", "position"],
|
||||
"columns": [
|
||||
"session_id",
|
||||
"position"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "todo_pk",
|
||||
"entityType": "pks",
|
||||
"table": "todo"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "workspace_pk",
|
||||
"table": "workspace",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["name"],
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "data_migration_pk",
|
||||
"table": "data_migration",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "account_state_pk",
|
||||
"table": "account_state",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "account_pk",
|
||||
"table": "account",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "credential_pk",
|
||||
"table": "credential",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["aggregate_id"],
|
||||
"columns": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "event_sequence_pk",
|
||||
"table": "event_sequence",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "event_pk",
|
||||
"table": "event",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "permission_pk",
|
||||
"table": "permission",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "project_pk",
|
||||
"table": "project",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "instruction_checkpoint_pk",
|
||||
"table": "instruction_checkpoint",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "message_pk",
|
||||
"table": "message",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "part_pk",
|
||||
"table": "part",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_input_pk",
|
||||
"table": "session_input",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_message_pk",
|
||||
"table": "session_message",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_pk",
|
||||
"table": "session",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_share_pk",
|
||||
"table": "session_share",
|
||||
|
|
@ -1994,10 +2096,6 @@
|
|||
"value": "promoted_seq",
|
||||
"isExpression": false
|
||||
},
|
||||
{
|
||||
"value": "type",
|
||||
"isExpression": false
|
||||
},
|
||||
{
|
||||
"value": "delivery",
|
||||
"isExpression": false
|
||||
|
|
@ -2010,7 +2108,7 @@
|
|||
"isUnique": false,
|
||||
"where": null,
|
||||
"origin": "manual",
|
||||
"name": "session_input_session_pending_type_delivery_seq_idx",
|
||||
"name": "session_input_session_pending_delivery_seq_idx",
|
||||
"entityType": "indexes",
|
||||
"table": "session_input"
|
||||
},
|
||||
|
|
@ -2198,4 +2296,4 @@
|
|||
}
|
||||
],
|
||||
"renames": []
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +119,17 @@ export class Directory extends Schema.Class<Directory>("Config.Directory")({
|
|||
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 {
|
||||
return entries
|
||||
|
|
@ -176,16 +186,37 @@ const layer = Layer.effect(
|
|||
|
||||
const discover = Effect.fn("Config.discover")(function* () {
|
||||
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 discovered = locationIsGlobal
|
||||
? []
|
||||
: yield* fs
|
||||
.up({
|
||||
targets: [".opencode", ...names.toReversed()],
|
||||
targets: [".opencode", ".claude", ".agents", ...names.toReversed()],
|
||||
start: location.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 = [
|
||||
globalDirectory,
|
||||
...discovered
|
||||
|
|
@ -193,15 +224,18 @@ const layer = Layer.effect(
|
|||
.toReversed()
|
||||
.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(
|
||||
Effect.orDie,
|
||||
Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),
|
||||
)
|
||||
|
||||
const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie)
|
||||
return {
|
||||
entries: [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()],
|
||||
directories,
|
||||
entries: [...claude, ...agents, ...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()],
|
||||
directories: [...directories, ...claude.map((entry) => entry.path), ...agents.map((entry) => entry.path)],
|
||||
files: directPaths,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -27,12 +27,10 @@ export const Selection = Schema.Union([Short, Explicit])
|
|||
.annotate({ identifier: "Config.ModelSelection" })
|
||||
|
||||
function parse(input: string): Selection {
|
||||
const providerEnd = input.indexOf("/")
|
||||
const variantStart = input.lastIndexOf("#")
|
||||
const hasVariant = variantStart > providerEnd
|
||||
const ref = Model.Ref.parse(input)
|
||||
return {
|
||||
providerID: Provider.ID.make(input.slice(0, providerEnd)),
|
||||
model: Model.ID.make(input.slice(providerEnd + 1, hasVariant ? variantStart : undefined)),
|
||||
...(hasVariant ? { variant: Model.VariantID.make(input.slice(variantStart + 1)) } : {}),
|
||||
providerID: ref.providerID,
|
||||
model: ref.id,
|
||||
...(ref.variant ? { variant: ref.variant } : {}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ import { ConfigMarkdown } from "../markdown"
|
|||
import { FSUtil } from "../../fs-util"
|
||||
import { ConfigAgentV1 } from "../../v1/config/agent"
|
||||
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 = [
|
||||
{ pattern: "{agent,agents}/**/*.md", primary: false },
|
||||
|
|
@ -18,6 +23,11 @@ const legacySources = [
|
|||
const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
|
||||
const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.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([
|
||||
"model",
|
||||
"variant",
|
||||
|
|
@ -37,9 +47,11 @@ export const Plugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
const config = yield* Config.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const global = yield* Global.Service
|
||||
const load = Effect.fn("ConfigAgentPlugin.load")(function* () {
|
||||
return yield* Effect.forEach(yield* config.entries(), (entry) => {
|
||||
if (entry.type === "document") return Effect.succeed([entry])
|
||||
if (entry.type !== "directory") return Effect.succeed([])
|
||||
return Effect.gen(function* () {
|
||||
const files = yield* discover(fs, entry.path)
|
||||
return yield* Effect.forEach(files, (file) =>
|
||||
|
|
@ -57,11 +69,14 @@ export const Plugin = define({
|
|||
})
|
||||
const loaded = { documents: yield* load() }
|
||||
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")
|
||||
if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
|
||||
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) {
|
||||
|
|
@ -74,7 +89,7 @@ export const Plugin = define({
|
|||
|
||||
const exists = draft.get(agentID) !== undefined
|
||||
draft.update(agentID, (agent) => {
|
||||
if (!exists) agent.permissions.push(...global)
|
||||
if (!exists) agent.permissions.push(...permissions)
|
||||
if (item.model !== undefined)
|
||||
agent.model = {
|
||||
id: item.model.model,
|
||||
|
|
@ -91,7 +106,9 @@ export const Plugin = define({
|
|||
if (item.hidden !== undefined) agent.hidden = item.hidden
|
||||
if (item.color !== undefined) agent.color = item.color
|
||||
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) {
|
||||
return Effect.forEach(legacySources, (source) =>
|
||||
fs
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const Plugin = define({
|
|||
const load = Effect.fn("ConfigCommandPlugin.load")(function* () {
|
||||
return yield* Effect.forEach(yield* config.entries(), (entry) => {
|
||||
if (entry.type === "document") return Effect.succeed([{ commands: entry.info.commands }])
|
||||
if (entry.type !== "directory") return Effect.succeed([])
|
||||
return loadDirectory(fs, entry.path).pipe(
|
||||
Effect.map((commands) => [
|
||||
{ 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
Loading…
Add table
Add a link
Reference in a new issue