From 656db495d56ebf10b1d9e97391dff320c46f50e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catriel=20M=C3=BCller?= Date: Mon, 16 Mar 2026 15:03:07 -0300 Subject: [PATCH] feat: merge opencode v1.2.18 --- .github/workflows/auto-docs.yml | 1 - bun.lock | 24 ++-- package.json | 3 +- packages/app/e2e/files/file-viewer.spec.ts | 53 ++++++++ packages/app/package.json | 2 +- .../app/src/components/prompt-input/submit.ts | 3 +- .../src/components/session-context-usage.tsx | 4 +- .../session/session-context-tab.tsx | 12 +- .../components/session/session-new-view.tsx | 20 +-- .../app/src/components/settings-providers.tsx | 13 +- packages/app/src/context/global-sync.tsx | 11 +- .../app/src/context/global-sync/bootstrap.ts | 16 +-- packages/app/src/context/language.tsx | 22 +++ packages/app/src/context/local.tsx | 30 +++-- packages/app/src/i18n/fr.ts | 3 - packages/app/src/i18n/th.ts | 9 -- packages/app/src/i18n/zht.ts | 9 -- packages/app/src/pages/layout.tsx | 53 +++++--- packages/app/src/pages/layout/deep-links.ts | 32 +++-- packages/app/src/pages/layout/helpers.test.ts | 41 ++++-- .../app/src/pages/layout/sidebar-items.tsx | 34 ++--- packages/app/src/pages/session.tsx | 62 ++++++--- .../composer/session-question-dock.tsx | 26 +++- packages/app/src/pages/session/file-tabs.tsx | 1 - .../src/pages/session/message-id-from-hash.ts | 6 + .../src/pages/session/message-timeline.tsx | 6 +- .../src/pages/session/session-side-panel.tsx | 4 +- .../app/src/pages/session/terminal-panel.tsx | 16 +-- .../session/use-session-hash-scroll.test.ts | 2 +- .../pages/session/use-session-hash-scroll.ts | 38 +++--- packages/app/src/utils/notification-click.ts | 5 +- packages/app/src/utils/server-errors.test.ts | 90 +++++++++++-- packages/app/src/utils/server-errors.ts | 79 +++++++---- packages/app/tsconfig.json | 3 +- packages/desktop-electron/package.json | 2 +- .../resources/entitlements.plist | 12 -- packages/desktop-electron/src/main/index.ts | 44 +++--- packages/desktop-electron/sst-env.d.ts | 2 +- packages/desktop/src-tauri/entitlements.plist | 12 -- packages/desktop/src-tauri/src/lib.rs | 2 +- packages/extensions/zed/extension.toml | 12 +- packages/kilo-docs/lib/nav/kiloclaw.ts | 4 +- packages/kilo-docs/next.config.js | 10 +- .../kiloclaw/development-tools/github.md | 22 +-- packages/opencode/package.json | 12 +- packages/opencode/script/build.ts | 113 ++++++---------- packages/opencode/script/publish.ts | 76 +++++------ packages/opencode/src/cli/cmd/auth.ts | 126 +++++++++++------- .../src/cli/cmd/tui/routes/session/index.tsx | 21 ++- packages/opencode/src/config/config.ts | 3 +- packages/opencode/src/index.ts | 5 + packages/sdk/js/package.json | 23 +--- packages/ui/package.json | 1 + packages/ui/src/components/basic-tool.tsx | 39 +++++- packages/ui/src/components/file-icon.css | 1 + packages/ui/src/components/file-icon.tsx | 17 +-- packages/ui/src/components/markdown.tsx | 14 +- packages/ui/src/components/message-nav.css | 2 +- packages/ui/src/components/message-nav.tsx | 25 ++-- packages/ui/src/components/message-part.css | 40 ++++++ packages/ui/src/components/message-part.tsx | 107 ++++++++++++++- packages/ui/src/components/session-review.tsx | 2 - packages/ui/src/components/session-turn.css | 14 +- packages/ui/src/components/tabs.css | 10 +- packages/ui/src/components/tooltip.tsx | 2 +- packages/ui/src/context/marked.tsx | 33 +++-- packages/ui/src/i18n/ar.ts | 2 + packages/ui/src/i18n/br.ts | 2 + packages/ui/src/i18n/bs.ts | 2 + packages/ui/src/i18n/da.ts | 2 + packages/ui/src/i18n/de.ts | 2 + packages/ui/src/i18n/en.ts | 2 + packages/ui/src/i18n/es.ts | 2 + packages/ui/src/i18n/fr.ts | 2 + packages/ui/src/i18n/ja.ts | 2 + packages/ui/src/i18n/ko.ts | 2 + packages/ui/src/i18n/no.ts | 2 + packages/ui/src/i18n/pl.ts | 2 + packages/ui/src/i18n/ru.ts | 2 + packages/ui/src/i18n/th.ts | 2 + packages/ui/src/i18n/tr.ts | 2 + packages/ui/src/i18n/zh.ts | 2 + packages/ui/src/i18n/zht.ts | 2 + script/upstream/utils/config.ts | 1 - turbo.json | 2 +- 85 files changed, 1014 insertions(+), 564 deletions(-) create mode 100644 packages/app/src/pages/session/message-id-from-hash.ts diff --git a/.github/workflows/auto-docs.yml b/.github/workflows/auto-docs.yml index 7af1b3748a2..0ea386e281a 100644 --- a/.github/workflows/auto-docs.yml +++ b/.github/workflows/auto-docs.yml @@ -10,7 +10,6 @@ jobs: github.event.pull_request.merged == true && startsWith(github.event.pull_request.title, 'feat:') runs-on: ubuntu-latest - steps: - name: Send webhook safely run: | diff --git a/bun.lock b/bun.lock index f4d56ee65ac..b2262ea37fa 100644 --- a/bun.lock +++ b/bun.lock @@ -15,12 +15,13 @@ "@actions/artifact": "5.0.1", "@tsconfig/bun": "catalog:", "@types/mime-types": "3.0.1", + "@typescript/native-preview": "catalog:", "glob": "13.0.5", "husky": "9.1.7", "prettier": "3.6.2", "semver": "^7.6.0", "sst": "3.18.10", - "turbo": "2.5.6", + "turbo": "2.8.13", }, }, "packages/app": { @@ -48,7 +49,7 @@ "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", - "ghostty-web": "0.4.0", + "ghostty-web": "github:anomalyco/ghostty-web#main", "luxon": "catalog:", "marked": "catalog:", "marked-shiki": "catalog:", @@ -110,7 +111,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "7.0.46", + "version": "7.0.47", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -494,6 +495,7 @@ "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", "@solidjs/meta": "catalog:", + "@solidjs/router": "catalog:", "@typescript/native-preview": "catalog:", "dompurify": "3.3.1", "fuzzysort": "catalog:", @@ -2865,7 +2867,7 @@ "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], - "ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="], + "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="], "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], @@ -3993,19 +3995,19 @@ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "turbo": ["turbo@2.5.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.6", "turbo-darwin-arm64": "2.5.6", "turbo-linux-64": "2.5.6", "turbo-linux-arm64": "2.5.6", "turbo-windows-64": "2.5.6", "turbo-windows-arm64": "2.5.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w=="], + "turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="], - "turbo-darwin-64": ["turbo-darwin-64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A=="], + "turbo-darwin-64": ["turbo-darwin-64@2.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="], - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LyiG+rD7JhMfYwLqB6k3LZQtYn8CQQUePbpA8mF/hMLPAekXdJo1g0bUPw8RZLwQXUIU/3BU7tXENvhSGz5DPA=="], + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kI+anKcLIM4L8h+NsM7mtAUpElkCOxv5LgiQVQR8BASyDFfc8Efj5kCk3cqxuxOvIqx0sLfCX7atrHQ2kwuNJQ=="], - "turbo-linux-64": ["turbo-linux-64@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-GOcUTT0xiT/pSnHL4YD6Yr3HreUhU8pUcGqcI2ksIF9b2/r/kRHwGFcsHgpG3+vtZF/kwsP0MV8FTlTObxsYIA=="], + "turbo-linux-64": ["turbo-linux-64@2.8.13", "", { "os": "linux", "cpu": "x64" }, "sha512-j29KnQhHyzdzgCykBFeBqUPS4Wj7lWMnZ8CHqytlYDap4Jy70l4RNG46pOL9+lGu6DepK2s1rE86zQfo0IOdPw=="], - "turbo-linux-arm64": ["turbo-linux-arm64@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-10Tm15bruJEA3m0V7iZcnQBpObGBcOgUcO+sY7/2vk1bweW34LMhkWi8svjV9iDF68+KJDThnYDlYE/bc7/zzQ=="], + "turbo-linux-arm64": ["turbo-linux-arm64@2.8.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-OEl1YocXGZDRDh28doOUn49QwNe82kXljO1HXApjU0LapkDiGpfl3jkAlPKxEkGDSYWc8MH5Ll8S16Rf5tEBYg=="], - "turbo-windows-64": ["turbo-windows-64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-FyRsVpgaj76It0ludwZsNN40ytHN+17E4PFJyeliBEbxrGTc5BexlXVpufB7XlAaoaZVxbS6KT8RofLfDRyEPg=="], + "turbo-windows-64": ["turbo-windows-64@2.8.13", "", { "os": "win32", "cpu": "x64" }, "sha512-717bVk1+Pn2Jody7OmWludhEirEe0okoj1NpRbSm5kVZz/yNN/jfjbxWC6ilimXMz7xoMT3IDfQFJsFR3PMANA=="], - "turbo-windows-arm64": ["turbo-windows-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-j/tWu8cMeQ7HPpKri6jvKtyXg9K1gRyhdK4tKrrchH8GNHscPX/F71zax58yYtLRWTiK04zNzPcUJuoS0+v/+Q=="], + "turbo-windows-arm64": ["turbo-windows-arm64@2.8.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-R819HShLIT0Wj6zWVnIsYvSNtRNj1q9VIyaUz0P24SMcLCbQZIm1sV09F4SDbg+KCCumqD2lcaR2UViQ8SnUJA=="], "turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="], diff --git a/package.json b/package.json index e350051098c..1468f8ab10d 100644 --- a/package.json +++ b/package.json @@ -69,12 +69,13 @@ "@actions/artifact": "5.0.1", "@tsconfig/bun": "catalog:", "@types/mime-types": "3.0.1", + "@typescript/native-preview": "catalog:", "glob": "13.0.5", "husky": "9.1.7", "prettier": "3.6.2", "semver": "^7.6.0", "sst": "3.18.10", - "turbo": "2.5.6" + "turbo": "2.8.13" }, "dependencies": { "@aws-sdk/client-s3": "3.933.0", diff --git a/packages/app/e2e/files/file-viewer.spec.ts b/packages/app/e2e/files/file-viewer.spec.ts index bee67c7d128..49fe1baa138 100644 --- a/packages/app/e2e/files/file-viewer.spec.ts +++ b/packages/app/e2e/files/file-viewer.spec.ts @@ -101,3 +101,56 @@ test("cmd+f opens text viewer search while prompt is focused", async ({ page, go await expect(findInput).toBeVisible() await expect(findInput).toBeFocused() }) + +test("cmd+f opens text viewer search while prompt is not focused", async ({ page, gotoSession }) => { + await gotoSession() + + await page.locator(promptSelector).click() + await page.keyboard.type("/open") + + const command = page.locator('[data-slash-id="file.open"]').first() + await expect(command).toBeVisible() + await page.keyboard.press("Enter") + + const dialog = page + .getByRole("dialog") + .filter({ has: page.getByPlaceholder(/search files/i) }) + .first() + await expect(dialog).toBeVisible() + + const input = dialog.getByRole("textbox").first() + await input.fill("package.json") + + const items = dialog.locator('[data-slot="list-item"][data-key^="file:"]') + let index = -1 + await expect + .poll( + async () => { + const keys = await items.evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-key") ?? "")) + index = keys.findIndex((key) => /packages[\\/]+app[\\/]+package\.json$/i.test(key.replace(/^file:/, ""))) + return index >= 0 + }, + { timeout: 30_000 }, + ) + .toBe(true) + + const item = items.nth(index) + await expect(item).toBeVisible() + await item.click() + + await expect(dialog).toHaveCount(0) + + const tab = page.getByRole("tab", { name: "package.json" }) + await expect(tab).toBeVisible() + await tab.click() + + const viewer = page.locator('[data-component="file"][data-mode="text"]').first() + await expect(viewer).toBeVisible() + + await viewer.click() + await page.keyboard.press(`${modKey}+f`) + + const findInput = page.getByPlaceholder("Find") + await expect(findInput).toBeVisible() + await expect(findInput).toBeFocused() +}) diff --git a/packages/app/package.json b/packages/app/package.json index 24c97f05b11..ee1f04fa987 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -57,7 +57,7 @@ "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", - "ghostty-web": "0.4.0", + "ghostty-web": "github:anomalyco/ghostty-web#main", "luxon": "catalog:", "marked": "catalog:", "marked-shiki": "catalog:", diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index ed790174674..23e77c93e58 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -16,6 +16,7 @@ import { Identifier } from "@/utils/id" import { Worktree as WorktreeState } from "@/utils/worktree" import { buildRequestParts } from "./build-request-parts" import { setCursorPosition } from "./editor-dom" +import { formatServerError } from "@/utils/server-errors" type PendingPrompt = { abort: AbortController @@ -286,7 +287,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { .catch((err) => { showToast({ title: language.t("prompt.toast.commandSendFailed.title"), - description: errorMessage(err), + description: formatServerError(err, language.t, language.t("common.requestFailed")), }) restoreInput() }) diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 47030aa177e..08ae4d3194e 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -39,7 +39,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const usd = createMemo( () => - new Intl.NumberFormat(language.locale(), { + new Intl.NumberFormat(language.intl(), { style: "currency", currency: "USD", }), @@ -77,7 +77,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { {(ctx) => ( <>
- {ctx().total.toLocaleString(language.locale())} + {ctx().total.toLocaleString(language.intl())} {language.t("context.usage.tokens")}
diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index 04d44b3818e..be5118c2eaf 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -128,7 +128,7 @@ export function SessionContextTab() { const usd = createMemo( () => - new Intl.NumberFormat(language.locale(), { + new Intl.NumberFormat(language.intl(), { style: "currency", currency: "USD", }), @@ -136,7 +136,7 @@ export function SessionContextTab() { const metrics = createMemo(() => getSessionContextMetrics(messages(), sync.data.provider.all)) const ctx = createMemo(() => metrics().context) - const formatter = createMemo(() => createSessionContextFormatter(language.locale())) + const formatter = createMemo(() => createSessionContextFormatter(language.intl())) const cost = createMemo(() => { return usd().format(metrics().totalCost) @@ -200,7 +200,7 @@ export function SessionContextTab() { const stats = [ { label: "context.stats.session", value: () => info()?.title ?? params.id ?? "—" }, - { label: "context.stats.messages", value: () => counts().all.toLocaleString(language.locale()) }, + { label: "context.stats.messages", value: () => counts().all.toLocaleString(language.intl()) }, { label: "context.stats.provider", value: providerLabel }, { label: "context.stats.model", value: modelLabel }, { label: "context.stats.limit", value: () => formatter().number(ctx()?.limit) }, @@ -213,8 +213,8 @@ export function SessionContextTab() { label: "context.stats.cacheTokens", value: () => `${formatter().number(ctx()?.cacheRead)} / ${formatter().number(ctx()?.cacheWrite)}`, }, - { label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.locale()) }, - { label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.locale()) }, + { label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) }, + { label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) }, { label: "context.stats.totalCost", value: cost }, { label: "context.stats.sessionCreated", value: () => formatter().time(info()?.time.created) }, { label: "context.stats.lastActivity", value: () => formatter().time(ctx()?.message.time.created) }, @@ -307,7 +307,7 @@ export function SessionContextTab() {
{breakdownLabel(segment.key)}
-
{segment.percent.toLocaleString(language.locale())}%
+
{segment.percent.toLocaleString(language.intl())}%
)} diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx index b7a544ba9a9..f2ecd51501e 100644 --- a/packages/app/src/components/session/session-new-view.tsx +++ b/packages/app/src/components/session/session-new-view.tsx @@ -51,26 +51,26 @@ export function NewSessionView(props: NewSessionViewProps) { return (
{language.t("command.session.new")}
-
- -
+
+ +
{getDirectory(projectRoot())} {getFilename(projectRoot())}
-
- -
{label(current())}
+
+ +
{label(current())}
{(project) => ( -
- -
+
+ +
{language.t("session.new.lastModified")}  {DateTime.fromMillis(project().time.updated ?? project().time.created) - .setLocale(language.locale()) + .setLocale(language.intl()) .toRelative()}
diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index 21db62950dd..a9839758b72 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -17,6 +17,7 @@ type ProviderItem = ReturnType["connected"]>[num const PROVIDER_NOTES = [ { match: (id: string) => id === "opencode", key: "dialog.provider.opencode.note" }, + { match: (id: string) => id === "opencode-go", key: "dialog.provider.opencodeGo.tagline" }, { match: (id: string) => id === "anthropic", key: "dialog.provider.anthropic.note" }, { match: (id: string) => id.startsWith("github-copilot"), key: "dialog.provider.copilot.note" }, { match: (id: string) => id === "openai", key: "dialog.provider.openai.note" }, @@ -181,21 +182,11 @@ export const SettingsProviders: Component = () => {
{item.name} - - - {language.t("dialog.provider.opencode.tagline")} - - {language.t("dialog.provider.tag.recommended")} - <> - - {language.t("dialog.provider.opencodeGo.tagline")} - - {language.t("dialog.provider.tag.recommended")} - + {language.t("dialog.provider.tag.recommended")}
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 65483df575e..c3fc89d7d50 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -228,10 +228,7 @@ function createGlobalSync() { showToast({ variant: "error", title: language.t("toast.session.listFailed.title", { project }), - description: formatServerError(err, { - unknown: language.t("error.chain.unknown"), - invalidConfiguration: language.t("error.server.invalidConfiguration"), - }), + description: formatServerError(err, language.t), }) }) @@ -261,8 +258,7 @@ function createGlobalSync() { setStore: child[1], vcsCache: cache, loadSessions, - unknownError: language.t("error.chain.unknown"), - invalidConfigurationError: language.t("error.server.invalidConfiguration"), + translate: language.t, }) })() @@ -331,8 +327,7 @@ function createGlobalSync() { url: globalSDK.url, }), requestFailedTitle: language.t("common.requestFailed"), - unknownError: language.t("error.chain.unknown"), - invalidConfigurationError: language.t("error.server.invalidConfiguration"), + translate: language.t, formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }), setGlobalStore: setBootStore, }) diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index 7d9251d3e69..2a55aab853e 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -36,8 +36,7 @@ export async function bootstrapGlobal(input: { connectErrorTitle: string connectErrorDescription: string requestFailedTitle: string - unknownError: string - invalidConfigurationError: string + translate: (key: string, vars?: Record) => string formatMoreCount: (count: number) => string setGlobalStore: SetStoreFunction }) { @@ -91,10 +90,7 @@ export async function bootstrapGlobal(input: { const results = await Promise.allSettled(tasks) const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason) if (errors.length) { - const message = formatServerError(errors[0], { - unknown: input.unknownError, - invalidConfiguration: input.invalidConfigurationError, - }) + const message = formatServerError(errors[0], input.translate) const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : "" showToast({ variant: "error", @@ -122,8 +118,7 @@ export async function bootstrapDirectory(input: { setStore: SetStoreFunction vcsCache: VcsCache loadSessions: (directory: string) => Promise | void - unknownError: string - invalidConfigurationError: string + translate: (key: string, vars?: Record) => string }) { if (input.store.status !== "complete") input.setStore("status", "loading") @@ -145,10 +140,7 @@ export async function bootstrapDirectory(input: { showToast({ variant: "error", title: `Failed to reload ${project}`, - description: formatServerError(err, { - unknown: input.unknownError, - invalidConfiguration: input.invalidConfigurationError, - }), + description: formatServerError(err, input.translate), }) input.setStore("status", "partial") return diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index cae4b835419..2d39799543c 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -104,6 +104,26 @@ const LOCALES: readonly Locale[] = [ "tr", ] +const INTL: Record = { + en: "en", + zh: "zh-Hans", + zht: "zh-Hant", + ko: "ko", + de: "de", + es: "es", + fr: "fr", + da: "da", + ja: "ja", + pl: "pl", + ru: "ru", + ar: "ar", + no: "nb-NO", + br: "pt-BR", + th: "th", + bs: "bs", + tr: "tr", +} + const LABEL_KEY: Record = { en: "language.en", zh: "language.zh", @@ -217,6 +237,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont ) const locale = createMemo(() => normalizeLocale(store.locale)) + const intl = createMemo(() => INTL[locale()]) const dict = createMemo(() => DICT[locale()]) @@ -233,6 +254,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont return { ready, locale, + intl, locales: LOCALES, label, t, diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index ac5da60e862..75d1334a5a5 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -35,6 +35,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const agent = (() => { const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden)) + const models = useModels() + const [store, setStore] = createStore<{ current?: string }>({ @@ -53,11 +55,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ setStore("current", undefined) return } - if (name && available.some((x) => x.name === name)) { - setStore("current", name) - return - } - setStore("current", available[0].name) + const match = name ? available.find((x) => x.name === name) : undefined + const value = match ?? available[0] + if (!value) return + setStore("current", value.name) + if (!value.model) return + setModel({ + providerID: value.model.providerID, + modelID: value.model.modelID, + }) + if (value.variant) + models.variant.set({ providerID: value.model.providerID, modelID: value.model.modelID }, value.variant) }, move(direction: 1 | -1) { const available = list() @@ -71,11 +79,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const value = available[next] if (!value) return setStore("current", value.name) - if (value.model) - setModel({ - providerID: value.model.providerID, - modelID: value.model.modelID, - }) + if (!value.model) return + setModel({ + providerID: value.model.providerID, + modelID: value.model.modelID, + }) + if (value.variant) + models.variant.set({ providerID: value.model.providerID, modelID: value.model.modelID }, value.variant) }, } })() diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index f939b222814..5ca652a639b 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -551,9 +551,6 @@ export const dict = { "settings.general.row.theme.description": "Personnaliser le thème d'Kilo.", "settings.general.row.font.title": "Police", "settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code", - "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", "settings.general.row.shellToolPartsExpanded.title": "Développer les parties de l'outil shell", "settings.general.row.shellToolPartsExpanded.description": "Afficher les parties de l'outil shell développées par défaut dans la chronologie", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 35321260cb0..f07df4a2f20 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -113,7 +113,6 @@ export const dict = { "dialog.model.empty": "ไม่พบผลลัพธ์โมเดล", "dialog.model.manage": "จัดการโมเดล", "dialog.model.manage.description": "ปรับแต่งโมเดลที่จะปรากฏในตัวเลือกโมเดล", - "dialog.model.manage.provider.toggle": "สลับโมเดล {{provider}} ทั้งหมด", "dialog.model.unpaid.freeModels.title": "โมเดลฟรีที่จัดหาให้โดย Kilo", "dialog.model.unpaid.addMore.title": "เพิ่มโมเดลเพิ่มเติมจากผู้ให้บริการยอดนิยม", @@ -495,7 +494,6 @@ export const dict = { "session.review.loadingChanges": "กำลังโหลดการเปลี่ยนแปลง...", "session.review.empty": "ยังไม่มีการเปลี่ยนแปลงในเซสชันนี้", "session.review.noChanges": "ไม่มีการเปลี่ยนแปลง", - "session.review.noVcs": "ไม่พบ git VCS ดังนั้นจะไม่สามารถตรวจจับการเปลี่ยนแปลงของเซสชันได้", "session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด", "session.files.all": "ไฟล์ทั้งหมด", @@ -512,11 +510,6 @@ export const dict = { "session.todo.collapse": "ย่อ", "session.todo.expand": "ขยาย", - "session.modeSwitch.switching": "กำลังสลับไปยังโหมด {{mode}}…", - "session.modeSwitch.waiting": "รอให้งานปัจจุบันเสร็จสมบูรณ์", - "session.modeSwitch.notAvailable": "ตัวแทนไม่พร้อมใช้งาน", - "session.modeSwitch.fallback": 'ไม่พบ "{{requested}}" กำลังใช้ "{{actual}}"', - "session.new.worktree.main": "สาขาหลัก", "session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})", "session.new.worktree.create": "สร้าง worktree ใหม่", @@ -613,8 +606,6 @@ export const dict = { "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ Kilo มีธีม", "settings.general.row.font.title": "ฟอนต์", "settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด", - "settings.general.row.reasoningSummaries.title": "แสดงสรุปการให้เหตุผล", - "settings.general.row.reasoningSummaries.description": "แสดงสรุปการให้เหตุผลของโมเดลในไทม์ไลน์", "settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell", "settings.general.row.shellToolPartsExpanded.description": "แสดงส่วนเครื่องมือ shell แบบขยายตามค่าเริ่มต้นในไทม์ไลน์", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 9959f501c40..c71e66268b1 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -117,7 +117,6 @@ export const dict = { "dialog.model.empty": "找不到模型", "dialog.model.manage": "管理模型", "dialog.model.manage.description": "自訂模型選擇器中顯示的模型。", - "dialog.model.manage.provider.toggle": "切換所有 {{provider}} 模型", "dialog.model.unpaid.freeModels.title": "Kilo 提供的免費模型", "dialog.model.unpaid.addMore.title": "從熱門提供者新增更多模型", @@ -493,7 +492,6 @@ export const dict = { "session.review.loadingChanges": "正在載入變更...", "session.review.empty": "此工作階段暫無變更", "session.review.noChanges": "沒有變更", - "session.review.noVcs": "未偵測到 git VCS,因此無法偵測到工作階段變更", "session.files.selectToOpen": "選取要開啟的檔案", "session.files.all": "所有檔案", "session.files.binaryContent": "二進位檔案(無法顯示內容)", @@ -508,11 +506,6 @@ export const dict = { "session.todo.collapse": "折疊", "session.todo.expand": "展開", - "session.modeSwitch.switching": "正在切換到 {{mode}} 模式…", - "session.modeSwitch.waiting": "等待目前任務完成", - "session.modeSwitch.notAvailable": "Agent 不可用", - "session.modeSwitch.fallback": '找不到 "{{requested}}",使用 "{{actual}}"', - "session.new.worktree.main": "主分支", "session.new.worktree.mainWithBranch": "主分支 ({{branch}})", "session.new.worktree.create": "建立新的 worktree", @@ -608,8 +601,6 @@ export const dict = { "settings.general.row.theme.description": "自訂 Kilo 的主題。", "settings.general.row.font.title": "字型", "settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型", - "settings.general.row.reasoningSummaries.title": "顯示推理摘要", - "settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要", "settings.general.row.shellToolPartsExpanded.title": "展開 shell 工具區塊", "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 53e545db496..b9c8a31c9d5 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -44,6 +44,7 @@ import { playSound, soundSrc } from "@/utils/sound" import { createAim } from "@/utils/aim" import { setNavigate } from "@/utils/notification-click" import { Worktree as WorktreeState } from "@/utils/worktree" +import { setSessionHandoff } from "@/pages/session/handoff" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" @@ -67,7 +68,12 @@ import { sortedRootSessions, workspaceKey, } from "./layout/helpers" -import { collectOpenProjectDeepLinks, deepLinkEvent, drainPendingDeepLinks } from "./layout/deep-links" +import { + collectNewSessionDeepLinks, + collectOpenProjectDeepLinks, + deepLinkEvent, + drainPendingDeepLinks, +} from "./layout/deep-links" import { createInlineEditorController } from "./layout/inline-editor" import { LocalWorkspace, @@ -1182,9 +1188,20 @@ export default function Layout(props: ParentProps) { const handleDeepLinks = (urls: string[]) => { if (!server.isLocal()) return + for (const directory of collectOpenProjectDeepLinks(urls)) { openProject(directory) } + + for (const link of collectNewSessionDeepLinks(urls)) { + openProject(link.directory, false) + const slug = base64Encode(link.directory) + if (link.prompt) { + setSessionHandoff(slug, { prompt: link.prompt }) + } + const href = link.prompt ? `/${slug}/session?prompt=${encodeURIComponent(link.prompt)}` : `/${slug}/session` + navigateWithSidebarReset(href) + } } onMount(() => { @@ -1834,7 +1851,7 @@ export default function Layout(props: ParentProps) { }} style={{ width: panelProps.mobile ? undefined : `${Math.max(layout.sidebar.width() - 64, 0)}px` }} > - + {(p) => ( <>
@@ -1843,7 +1860,7 @@ export default function Layout(props: ParentProps) { renameProject(p(), next)} + onSave={(next) => renameProject(p, next)} class="text-14-medium text-text-strong truncate" displayClass="text-14-medium text-text-strong truncate" stopPropagation @@ -1852,7 +1869,7 @@ export default function Layout(props: ParentProps) { - {p().worktree.replace(homedir(), "~")} + {p.worktree.replace(homedir(), "~")}
@@ -1871,7 +1888,7 @@ export default function Layout(props: ParentProps) { icon="dot-grid" variant="ghost" data-action="project-menu" - data-project={base64Encode(p().worktree)} + data-project={base64Encode(p.worktree)} class="shrink-0 size-6 rounded-md data-[expanded]:bg-surface-base-active" classList={{ "opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100": !panelProps.mobile, @@ -1880,24 +1897,24 @@ export default function Layout(props: ParentProps) { /> - showEditProjectDialog(p())}> + showEditProjectDialog(p)}> {language.t("common.edit")} toggleProjectWorkspaces(p())} + data-project={base64Encode(p.worktree)} + disabled={p.vcs !== "git" && !layout.sidebar.workspaces(p.worktree)()} + onSelect={() => toggleProjectWorkspaces(p)} > - {layout.sidebar.workspaces(p().worktree)() + {layout.sidebar.workspaces(p.worktree)() ? language.t("sidebar.workspaces.disable") : language.t("sidebar.workspaces.enable")} @@ -1908,8 +1925,8 @@ export default function Layout(props: ParentProps) { closeProject(p().worktree)} + data-project={base64Encode(p.worktree)} + onSelect={() => closeProject(p.worktree)} > {language.t("common.close")} @@ -1934,7 +1951,7 @@ export default function Layout(props: ParentProps) { size="large" icon="plus-small" class="w-full" - onClick={() => navigateWithSidebarReset(`/${base64Encode(p().worktree)}/session`)} + onClick={() => navigateWithSidebarReset(`/${base64Encode(p.worktree)}/session`)} > {language.t("command.session.new")} @@ -1943,7 +1960,7 @@ export default function Layout(props: ParentProps) {
@@ -1958,7 +1975,7 @@ export default function Layout(props: ParentProps) { keybind={command.keybind("workspace.new")} placement="top" > - @@ -1984,7 +2001,7 @@ export default function Layout(props: ParentProps) { diff --git a/packages/app/src/pages/layout/deep-links.ts b/packages/app/src/pages/layout/deep-links.ts index ecbad271fc2..67137f17fa9 100644 --- a/packages/app/src/pages/layout/deep-links.ts +++ b/packages/app/src/pages/layout/deep-links.ts @@ -1,15 +1,17 @@ export const deepLinkEvent = "opencode:deep-link" -export const parseDeepLink = (input: string) => { +const parseUrl = (input: string) => { if (!input.startsWith("opencode://")) return if (typeof URL.canParse === "function" && !URL.canParse(input)) return - const url = (() => { - try { - return new URL(input) - } catch { - return undefined - } - })() + try { + return new URL(input) + } catch { + return + } +} + +export const parseDeepLink = (input: string) => { + const url = parseUrl(input) if (!url) return if (url.hostname !== "open-project") return const directory = url.searchParams.get("directory") @@ -17,9 +19,23 @@ export const parseDeepLink = (input: string) => { return directory } +export const parseNewSessionDeepLink = (input: string) => { + const url = parseUrl(input) + if (!url) return + if (url.hostname !== "new-session") return + const directory = url.searchParams.get("directory") + if (!directory) return + const prompt = url.searchParams.get("prompt") || undefined + if (!prompt) return { directory } + return { directory, prompt } +} + export const collectOpenProjectDeepLinks = (urls: string[]) => urls.map(parseDeepLink).filter((directory): directory is string => !!directory) +export const collectNewSessionDeepLinks = (urls: string[]) => + urls.map(parseNewSessionDeepLink).filter((link): link is { directory: string; prompt?: string } => !!link) + type OpenCodeWindow = Window & { __KILO__?: { deepLinks?: string[] diff --git a/packages/app/src/pages/layout/helpers.test.ts b/packages/app/src/pages/layout/helpers.test.ts index 4f82198595f..bd2d60bbd57 100644 --- a/packages/app/src/pages/layout/helpers.test.ts +++ b/packages/app/src/pages/layout/helpers.test.ts @@ -1,15 +1,14 @@ import { describe, expect, test } from "bun:test" -import { type Session } from "@kilocode/sdk/v2/client" -import { collectOpenProjectDeepLinks, drainPendingDeepLinks, parseDeepLink } from "./deep-links" import { - displayName, - errorMessage, - getDraggableId, - hasProjectPermissions, - latestRootSession, - syncWorkspaceOrder, - workspaceKey, -} from "./helpers" + collectNewSessionDeepLinks, + collectOpenProjectDeepLinks, + drainPendingDeepLinks, + parseDeepLink, + parseNewSessionDeepLink, +} from "./deep-links" +import { displayName, errorMessage, getDraggableId, syncWorkspaceOrder, workspaceKey } from "./helpers" +import { type Session } from "@kilocode/sdk/v2/client" +import { hasProjectPermissions, latestRootSession } from "./helpers" const session = (input: Partial & Pick) => ({ @@ -62,6 +61,28 @@ describe("layout deep links", () => { expect(result).toEqual(["/a", "/c"]) }) + test("parses new-session deep links with optional prompt", () => { + expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo")).toEqual({ directory: "/tmp/demo" }) + expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo&prompt=hello%20world")).toEqual({ + directory: "/tmp/demo", + prompt: "hello world", + }) + }) + + test("ignores new-session deep links without directory", () => { + expect(parseNewSessionDeepLink("opencode://new-session")).toBeUndefined() + expect(parseNewSessionDeepLink("opencode://new-session?directory=")).toBeUndefined() + }) + + test("collects only valid new-session deep links", () => { + const result = collectNewSessionDeepLinks([ + "opencode://new-session?directory=/a", + "opencode://open-project?directory=/b", + "opencode://new-session?directory=/c&prompt=ship%20it", + ]) + expect(result).toEqual([{ directory: "/a" }, { directory: "/c", prompt: "ship it" }]) + }) + test("drains global deep links once", () => { const target = { __KILO__: { diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index d2d83cd69c7..ac35596a070 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -1,10 +1,4 @@ -import { A, useNavigate, useParams } from "@solidjs/router" -import { useGlobalSync } from "@/context/global-sync" -import { useLanguage } from "@/context/language" -import { useLayout, type LocalProject, getAvatarColors } from "@/context/layout" -import { useNotification } from "@/context/notification" -import { usePermission } from "@/context/permission" -import { base64Encode } from "@opencode-ai/util/encode" +import type { Message, Session, TextPart, UserMessage } from "@kilocode/sdk/v2/client" import { Avatar } from "@opencode-ai/ui/avatar" import { HoverCard } from "@opencode-ai/ui/hover-card" import { Icon } from "@opencode-ai/ui/icon" @@ -12,12 +6,18 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { MessageNav } from "@opencode-ai/ui/message-nav" import { Spinner } from "@opencode-ai/ui/spinner" import { Tooltip } from "@opencode-ai/ui/tooltip" +import { base64Encode } from "@opencode-ai/util/encode" import { getFilename } from "@opencode-ai/util/path" -import { type Message, type Session, type TextPart, type UserMessage } from "@kilocode/sdk/v2/client" -import { For, Match, Show, Switch, createMemo, onCleanup, type Accessor, type JSX } from "solid-js" +import { A, useNavigate, useParams } from "@solidjs/router" +import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js" +import { useGlobalSync } from "@/context/global-sync" +import { useLanguage } from "@/context/language" +import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout" +import { useNotification } from "@/context/notification" +import { usePermission } from "@/context/permission" import { agentColor } from "@/utils/agent" -import { hasProjectPermissions } from "./helpers" import { sessionPermissionRequest } from "../session/composer/session-request-tree" +import { hasProjectPermissions } from "./helpers" const KILO_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750" @@ -229,7 +229,9 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed()) const isActive = createMemo(() => props.session.id === params.id) - const hoverPrefetch = { current: undefined as ReturnType | undefined } + const hoverPrefetch = { + current: undefined as ReturnType | undefined, + } const cancelHoverPrefetch = () => { if (hoverPrefetch.current === undefined) return clearTimeout(hoverPrefetch.current) @@ -298,17 +300,15 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { setHoverSession={props.setHoverSession} messageLabel={messageLabel} onMessageSelect={(message) => { - if (!isActive()) { + if (!isActive()) layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id) - navigate(`${props.slug}/session/${props.session.id}`) - return - } - window.history.replaceState(null, "", `#message-${message.id}`) - window.dispatchEvent(new HashChangeEvent("hashchange")) + + navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`) }} trigger={item} /> +
() + + createEffect(() => { + if (!untrack(() => prompt.ready())) return + prompt.ready() + untrack(() => { + if (params.id || !prompt.ready()) return + const text = searchParams.prompt + if (!text) return + prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length) + setSearchParams({ ...searchParams, prompt: undefined }) + }) + }) const [ui, setUi] = createStore({ pendingMessage: undefined as string | undefined, @@ -507,7 +518,10 @@ export default function Page() { () => { const msg = lastUserMessage() if (!msg) return - if (msg.agent) local.agent.set(msg.agent) + if (msg.agent) { + local.agent.set(msg.agent) + if (local.agent.current()?.model) return + } if (msg.model) local.model.set(msg.model) }, ), @@ -781,7 +795,11 @@ export default function Page() { on( sessionKey, () => { - setTree({ reviewScroll: undefined, pendingDiff: undefined, activeDiff: undefined }) + setTree({ + reviewScroll: undefined, + pendingDiff: undefined, + activeDiff: undefined, + }) }, { defer: true }, ), diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx index d64092cfe08..db1f788940e 100644 --- a/packages/app/src/pages/session/composer/session-question-dock.tsx +++ b/packages/app/src/pages/session/composer/session-question-dock.tsx @@ -8,6 +8,8 @@ import type { QuestionAnswer, QuestionRequest } from "@kilocode/sdk/v2" import { useLanguage } from "@/context/language" import { useSDK } from "@/context/sdk" +const cache = new Map() + // kilocode_change start - add onModeAction prop for mode-switching support export const SessionQuestionDock: Component<{ request: QuestionRequest @@ -21,16 +23,18 @@ export const SessionQuestionDock: Component<{ const questions = createMemo(() => props.request.questions) const total = createMemo(() => questions().length) + const cached = cache.get(props.request.id) const [store, setStore] = createStore({ - tab: 0, - answers: [] as QuestionAnswer[], - custom: [] as string[], - customOn: [] as boolean[], + tab: cached?.tab ?? 0, + answers: cached?.answers ?? ([] as QuestionAnswer[]), + custom: cached?.custom ?? ([] as string[]), + customOn: cached?.customOn ?? ([] as boolean[]), editing: false, sending: false, }) let root: HTMLDivElement | undefined + let replied = false const question = createMemo(() => questions()[store.tab]) const options = createMemo(() => question()?.options ?? []) @@ -113,6 +117,16 @@ export const SessionQuestionDock: Component<{ }) }) + onCleanup(() => { + if (replied) return + cache.set(props.request.id, { + tab: store.tab, + answers: store.answers.map((a) => (a ? [...a] : [])), + custom: store.custom.map((s) => s ?? ""), + customOn: store.customOn.map((b) => b ?? false), + }) + }) + const fail = (err: unknown) => { const message = err instanceof Error ? err.message : String(err) showToast({ title: language.t("common.requestFailed"), description: message }) @@ -125,6 +139,8 @@ export const SessionQuestionDock: Component<{ setStore("sending", true) try { await sdk.client.question.reply({ requestID: props.request.id, answers }) + replied = true + cache.delete(props.request.id) } catch (err) { fail(err) } finally { @@ -139,6 +155,8 @@ export const SessionQuestionDock: Component<{ setStore("sending", true) try { await sdk.client.question.reject({ requestID: props.request.id }) + replied = true + cache.delete(props.request.id) } catch (err) { fail(err) } finally { diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index 93264353bd4..77643789d04 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -234,7 +234,6 @@ export function FileTabContent(props: { tab: string }) { if (typeof window === "undefined") return const onKeyDown = (event: KeyboardEvent) => { - if (event.defaultPrevented) return if (tabs().active() !== props.tab) return if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return if (event.key.toLowerCase() !== "f") return diff --git a/packages/app/src/pages/session/message-id-from-hash.ts b/packages/app/src/pages/session/message-id-from-hash.ts new file mode 100644 index 00000000000..2857f4b01d6 --- /dev/null +++ b/packages/app/src/pages/session/message-id-from-hash.ts @@ -0,0 +1,6 @@ +export const messageIdFromHash = (hash: string) => { + const value = hash.startsWith("#") ? hash.slice(1) : hash + const match = value.match(/^message-(.+)$/) + if (!match) return + return match[1] +} diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index a083d044485..192c7667d9d 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -611,7 +611,7 @@ export function MessageTimeline(props: {
- + {(id) => (
@@ -646,12 +646,12 @@ export function MessageTimeline(props: { > {language.t("common.rename")} - void archiveSession(id())}> + void archiveSession(id)}> {language.t("common.archive")} dialog.show(() => )} + onSelect={() => dialog.show(() => )} > {language.t("common.delete")} diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index ad802d15d18..55c1607a093 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -331,7 +331,9 @@ export function SessionSidePanel(props: { const path = createMemo(() => file.pathFromTab(tab)) return (
- {(p) => } + + {(p) => } +
) }} diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx index cc4c17ee216..c8bfc140533 100644 --- a/packages/app/src/pages/session/terminal-panel.tsx +++ b/packages/app/src/pages/session/terminal-panel.tsx @@ -191,8 +191,8 @@ export function TerminalPanel() { {(id) => ( - - {(pty) => } + + {(pty) => } )} @@ -217,10 +217,10 @@ export function TerminalPanel() {
{(id) => ( - + {(pty) => (
- terminal.clone(id)} /> + terminal.clone(id)} />
)}
@@ -229,14 +229,14 @@ export function TerminalPanel() {
- + {(draggedId) => ( - + {(t) => (
{terminalTabLabel({ - title: t().title, - titleNumber: t().titleNumber, + title: t.title, + titleNumber: t.titleNumber, t: language.t as (key: string, vars?: Record) => string, })}
diff --git a/packages/app/src/pages/session/use-session-hash-scroll.test.ts b/packages/app/src/pages/session/use-session-hash-scroll.test.ts index 844f5451e39..7f3389baaac 100644 --- a/packages/app/src/pages/session/use-session-hash-scroll.test.ts +++ b/packages/app/src/pages/session/use-session-hash-scroll.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { messageIdFromHash } from "./use-session-hash-scroll" +import { messageIdFromHash } from "./message-id-from-hash" describe("messageIdFromHash", () => { test("parses hash with leading #", () => { diff --git a/packages/app/src/pages/session/use-session-hash-scroll.ts b/packages/app/src/pages/session/use-session-hash-scroll.ts index 680c93e4b4f..74b0ca83ce0 100644 --- a/packages/app/src/pages/session/use-session-hash-scroll.ts +++ b/packages/app/src/pages/session/use-session-hash-scroll.ts @@ -1,12 +1,9 @@ -import { createEffect, createMemo, onCleanup, onMount } from "solid-js" -import { UserMessage } from "@kilocode/sdk/v2" +import type { UserMessage } from "@kilocode/sdk/v2" +import { useLocation, useNavigate } from "@solidjs/router" +import { createEffect, createMemo, onMount } from "solid-js" +import { messageIdFromHash } from "./message-id-from-hash" -export const messageIdFromHash = (hash: string) => { - const value = hash.startsWith("#") ? hash.slice(1) : hash - const match = value.match(/^message-(.+)$/) - if (!match) return - return match[1] -} +export { messageIdFromHash } from "./message-id-from-hash" export const useSessionHashScroll = (input: { sessionKey: () => string @@ -30,13 +27,18 @@ export const useSessionHashScroll = (input: { const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i]))) let pendingKey = "" + const location = useLocation() + const navigate = useNavigate() + const clearMessageHash = () => { - if (!window.location.hash) return - window.history.replaceState(null, "", window.location.href.replace(/#.*$/, "")) + if (!location.hash) return + navigate(location.pathname + location.search, { replace: true }) } const updateHash = (id: string) => { - window.history.replaceState(null, "", `#${input.anchor(id)}`) + navigate(location.pathname + location.search + `#${input.anchor(id)}`, { + replace: true, + }) } const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => { @@ -53,6 +55,7 @@ export const useSessionHashScroll = (input: { } const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => { + console.log({ message, behavior }) if (input.currentMessageId() !== message.id) input.setActiveMessage(message) const index = messageIndex().get(message.id) ?? -1 @@ -100,7 +103,7 @@ export const useSessionHashScroll = (input: { } const applyHash = (behavior: ScrollBehavior) => { - const hash = window.location.hash.slice(1) + const hash = location.hash.slice(1) if (!hash) { input.autoScroll.forceScrollToBottom() const el = input.scroller() @@ -132,6 +135,7 @@ export const useSessionHashScroll = (input: { } createEffect(() => { + location.hash if (!input.sessionID() || !input.messagesReady()) return requestAnimationFrame(() => applyHash("auto")) }) @@ -155,7 +159,7 @@ export const useSessionHashScroll = (input: { } } - if (!targetId) targetId = messageIdFromHash(window.location.hash) + if (!targetId) targetId = messageIdFromHash(location.hash) if (!targetId) return if (input.currentMessageId() === targetId) return @@ -171,14 +175,6 @@ export const useSessionHashScroll = (input: { if (typeof window !== "undefined" && "scrollRestoration" in window.history) { window.history.scrollRestoration = "manual" } - - const handler = () => { - if (!input.sessionID() || !input.messagesReady()) return - requestAnimationFrame(() => applyHash("auto")) - } - - window.addEventListener("hashchange", handler) - onCleanup(() => window.removeEventListener("hashchange", handler)) }) return { diff --git a/packages/app/src/utils/notification-click.ts b/packages/app/src/utils/notification-click.ts index 94086c59593..316b2782062 100644 --- a/packages/app/src/utils/notification-click.ts +++ b/packages/app/src/utils/notification-click.ts @@ -7,6 +7,7 @@ export const setNavigate = (fn: (href: string) => void) => { export const handleNotificationClick = (href?: string) => { window.focus() if (!href) return - if (nav) nav(href) - else window.location.assign(href) + if (nav) return nav(href) + console.warn("notification-click: navigate function not set, falling back to window.location.assign") + window.location.assign(href) } diff --git a/packages/app/src/utils/server-errors.test.ts b/packages/app/src/utils/server-errors.test.ts index 1969d1afc27..1f53bb8cf66 100644 --- a/packages/app/src/utils/server-errors.test.ts +++ b/packages/app/src/utils/server-errors.test.ts @@ -1,8 +1,37 @@ import { describe, expect, test } from "bun:test" -import type { ConfigInvalidError } from "./server-errors" -import { formatServerError, parseReabaleConfigInvalidError } from "./server-errors" +import type { ConfigInvalidError, ProviderModelNotFoundError } from "./server-errors" +import { formatServerError, parseReadableConfigInvalidError } from "./server-errors" -describe("parseReabaleConfigInvalidError", () => { +function fill(text: string, vars?: Record) { + if (!vars) return text + return text.replace(/{{\s*(\w+)\s*}}/g, (_, key: string) => { + const value = vars[key] + if (value === undefined) return "" + return String(value) + }) +} + +function useLanguageMock() { + const dict: Record = { + "error.chain.unknown": "Erro desconhecido", + "error.chain.configInvalid": "Arquivo de config em {{path}} invalido", + "error.chain.configInvalidWithMessage": "Arquivo de config em {{path}} invalido: {{message}}", + "error.chain.modelNotFound": "Modelo nao encontrado: {{provider}}/{{model}}", + "error.chain.didYouMean": "Voce quis dizer: {{suggestions}}", + "error.chain.checkConfig": "Revise provider/model no config", + } + return { + t(key: string, vars?: Record) { + const text = dict[key] + if (!text) return key + return fill(text, vars) + }, + } +} + +const language = useLanguageMock() + +describe("parseReadableConfigInvalidError", () => { test("formats issues with file path", () => { const error = { name: "ConfigInvalidError", @@ -15,10 +44,10 @@ describe("parseReabaleConfigInvalidError", () => { }, } satisfies ConfigInvalidError - const result = parseReabaleConfigInvalidError(error) + const result = parseReadableConfigInvalidError(error, language.t) expect(result).toBe( - ["Invalid configuration", "opencode.config.ts", "settings.host: Required", "mode: Invalid"].join("\n"), + ["Arquivo de config em opencode.config.ts invalido: settings.host: Required", "mode: Invalid"].join("\n"), ) }) @@ -31,9 +60,9 @@ describe("parseReabaleConfigInvalidError", () => { }, } satisfies ConfigInvalidError - const result = parseReabaleConfigInvalidError(error) + const result = parseReadableConfigInvalidError(error, language.t) - expect(result).toBe(["Invalid configuration", "Bad value"].join("\n")) + expect(result).toBe("Arquivo de config em config invalido: Bad value") }) }) @@ -46,24 +75,57 @@ describe("formatServerError", () => { }, } satisfies ConfigInvalidError - const result = formatServerError(error) + const result = formatServerError(error, language.t) - expect(result).toBe(["Invalid configuration", "Missing host"].join("\n")) + expect(result).toBe("Arquivo de config em config invalido: Missing host") }) test("returns error messages", () => { - expect(formatServerError(new Error("Request failed with status 503"))).toBe("Request failed with status 503") + expect(formatServerError(new Error("Request failed with status 503"), language.t)).toBe( + "Request failed with status 503", + ) }) test("returns provided string errors", () => { - expect(formatServerError("Failed to connect to server")).toBe("Failed to connect to server") + expect(formatServerError("Failed to connect to server", language.t)).toBe("Failed to connect to server") }) - test("falls back to unknown", () => { - expect(formatServerError(0)).toBe("Unknown error") + test("uses translated unknown fallback", () => { + expect(formatServerError(0, language.t)).toBe("Erro desconhecido") }) test("falls back for unknown error objects and names", () => { - expect(formatServerError({ name: "ServerTimeoutError", data: { seconds: 30 } })).toBe("Unknown error") + expect(formatServerError({ name: "ServerTimeoutError", data: { seconds: 30 } }, language.t)).toBe( + "Erro desconhecido", + ) + }) + + test("formats provider model errors using provider/model", () => { + const error = { + name: "ProviderModelNotFoundError", + data: { + providerID: "openai", + modelID: "gpt-4.1", + }, + } satisfies ProviderModelNotFoundError + + expect(formatServerError(error, language.t)).toBe( + ["Modelo nao encontrado: openai/gpt-4.1", "Revise provider/model no config"].join("\n"), + ) + }) + + test("formats provider model suggestions", () => { + const error = { + name: "ProviderModelNotFoundError", + data: { + providerID: "x", + modelID: "y", + suggestions: ["x/y2", "x/y3"], + }, + } satisfies ProviderModelNotFoundError + + expect(formatServerError(error, language.t)).toBe( + ["Modelo nao encontrado: x/y", "Voce quis dizer: x/y2, x/y3", "Revise provider/model no config"].join("\n"), + ) }) }) diff --git a/packages/app/src/utils/server-errors.ts b/packages/app/src/utils/server-errors.ts index 85ebca13206..2c3a8c54dbb 100644 --- a/packages/app/src/utils/server-errors.ts +++ b/packages/app/src/utils/server-errors.ts @@ -7,28 +7,31 @@ export type ConfigInvalidError = { } } -type Label = { - unknown: string - invalidConfiguration: string -} - -const fallback: Label = { - unknown: "Unknown error", - invalidConfiguration: "Invalid configuration", -} - -function resolveLabel(labels: Partial
+ } + > + {content()} {content()} diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 577452fde03..bb809c79024 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -609,6 +609,46 @@ justify-content: center; } +[data-component="exa-tool-output"] { + width: 100%; + padding-top: 8px; + display: flex; + flex-direction: column; +} + +[data-slot="basic-tool-tool-subtitle"].exa-tool-query { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +[data-slot="exa-tool-links"] { + display: flex; + flex-direction: column; + gap: 4px; +} + +[data-slot="exa-tool-link"] { + display: block; + max-width: 100%; + color: var(--text-interactive-base); + text-decoration: underline; + text-underline-offset: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:hover { + color: var(--text-interactive-base); + } + + &:visited { + color: var(--text-interactive-base); + } +} + [data-component="todos"] { padding: 10px 0 24px 0; display: flex; diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 1cd98368d18..528d5577ccf 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -76,6 +76,7 @@ import { TextShimmer } from "./text-shimmer" import { AnimatedCountList } from "./tool-count-summary" import { ToolStatusTitle } from "./tool-status-title" import { animate } from "motion" +import { useLocation } from "@solidjs/router" function ShellSubmessage(props: { text: string; animate?: boolean }) { let widthRef: HTMLSpanElement | undefined @@ -266,6 +267,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo { title: i18n.t("ui.tool.webfetch"), subtitle: input.url, } + case "websearch": + return { + icon: "window-cursor", + title: i18n.t("ui.tool.websearch"), + subtitle: input.query, + } + case "codesearch": + return { + icon: "code", + title: i18n.t("ui.tool.codesearch"), + subtitle: input.query, + } case "task": return { icon: "task", @@ -326,6 +339,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo { } } +function urls(text: string | undefined) { + if (!text) return [] + const seen = new Set() + return [...text.matchAll(/https?:\/\/[^\s<>"'`)\]]+/g)] + .map((item) => item[0].replace(/[),.;:!?]+$/g, "")) + .filter((item) => { + if (seen.has(item)) return false + seen.add(item) + return true + }) +} + const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"]) const HIDDEN_TOOLS = new Set(["todowrite", "todoread"]) @@ -621,6 +646,32 @@ function contextToolSummary(parts: ToolPart[]) { return { read, search, list } } +function ExaOutput(props: { output?: string }) { + const links = createMemo(() => urls(props.output)) + + return ( + 0}> +
+
+ + {(url) => ( + event.stopPropagation()} + > + {url} + + )} + +
+
+
+ ) +} + export function registerPartComponent(type: string, component: PartComponent) { PART_MAPPING[type] = component } @@ -1563,11 +1614,64 @@ ToolRegistry.register({ }, }) +ToolRegistry.register({ + name: "websearch", + render(props) { + const i18n = useI18n() + const query = createMemo(() => { + const value = props.input.query + if (typeof value !== "string") return "" + return value + }) + + return ( + + + + ) + }, +}) + +ToolRegistry.register({ + name: "codesearch", + render(props) { + const i18n = useI18n() + const query = createMemo(() => { + const value = props.input.query + if (typeof value !== "string") return "" + return value + }) + + return ( + + + + ) + }, +}) + ToolRegistry.register({ name: "task", render(props) { const data = useData() const i18n = useI18n() + const location = useLocation() const childSessionId = () => props.metadata.sessionId as string | undefined const title = createMemo(() => i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool })) const description = createMemo(() => { @@ -1584,8 +1688,7 @@ ToolRegistry.register({ const direct = data.sessionHref?.(sessionId) if (direct) return direct - if (typeof window === "undefined") return - const path = window.location.pathname + const path = location.pathname const idx = path.indexOf("/session") if (idx === -1) return return `${path.slice(0, idx)}/session/${sessionId}` diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 6d288e21474..e1dc5012ed5 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -355,8 +355,6 @@ export const SessionReview = (props: SessionReviewProps) => { if (typeof window === "undefined") return const onKeyDown = (event: KeyboardEvent) => { - if (event.defaultPrevented) return - const mod = event.metaKey || event.ctrlKey if (!mod) return diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 15d7b503525..cf1e9811594 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -171,10 +171,8 @@ [data-slot="session-turn-diff-path"] { display: flex; + flex-grow: 1; min-width: 0; - align-items: baseline; - overflow: hidden; - white-space: nowrap; font-family: var(--font-family-sans); font-size: var(--font-size-small); @@ -182,24 +180,16 @@ } [data-slot="session-turn-diff-directory"] { - flex: 1 1 auto; - color: var(--text-weak); - min-width: 0; + color: var(--text-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; - unicode-bidi: plaintext; text-align: left; } [data-slot="session-turn-diff-filename"] { flex-shrink: 0; - max-width: 100%; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; color: var(--text-strong); font-weight: var(--font-weight-medium); } diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index 3a0c2dea206..fc99804d8de 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -241,26 +241,26 @@ [data-slot="tabs-trigger"] { .tab-fileicon-color, .tab-fileicon-mono { - transition: opacity 120ms ease; + pointer-events: none; } .tab-fileicon-color { - opacity: 0; + display: none; } .tab-fileicon-mono { - opacity: 1; + display: block; color: currentColor; } &[data-selected], &:hover { .tab-fileicon-color { - opacity: 1; + display: block; } .tab-fileicon-mono { - opacity: 0; + display: none; } } } diff --git a/packages/ui/src/components/tooltip.tsx b/packages/ui/src/components/tooltip.tsx index 055e504654b..63105d00fc4 100644 --- a/packages/ui/src/components/tooltip.tsx +++ b/packages/ui/src/components/tooltip.tsx @@ -47,7 +47,7 @@ export function Tooltip(props: TooltipProps) { {local.children} - + {local.children} diff --git a/packages/ui/src/context/marked.tsx b/packages/ui/src/context/marked.tsx index b5970c560fb..3cf17443742 100644 --- a/packages/ui/src/context/marked.tsx +++ b/packages/ui/src/context/marked.tsx @@ -1,6 +1,6 @@ import { marked } from "marked" import markedKatex from "marked-katex-extension" - +import markedShiki from "marked-shiki" import katex from "katex" import { bundledLanguages, type BundledLanguage } from "shiki" import { parseFilePath } from "../file-path" // kilocode_change @@ -534,9 +534,7 @@ export async function deferredHighlight( onComplete?: () => void, signal?: { aborted: boolean }, ): Promise { - const blocks = Array.from( - container.querySelectorAll("pre > code[data-lang]:not([data-highlighted])"), - ) + const blocks = Array.from(container.querySelectorAll("pre > code[data-lang]:not([data-highlighted])")) if (blocks.length === 0) { onComplete?.() return @@ -591,10 +589,13 @@ export async function deferredHighlight( resolve() } if (!highlighter.getLoadedLanguages().includes(language)) { - highlighter.loadLanguage(language as BundledLanguage).then(highlight).catch((err) => { - console.warn("Failed to load language for highlighting", language, err) - resolve() - }) + highlighter + .loadLanguage(language as BundledLanguage) + .then(highlight) + .catch((err) => { + console.warn("Failed to load language for highlighting", language, err) + resolve() + }) return } highlight() @@ -659,6 +660,22 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext( throwOnError: false, nonStandard: true, }), + markedShiki({ + async highlight(code, lang) { + const highlighter = await getSharedHighlighter({ themes: ["Kilo"], langs: [] }) + if (!(lang in bundledLanguages)) { + lang = "text" + } + if (!highlighter.getLoadedLanguages().includes(lang)) { + await highlighter.loadLanguage(lang as BundledLanguage) + } + return highlighter.codeToHtml(code, { + lang: lang || "text", + theme: "Kilo", + tabindex: false, + }) + }, + }), ) // kilocode_change end diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 380de01f7c7..e86761238fa 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -94,6 +94,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "جلب الويب", + "ui.tool.websearch": "بحث الويب", + "ui.tool.codesearch": "بحث الكود", "ui.tool.shell": "Shell", "ui.tool.patch": "تصحيح", "ui.tool.todos": "المهام", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 033c6a9b1a0..1ab4fe0daaf 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -94,6 +94,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Buscar Web", + "ui.tool.websearch": "Pesquisa na Web", + "ui.tool.codesearch": "Pesquisa de Código", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Tarefas", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 8f542cae08e..cfc08c7f08c 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -98,6 +98,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Web preuzimanje", + "ui.tool.websearch": "Pretraga weba", + "ui.tool.codesearch": "Pretraga koda", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Lista zadataka", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 233beea1097..232eb43ccd1 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -93,6 +93,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webhentning", + "ui.tool.websearch": "Websøgning", + "ui.tool.codesearch": "Kodesøgning", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Opgaver", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index f607d55e24d..6d4859800e3 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -99,6 +99,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webabruf", + "ui.tool.websearch": "Websuche", + "ui.tool.codesearch": "Codesuche", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Aufgaben", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 0fd16713ef2..55458918584 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -95,6 +95,8 @@ export const dict: Record = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Web Search", + "ui.tool.codesearch": "Code Search", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "To-dos", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 95f886e1631..a8f943fc114 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -94,6 +94,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Búsqueda web", + "ui.tool.codesearch": "Búsqueda de código", "ui.tool.shell": "Shell", "ui.tool.patch": "Parche", "ui.tool.todos": "Tareas", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index d42ad546668..4d1934234ab 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -94,6 +94,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Recherche Web", + "ui.tool.codesearch": "Recherche de code", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Tâches", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 1771f56cc1d..67093969c68 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -93,6 +93,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Web検索", + "ui.tool.codesearch": "コード検索", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Todo", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index aab6e31ab83..0d256bbe58b 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -94,6 +94,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "웹 가져오기", + "ui.tool.websearch": "웹 검색", + "ui.tool.codesearch": "코드 검색", "ui.tool.shell": "셸", "ui.tool.patch": "패치", "ui.tool.todos": "할 일", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 7ff274e63d2..03a396bed39 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -97,6 +97,8 @@ export const dict: Record = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webhenting", + "ui.tool.websearch": "Nettsøk", + "ui.tool.codesearch": "Kodesøk", "ui.tool.shell": "Shell", "ui.tool.patch": "Patch", "ui.tool.todos": "Gjøremål", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 8c16b8b1c55..1bae8a0714a 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -93,6 +93,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Pobieranie sieciowe", + "ui.tool.websearch": "Wyszukiwanie w sieci", + "ui.tool.codesearch": "Wyszukiwanie kodu", "ui.tool.shell": "Terminal", "ui.tool.patch": "Patch", "ui.tool.todos": "Zadania", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 7c09204e5cd..44bf393b59a 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -93,6 +93,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Веб-поиск", + "ui.tool.codesearch": "Поиск кода", "ui.tool.shell": "Оболочка", "ui.tool.patch": "Патч", "ui.tool.todos": "Задачи", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index baad59cfd2e..e3552f95638 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -95,6 +95,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "ดึงจากเว็บ", + "ui.tool.websearch": "ค้นหาเว็บ", + "ui.tool.codesearch": "ค้นหาโค้ด", "ui.tool.shell": "เชลล์", "ui.tool.patch": "แพตช์", "ui.tool.todos": "รายการงาน", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index 9f13d00e955..8e536c51768 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -100,6 +100,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Web getir", + "ui.tool.websearch": "Web Araması", + "ui.tool.codesearch": "Kod Araması", "ui.tool.shell": "Kabuk", "ui.tool.patch": "Yama", "ui.tool.todos": "Görevler", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 5360a086049..e603edd520d 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -98,6 +98,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "网络搜索", + "ui.tool.codesearch": "代码搜索", "ui.tool.shell": "Shell", "ui.tool.patch": "补丁", "ui.tool.todos": "待办", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 8f163e4ca47..1b186450e9d 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -98,6 +98,8 @@ export const dict = { "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "網頁搜尋", + "ui.tool.codesearch": "程式碼搜尋", "ui.tool.shell": "Shell", "ui.tool.patch": "修補", "ui.tool.todos": "待辦", diff --git a/script/upstream/utils/config.ts b/script/upstream/utils/config.ts index 0d14818a669..255d63dad7e 100644 --- a/script/upstream/utils/config.ts +++ b/script/upstream/utils/config.ts @@ -74,7 +74,6 @@ export const defaultConfig: MergeConfig = { "SECURITY.md", "AGENTS.md", // GitHub workflows - MANUAL REVIEW (can break CI/CD) - ".github/workflows/publish-stable.yml", ".github/workflows/publish.yml", ".github/workflows/close-stale-prs.yml", ".github/pull_request_template.md", diff --git a/turbo.json b/turbo.json index 0cc3904a8b2..ba0d254ef83 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://turborepo.com/schema.json", + "$schema": "https://v2-8-13.turborepo.dev/schema.json", "globalEnv": ["CI", "KILO_DISABLE_SHARE"], "globalPassThroughEnv": ["CI", "KILO_DISABLE_SHARE"], "tasks": {